From 6f1eb6860c546631077ed21bdd36b9d665ac79b5 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Thu, 23 Aug 2018 00:29:24 -0400 Subject: Fixed bug where alias was dropping quotes --- cmd2/parsing.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'cmd2/parsing.py') diff --git a/cmd2/parsing.py b/cmd2/parsing.py index b67cef10..67e97c2b 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -5,7 +5,7 @@ import os import re import shlex -from typing import List, Tuple, Dict +from typing import List, Tuple, Dict, Union from . import constants from . import utils @@ -105,6 +105,17 @@ class Statement(str): rtn = None return rtn + @property + def arg_list(self) -> Union[List[str], None]: + """ + Returns a list of the arguments to the command, not including any output + redirection or terminators. quoted arguments remain quoted. + """ + if self.args is None: + return None + + return self.args.split() + def __setattr__(self, name, value): """Statement instances should feel immutable; raise ValueError""" raise ValueError -- cgit v1.2.1 From 06163734b94b71ce52938f27b5718bd66357553a Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Thu, 23 Aug 2018 00:48:15 -0400 Subject: No longer returning None from arg_list --- cmd2/parsing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'cmd2/parsing.py') diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 67e97c2b..875e54c9 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -5,7 +5,7 @@ import os import re import shlex -from typing import List, Tuple, Dict, Union +from typing import List, Tuple, Dict from . import constants from . import utils @@ -106,13 +106,13 @@ class Statement(str): return rtn @property - def arg_list(self) -> Union[List[str], None]: + def arg_list(self) -> List[str]: """ Returns a list of the arguments to the command, not including any output redirection or terminators. quoted arguments remain quoted. """ if self.args is None: - return None + return [] return self.args.split() -- cgit v1.2.1 From 11e3eabbff8f80c9c85c04b7e9d6071246856bcf Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Thu, 23 Aug 2018 13:12:46 -0400 Subject: Removed Statement.args since it was redundant. Replaced with already parsed list of args with quotes preserved. --- cmd2/parsing.py | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) (limited to 'cmd2/parsing.py') diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 875e54c9..2a4ae56f 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -5,7 +5,7 @@ import os import re import shlex -from typing import List, Tuple, Dict +from typing import List, Tuple, Dict, Union from . import constants from . import utils @@ -33,10 +33,10 @@ class Statement(str): :var multiline_command: if the command is a multiline command, the name of the command, otherwise None :type command: str or None - :var args: the arguments to the command, not including any output + :var arg_list: list of arguments to the command, not including any output redirection or terminators. quoted arguments remain quoted. - :type args: str or None + :type arg_list: list :var: argv: a list of arguments a la sys.argv. Quotes, if any, are removed from the elements of the list, and aliases and shortcuts are expanded @@ -61,7 +61,7 @@ class Statement(str): *, raw: str = None, command: str = None, - args: str = None, + arg_list: List[str] = None, argv: List[str] = None, multiline_command: str = None, terminator: str = None, @@ -78,7 +78,9 @@ class Statement(str): stmt = str.__new__(cls, obj) object.__setattr__(stmt, "raw", raw) object.__setattr__(stmt, "command", command) - object.__setattr__(stmt, "args", args) + if arg_list is None: + arg_list = [] + object.__setattr__(stmt, "arg_list", arg_list) if argv is None: argv = [] object.__setattr__(stmt, "argv", argv) @@ -96,26 +98,15 @@ class Statement(str): Quoted arguments remain quoted. """ - if self.command and self.args: - rtn = '{} {}'.format(self.command, self.args) + if self.command and self: + rtn = '{} {}'.format(self.command, self) elif self.command: - # we are trusting that if we get here that self.args is None + # there were no arguments to the command rtn = self.command else: rtn = None return rtn - @property - def arg_list(self) -> List[str]: - """ - Returns a list of the arguments to the command, not including any output - redirection or terminators. quoted arguments remain quoted. - """ - if self.args is None: - return [] - - return self.args.split() - def __setattr__(self, name, value): """Statement instances should feel immutable; raise ValueError""" raise ValueError @@ -403,7 +394,7 @@ class StatementParser: statement = Statement('' if args is None else args, raw=line, command=command, - args=args, + arg_list=[] if len(argv) <= 1 else argv[1:], argv=list(map(lambda x: utils.strip_quotes(x), argv)), multiline_command=multiline_command, terminator=terminator, @@ -428,10 +419,9 @@ class StatementParser: values in the following attributes: - raw - command - - args Different from parse(), this method does not remove redundant whitespace - within statement.args. It does however, ensure args does not have + within the statement. It does however, ensure statement does not have leading or trailing whitespace. """ # expand shortcuts and aliases @@ -453,7 +443,7 @@ class StatementParser: # no trailing whitespace args = line[match.end(2):].rstrip() # if the command is none that means the input was either empty - # or something wierd like '>'. args should be None if we couldn't + # or something weird like '>'. args should be None if we couldn't # parse a command if not command or not args: args = None @@ -470,7 +460,6 @@ class StatementParser: statement = Statement('' if args is None else args, raw=rawinput, command=command, - args=args, multiline_command=multiline_command, ) return statement -- cgit v1.2.1 From 1a7f408fa0b259b30971cda477d952e202ac4fc2 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Sat, 1 Sep 2018 19:40:38 -0400 Subject: Using empty strings and lists instead of None for default values in Statment --- cmd2/parsing.py | 135 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 71 insertions(+), 64 deletions(-) (limited to 'cmd2/parsing.py') diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 2a4ae56f..3737b736 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -29,46 +29,41 @@ class Statement(str): :var raw: string containing exactly what we input by the user :type raw: str :var command: the command, i.e. the first whitespace delimited word - :type command: str or None + :type command: str :var multiline_command: if the command is a multiline command, the name of the - command, otherwise None - :type command: str or None + command, otherwise empty + :type command: str :var arg_list: list of arguments to the command, not including any output redirection or terminators. quoted arguments remain quoted. :type arg_list: list - :var: argv: a list of arguments a la sys.argv. Quotes, if any, are removed - from the elements of the list, and aliases and shortcuts - are expanded - :type argv: list :var terminator: the character which terminated the multiline command, if there was one - :type terminator: str or None + :type terminator: str :var suffix: characters appearing after the terminator but before output redirection, if any - :type suffix: str or None + :type suffix: str :var pipe_to: if output was piped to a shell command, the shell command as a list of tokens :type pipe_to: list :var output: if output was redirected, the redirection token, i.e. '>>' - :type output: str or None + :type output: str :var output_to: if output was redirected, the destination file - :type output_to: str or None + :type output_to: str """ def __new__(cls, obj: object, *, - raw: str = None, - command: str = None, + raw: str = '', + command: str = '', arg_list: List[str] = None, - argv: List[str] = None, - multiline_command: str = None, - terminator: str = None, - suffix: str = None, - pipe_to: str = None, - output: str = None, - output_to: str = None + multiline_command: str = '', + terminator: str = '', + suffix: str = '', + pipe_to: List[str] = None, + output: str = '', + output_to: str = '' ): """Create a new instance of Statement @@ -81,30 +76,51 @@ class Statement(str): if arg_list is None: arg_list = [] object.__setattr__(stmt, "arg_list", arg_list) - if argv is None: - argv = [] - object.__setattr__(stmt, "argv", argv) object.__setattr__(stmt, "multiline_command", multiline_command) object.__setattr__(stmt, "terminator", terminator) object.__setattr__(stmt, "suffix", suffix) + if pipe_to is None: + pipe_to = [] object.__setattr__(stmt, "pipe_to", pipe_to) object.__setattr__(stmt, "output", output) object.__setattr__(stmt, "output_to", output_to) return stmt @property - def command_and_args(self): + def command_and_args(self) -> str: """Combine command and args with a space separating them. Quoted arguments remain quoted. """ - if self.command and self: - rtn = '{} {}'.format(self.command, self) + if self.command and self.args: + rtn = '{} {}'.format(self.command, self.args) elif self.command: # there were no arguments to the command rtn = self.command else: - rtn = None + rtn = '' + return rtn + + @property + def args(self) -> str: + """the arguments to the command, not including any output redirection or terminators. + + Quoted arguments remain quoted. + """ + return str(self) + + @property + def argv(self) -> List[str]: + """a list of arguments a la sys.argv. Quotes, if any, are removed + from the elements of the list, and aliases and shortcuts are expanded + """ + if self.command: + rtn = [utils.strip_quotes(self.command)] + for cur_token in self.arg_list: + rtn.append(utils.strip_quotes(cur_token)) + else: + rtn = [] + return rtn def __setattr__(self, name, value): @@ -233,7 +249,7 @@ class StatementParser: if match: if word == match.group(1): valid = True - errmsg = None + errmsg = '' return valid, errmsg def tokenize(self, line: str) -> List[str]: @@ -270,13 +286,13 @@ class StatementParser: # handle the special case/hardcoded terminator of a blank line # we have to do this before we tokenize because tokenizing # destroys all unquoted whitespace in the input - terminator = None + terminator = '' if line[-1:] == constants.LINE_FEED: terminator = constants.LINE_FEED - command = None - args = None - argv = None + command = '' + args = '' + arg_list = [] # lex the input into a list of tokens tokens = self.tokenize(line) @@ -304,8 +320,8 @@ class StatementParser: terminator_pos = len(tokens)+1 # everything before the first terminator is the command and the args - argv = tokens[:terminator_pos] - (command, args) = self._command_and_args(argv) + (command, args) = self._command_and_args(tokens[:terminator_pos]) + arg_list = tokens[1:terminator_pos] # we will set the suffix later # remove all the tokens before and including the terminator tokens = tokens[terminator_pos+1:] @@ -317,7 +333,7 @@ class StatementParser: # because redirectors can only be after a terminator command = testcommand args = testargs - argv = tokens + arg_list = tokens[1:] tokens = [] # check for a pipe to a shell process @@ -338,11 +354,11 @@ class StatementParser: tokens = tokens[:pipe_pos] except ValueError: # no pipe in the tokens - pipe_to = None + pipe_to = [] # check for output redirect - output = None - output_to = None + output = '' + output_to = '' try: output_pos = tokens.index(constants.REDIRECTION_OUTPUT) output = constants.REDIRECTION_OUTPUT @@ -376,26 +392,23 @@ class StatementParser: suffix = ' '.join(tokens) else: # no terminator, so whatever is left is the command and the args - suffix = None + suffix = '' if not command: # command could already have been set, if so, don't set it again - argv = tokens - (command, args) = self._command_and_args(argv) + (command, args) = self._command_and_args(tokens) + arg_list = tokens[1:] # set multiline if command in self.multiline_commands: multiline_command = command else: - multiline_command = None + multiline_command = '' # build the statement - # string representation of args must be an empty string instead of - # None for compatibility with standard library cmd - statement = Statement('' if args is None else args, + statement = Statement(args, raw=line, command=command, - arg_list=[] if len(argv) <= 1 else argv[1:], - argv=list(map(lambda x: utils.strip_quotes(x), argv)), + arg_list=arg_list, multiline_command=multiline_command, terminator=terminator, suffix=suffix, @@ -419,6 +432,7 @@ class StatementParser: values in the following attributes: - raw - command + - multiline_command Different from parse(), this method does not remove redundant whitespace within the statement. It does however, ensure statement does not have @@ -427,37 +441,33 @@ class StatementParser: # expand shortcuts and aliases line = self._expand(rawinput) - command = None - args = None + command = '' + args = '' match = self._command_pattern.search(line) if match: # we got a match, extract the command command = match.group(1) - # the match could be an empty string, if so, turn it into none - if not command: - command = None + # the _command_pattern regex is designed to match the spaces # between command and args with a second match group. Using # the end of the second match group ensures that args has # no leading whitespace. The rstrip() makes sure there is # no trailing whitespace args = line[match.end(2):].rstrip() - # if the command is none that means the input was either empty - # or something weird like '>'. args should be None if we couldn't + # if the command is empty that means the input was either empty + # or something weird like '>'. args should be empty if we couldn't # parse a command if not command or not args: - args = None + args = '' # set multiline if command in self.multiline_commands: multiline_command = command else: - multiline_command = None + multiline_command = '' # build the statement - # string representation of args must be an empty string instead of - # None for compatibility with standard library cmd - statement = Statement('' if args is None else args, + statement = Statement(args, raw=rawinput, command=command, multiline_command=multiline_command, @@ -503,12 +513,9 @@ class StatementParser: def _command_and_args(tokens: List[str]) -> Tuple[str, str]: """Given a list of tokens, return a tuple of the command and the args as a string. - - The args string will be '' instead of None to retain backwards compatibility - with cmd in the standard library. """ - command = None - args = None + command = '' + args = '' if tokens: command = tokens[0] -- cgit v1.2.1 From 5957ecbe2acd177c048e53dc4fae17c25e6e14e9 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Sat, 1 Sep 2018 19:42:25 -0400 Subject: Removed unused import --- cmd2/parsing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cmd2/parsing.py') diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 3737b736..af97cdc3 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -5,7 +5,7 @@ import os import re import shlex -from typing import List, Tuple, Dict, Union +from typing import List, Tuple, Dict from . import constants from . import utils -- cgit v1.2.1 From fe0442973153896721f12ceeb08822bd478d4fca Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Sat, 1 Sep 2018 19:50:23 -0400 Subject: Updated comment --- cmd2/parsing.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'cmd2/parsing.py') diff --git a/cmd2/parsing.py b/cmd2/parsing.py index af97cdc3..326ef08d 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -434,9 +434,8 @@ class StatementParser: - command - multiline_command - Different from parse(), this method does not remove redundant whitespace - within the statement. It does however, ensure statement does not have - leading or trailing whitespace. + Different from parse(), this method does not remove redundant whitespace within args. + However, it does ensure args has no leading or trailing whitespace. """ # expand shortcuts and aliases line = self._expand(rawinput) -- cgit v1.2.1 From 880631eb0437e40ee317d34bf8ca54c9394cd6b8 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Wed, 5 Sep 2018 23:44:33 -0400 Subject: Made it possible for editors like Pycharm to see members of Statement in tab completion. This also fixed many warnings. --- cmd2/parsing.py | 77 +++++++++++++++++++++++++++------------------------------ 1 file changed, 37 insertions(+), 40 deletions(-) (limited to 'cmd2/parsing.py') diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 326ef08d..8b25684a 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -25,36 +25,9 @@ class Statement(str): The string portion of the class contains the arguments, but not the command, nor the output redirection clauses. - - :var raw: string containing exactly what we input by the user - :type raw: str - :var command: the command, i.e. the first whitespace delimited word - :type command: str - :var multiline_command: if the command is a multiline command, the name of the - command, otherwise empty - :type command: str - :var arg_list: list of arguments to the command, not including any output - redirection or terminators. quoted arguments remain - quoted. - :type arg_list: list - :var terminator: the character which terminated the multiline command, if - there was one - :type terminator: str - :var suffix: characters appearing after the terminator but before output - redirection, if any - :type suffix: str - :var pipe_to: if output was piped to a shell command, the shell command - as a list of tokens - :type pipe_to: list - :var output: if output was redirected, the redirection token, i.e. '>>' - :type output: str - :var output_to: if output was redirected, the destination file - :type output_to: str - """ def __new__(cls, - obj: object, - *, + value: str, raw: str = '', command: str = '', arg_list: List[str] = None, @@ -65,25 +38,49 @@ class Statement(str): output: str = '', output_to: str = '' ): + """ + :param value: The arguments, but not the command, nor the output redirection clauses. + :param raw: string containing exactly what we input by the user + :param command: the command, i.e. the first whitespace delimited word + :param arg_list: list of arguments to the command, not including any output + redirection or terminators. quoted arguments remain quoted. + :param multiline_command: if the command is a multiline command, the name of the + command, otherwise empty + :param terminator: the character which terminated the multiline command, if + there was one + :param suffix: characters appearing after the terminator but before output redirection, if any + :param pipe_to: if output was piped to a shell command, the shell command as a list of tokens + :param output: if output was redirected, the redirection token, i.e. '>>' + :param output_to: if output was redirected, the destination file + """ """Create a new instance of Statement We must override __new__ because we are subclassing `str` which is immutable. """ - stmt = str.__new__(cls, obj) - object.__setattr__(stmt, "raw", raw) - object.__setattr__(stmt, "command", command) + stmt = super().__new__(cls, value) + stmt.raw = raw + stmt.command = command + if arg_list is None: arg_list = [] - object.__setattr__(stmt, "arg_list", arg_list) - object.__setattr__(stmt, "multiline_command", multiline_command) - object.__setattr__(stmt, "terminator", terminator) - object.__setattr__(stmt, "suffix", suffix) + stmt.arg_list = arg_list + + stmt.multiline_command = multiline_command + stmt.terminator = terminator + stmt.suffix = suffix + if pipe_to is None: pipe_to = [] - object.__setattr__(stmt, "pipe_to", pipe_to) - object.__setattr__(stmt, "output", output) - object.__setattr__(stmt, "output_to", output_to) + stmt.pipe_to = pipe_to + + stmt.output = output + stmt.output_to = output_to + + # Make Statement feel immutable by overriding these functions + stmt.__setattr__ = stmt.__stmt_setattr__ + stmt.__delattr__ = stmt.__stmt_delattr__ + return stmt @property @@ -123,11 +120,11 @@ class Statement(str): return rtn - def __setattr__(self, name, value): + def __stmt_setattr__(self, name, value): """Statement instances should feel immutable; raise ValueError""" raise ValueError - def __delattr__(self, name): + def __stmt_delattr__(self, name): """Statement instances should feel immutable; raise ValueError""" raise ValueError -- cgit v1.2.1 From 2b42ac5507acb424fd30534397b60c1089bd09b4 Mon Sep 17 00:00:00 2001 From: kotfu Date: Wed, 5 Sep 2018 22:16:04 -0600 Subject: Add back previously removed tests for statement.args --- cmd2/parsing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cmd2/parsing.py') diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 8b25684a..082cc7aa 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -425,7 +425,7 @@ class StatementParser: This method is used by tab completion code and therefore must not generate an exception if there are unclosed quotes. - The Statement object returned by this method can at most contained + The Statement object returned by this method can at most contain values in the following attributes: - raw - command -- cgit v1.2.1 From 7801d80ebaf2851ec0647733e385e88e90b2ae18 Mon Sep 17 00:00:00 2001 From: kotfu Date: Wed, 5 Sep 2018 22:25:37 -0600 Subject: Replace previously removed Statement initialization parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Statement is a subclass of str. Statement.__new__ should behave like str.__new__. Meaning it should accept any object as it’s argument, and our extended initialization parameters (i.e. command, arg_list) should not be allowed as positional arguments, only named arguments. --- cmd2/parsing.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'cmd2/parsing.py') diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 082cc7aa..553c5b9a 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -27,7 +27,8 @@ class Statement(str): the output redirection clauses. """ def __new__(cls, - value: str, + obj: object, + *, raw: str = '', command: str = '', arg_list: List[str] = None, @@ -58,7 +59,7 @@ class Statement(str): We must override __new__ because we are subclassing `str` which is immutable. """ - stmt = super().__new__(cls, value) + stmt = super().__new__(cls, obj) stmt.raw = raw stmt.command = command -- cgit v1.2.1 From f5e94b366fcffd141890a4bd8c9d2259afd1f43c Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 6 Sep 2018 19:17:14 -0700 Subject: Use attrs on Statement object to do immutability --- cmd2/parsing.py | 99 +++++++++++++++++++++++---------------------------------- 1 file changed, 40 insertions(+), 59 deletions(-) (limited to 'cmd2/parsing.py') diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 553c5b9a..a5dbb72c 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -7,10 +7,13 @@ import re import shlex from typing import List, Tuple, Dict +import attr + from . import constants from . import utils +@attr.s(frozen=True) class Statement(str): """String subclass with additional attributes to store the results of parsing. @@ -26,9 +29,38 @@ class Statement(str): The string portion of the class contains the arguments, but not the command, nor the output redirection clauses. """ + # the arguments, but not the command, nor the output redirection clauses. + args = attr.ib(default='', validator=attr.validators.instance_of(str), type=str) + + # string containing exactly what we input by the user + raw = attr.ib(default='', validator=attr.validators.instance_of(str), type=str) + + # the command, i.e. the first whitespace delimited word + command = attr.ib(default='', validator=attr.validators.instance_of(str), type=str) + + # list of arguments to the command, not including any output redirection or terminators; quoted args remain quoted + arg_list = attr.ib(factory=list, validator=attr.validators.instance_of(list), type=List[str]) + + # if the command is a multiline command, the name of the command, otherwise empty + multiline_command = attr.ib(default='', validator=attr.validators.instance_of(str), type=str) + + # the character which terminated the multiline command, if there was one + terminator = attr.ib(default='', validator=attr.validators.instance_of(str), type=str) + + # characters appearing after the terminator but before output redirection, if any + suffix = attr.ib(default='', validator=attr.validators.instance_of(str), type=str) + + # if output was piped to a shell command, the shell command as a list of tokens + pipe_to = attr.ib(factory=list, validator=attr.validators.instance_of(list), type=List[str]) + + # if output was redirected, the redirection token, i.e. '>>' + output = attr.ib(default='', validator=attr.validators.instance_of(str), type=str) + + # if output was redirected, the destination file + output_to = attr.ib(default='', validator=attr.validators.instance_of(str), type=str) + def __new__(cls, - obj: object, - *, + args: object, raw: str = '', command: str = '', arg_list: List[str] = None, @@ -39,49 +71,14 @@ class Statement(str): output: str = '', output_to: str = '' ): - """ - :param value: The arguments, but not the command, nor the output redirection clauses. - :param raw: string containing exactly what we input by the user - :param command: the command, i.e. the first whitespace delimited word - :param arg_list: list of arguments to the command, not including any output - redirection or terminators. quoted arguments remain quoted. - :param multiline_command: if the command is a multiline command, the name of the - command, otherwise empty - :param terminator: the character which terminated the multiline command, if - there was one - :param suffix: characters appearing after the terminator but before output redirection, if any - :param pipe_to: if output was piped to a shell command, the shell command as a list of tokens - :param output: if output was redirected, the redirection token, i.e. '>>' - :param output_to: if output was redirected, the destination file - """ - """Create a new instance of Statement - - We must override __new__ because we are subclassing `str` which is - immutable. - """ - stmt = super().__new__(cls, obj) - stmt.raw = raw - stmt.command = command - - if arg_list is None: - arg_list = [] - stmt.arg_list = arg_list + """Create a new instance of Statement. - stmt.multiline_command = multiline_command - stmt.terminator = terminator - stmt.suffix = suffix - - if pipe_to is None: - pipe_to = [] - stmt.pipe_to = pipe_to - - stmt.output = output - stmt.output_to = output_to - - # Make Statement feel immutable by overriding these functions - stmt.__setattr__ = stmt.__stmt_setattr__ - stmt.__delattr__ = stmt.__stmt_delattr__ + We must override __new__ because we are subclassing `str` which is immutable and takes a different number of + arguments as Statement. + NOTE: attrs takes care of initializing other members in the __init__ it generates. + """ + stmt = super().__new__(cls, args) return stmt @property @@ -99,14 +96,6 @@ class Statement(str): rtn = '' return rtn - @property - def args(self) -> str: - """the arguments to the command, not including any output redirection or terminators. - - Quoted arguments remain quoted. - """ - return str(self) - @property def argv(self) -> List[str]: """a list of arguments a la sys.argv. Quotes, if any, are removed @@ -121,14 +110,6 @@ class Statement(str): return rtn - def __stmt_setattr__(self, name, value): - """Statement instances should feel immutable; raise ValueError""" - raise ValueError - - def __stmt_delattr__(self, name): - """Statement instances should feel immutable; raise ValueError""" - raise ValueError - class StatementParser: """Parse raw text into command components. -- cgit v1.2.1 From a318745bed432fcd0527e691406c238c0d4d07fc Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Thu, 6 Sep 2018 19:30:47 -0700 Subject: Simplified override of __new__ Eliminated redundant type information, since that is all being provided by attr.ib() calls --- cmd2/parsing.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) (limited to 'cmd2/parsing.py') diff --git a/cmd2/parsing.py b/cmd2/parsing.py index a5dbb72c..7a9a9ff6 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -59,18 +59,7 @@ class Statement(str): # if output was redirected, the destination file output_to = attr.ib(default='', validator=attr.validators.instance_of(str), type=str) - def __new__(cls, - args: object, - raw: str = '', - command: str = '', - arg_list: List[str] = None, - multiline_command: str = '', - terminator: str = '', - suffix: str = '', - pipe_to: List[str] = None, - output: str = '', - output_to: str = '' - ): + def __new__(cls, value: object, *pos_args, **kw_args): """Create a new instance of Statement. We must override __new__ because we are subclassing `str` which is immutable and takes a different number of @@ -78,7 +67,7 @@ class Statement(str): NOTE: attrs takes care of initializing other members in the __init__ it generates. """ - stmt = super().__new__(cls, args) + stmt = super().__new__(cls, value) return stmt @property -- cgit v1.2.1 From e2adc8fe379aa40142d8fee1414a5b563084b704 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sat, 8 Sep 2018 16:21:02 -0400 Subject: Took a stab at improving documentation and unit tests for Statement.parse_command_only() Also slightly improved a few other unit tests --- cmd2/parsing.py | 1 + 1 file changed, 1 insertion(+) (limited to 'cmd2/parsing.py') diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 7a9a9ff6..f6186624 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -398,6 +398,7 @@ class StatementParser: The Statement object returned by this method can at most contain values in the following attributes: + - args - raw - command - multiline_command -- cgit v1.2.1 From 63f0aa3256ef4422c2b3eab3d9ea0d44a15cc93e Mon Sep 17 00:00:00 2001 From: kotfu Date: Sun, 9 Sep 2018 20:05:08 -0600 Subject: Added/updated documentation for `Statement` --- cmd2/parsing.py | 88 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 15 deletions(-) (limited to 'cmd2/parsing.py') diff --git a/cmd2/parsing.py b/cmd2/parsing.py index f6186624..61b6f745 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -28,6 +28,57 @@ class Statement(str): The string portion of the class contains the arguments, but not the command, nor the output redirection clauses. + + Here's some suggestions and best practices for how to use the attributes of this + object: + + command - the name of the command, shortcuts and aliases have already been + expanded + + args - the arguments to the command, excluding output redirection and command + terminators. If the user used quotes in their input, they remain here, + and you will have to handle them on your own. + + arg_list - the arguments to the command, excluding output redirection and + command terminators. Each argument is represented as an element + in the list. Quoted arguments remain quoted. If you want to + remove the quotes, use `cmd2.utils.strip_quotes()` or use + `argv[1:]` + + command_and_args - join the args and the command together with a space. Output + redirection is excluded. + + argv - this is a list of arguments in the style of `sys.argv`. The first element + of the list is the command. Subsequent elements of the list contain any + additional arguments, with quotes removed, just like bash would. This + is very useful if you are going to use `argparse.parse_args()`: + ``` + def do_mycommand(stmt): + mycommand_argparser.parse_args(stmt.argv) + ... + ``` + + raw - if you want full access to exactly what the user typed at the input prompt + you can get it, but you'll have to parse it on your own, including: + - shortcuts and aliases + - quoted commands and arguments + - output redirection + - multi-line command terminator handling + if you use multiline commands, all the input will be passed to you in + this string, but there will be embedded newlines where + the user hit return to continue the command on the next line. + + Tips: + + 1. `argparse` is your friend for anything complex. `cmd2` has two decorators + (`with_argparser`, and `with_argparser_and_unknown_args`) which you can use + to make your command method receive a namespace of parsed arguments, whether + positional or denoted with switches. + + 2. For commands with simple positional arguments, use `args` or `arg_list` + + 3. If you don't want to have to worry about quoted arguments, use + argv[1:], which strips them all off for you. """ # the arguments, but not the command, nor the output redirection clauses. args = attr.ib(default='', validator=attr.validators.instance_of(str), type=str) @@ -62,10 +113,11 @@ class Statement(str): def __new__(cls, value: object, *pos_args, **kw_args): """Create a new instance of Statement. - We must override __new__ because we are subclassing `str` which is immutable and takes a different number of - arguments as Statement. + We must override __new__ because we are subclassing `str` which is + immutable and takes a different number of arguments as Statement. - NOTE: attrs takes care of initializing other members in the __init__ it generates. + NOTE: attrs takes care of initializing other members in the __init__ it + generates. """ stmt = super().__new__(cls, value) return stmt @@ -74,7 +126,8 @@ class Statement(str): def command_and_args(self) -> str: """Combine command and args with a space separating them. - Quoted arguments remain quoted. + Quoted arguments remain quoted. Output redirection and piping are + excluded, as are any multiline command terminators. """ if self.command and self.args: rtn = '{} {}'.format(self.command, self.args) @@ -87,8 +140,10 @@ class Statement(str): @property def argv(self) -> List[str]: - """a list of arguments a la sys.argv. Quotes, if any, are removed - from the elements of the list, and aliases and shortcuts are expanded + """a list of arguments a la sys.argv. + + Quotes, if any, are removed from the elements of the list, and aliases + and shortcuts are expanded """ if self.command: rtn = [utils.strip_quotes(self.command)] @@ -103,7 +158,8 @@ class Statement(str): class StatementParser: """Parse raw text into command components. - Shortcuts is a list of tuples with each tuple containing the shortcut and the expansion. + Shortcuts is a list of tuples with each tuple containing the shortcut and + the expansion. """ def __init__( self, @@ -396,15 +452,16 @@ class StatementParser: This method is used by tab completion code and therefore must not generate an exception if there are unclosed quotes. - The Statement object returned by this method can at most contain - values in the following attributes: + The Statement object returned by this method can at most contain values + in the following attributes: - args - raw - command - multiline_command - Different from parse(), this method does not remove redundant whitespace within args. - However, it does ensure args has no leading or trailing whitespace. + Different from parse(), this method does not remove redundant whitespace + within args. However, it does ensure args has no leading or trailing + whitespace. """ # expand shortcuts and aliases line = self._expand(rawinput) @@ -503,10 +560,11 @@ class StatementParser: return matched_string def _split_on_punctuation(self, tokens: List[str]) -> List[str]: - """ - # Further splits tokens from a command line using punctuation characters - # as word breaks when they are in unquoted strings. Each run of punctuation - # characters is treated as a single token. + """Further splits tokens from a command line using punctuation characters + + Punctuation characters are treated as word breaks when they are in + unquoted strings. Each run of punctuation characters is treated as a + single token. :param tokens: the tokens as parsed by shlex :return: the punctuated tokens -- cgit v1.2.1 From caa3c4e1da22ca8f4b9086c1bc0e3fe3010a95ab Mon Sep 17 00:00:00 2001 From: kotfu Date: Sun, 9 Sep 2018 21:09:08 -0600 Subject: Fix bug in `parse_command_only` More robust unit tests identified a bug, which is also fixed in this commit. --- cmd2/parsing.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'cmd2/parsing.py') diff --git a/cmd2/parsing.py b/cmd2/parsing.py index 61b6f745..8edfacb9 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -452,13 +452,16 @@ class StatementParser: This method is used by tab completion code and therefore must not generate an exception if there are unclosed quotes. - The Statement object returned by this method can at most contain values + The `Statement` object returned by this method can at most contain values in the following attributes: - args - raw - command - multiline_command + `Statement.args` includes all output redirection clauses and command + terminators. + Different from parse(), this method does not remove redundant whitespace within args. However, it does ensure args has no leading or trailing whitespace. @@ -473,12 +476,10 @@ class StatementParser: # we got a match, extract the command command = match.group(1) - # the _command_pattern regex is designed to match the spaces - # between command and args with a second match group. Using - # the end of the second match group ensures that args has - # no leading whitespace. The rstrip() makes sure there is - # no trailing whitespace - args = line[match.end(2):].rstrip() + # take everything from the end of the first match group to + # the end of the line as the arguments (stripping leading + # and trailing spaces) + args = line[match.end(1):].strip() # if the command is empty that means the input was either empty # or something weird like '>'. args should be empty if we couldn't # parse a command -- cgit v1.2.1