diff options
-rw-r--r-- | cmd2/cmd2.py | 35 | ||||
-rw-r--r-- | cmd2/parsing.py | 90 |
2 files changed, 76 insertions, 49 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index e41d947d..d0a86391 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -49,7 +49,7 @@ from . import utils from .argparse_completer import AutoCompleter, ACArgumentParser, ACTION_ARG_CHOICES from .clipboard import can_clip, get_paste_buffer, write_to_paste_buffer from .history import History, HistoryItem -from .parsing import StatementParser, Statement, Macro, MacroArg, shlex_split, get_command_arg_list +from .parsing import StatementParser, Statement, Macro, MacroArg, shlex_split # Set up readline from .rl_utils import rl_type, RlType, rl_get_point, rl_set_prompt, vt100_support, rl_make_safe_prompt @@ -174,9 +174,13 @@ def with_argument_list(*args: List[Callable], preserve_quotes: bool = False) -> def arg_decorator(func: Callable): @functools.wraps(func) def cmd_wrapper(cmd2_instance, statement: Union[Statement, str]): - parsed_arglist = get_command_arg_list(statement, preserve_quotes) + _, parsed_arglist = cmd2_instance.statement_parser.get_command_arg_list(command_name, + statement, + preserve_quotes) + return func(cmd2_instance, parsed_arglist) + command_name = func.__name__[len(COMMAND_FUNC_PREFIX):] cmd_wrapper.__doc__ = func.__doc__ return cmd_wrapper @@ -193,7 +197,10 @@ def with_argparser_and_unknown_args(argparser: argparse.ArgumentParser, preserve :param argparser: unique instance of ArgumentParser :param preserve_quotes: if True, then arguments passed to argparse maintain their quotes - :return: function that gets passed argparse-parsed args and a list of unknown argument strings + :return: function that gets passed argparse-parsed args in a Namespace and a list of unknown argument strings + A member called __statement__ is added to the Namespace to provide command functions access to the + Statement object. This can be useful when knowledge of the command line is needed. + """ import functools @@ -201,18 +208,22 @@ def with_argparser_and_unknown_args(argparser: argparse.ArgumentParser, preserve def arg_decorator(func: Callable): @functools.wraps(func) def cmd_wrapper(cmd2_instance, statement: Union[Statement, str]): - parsed_arglist = get_command_arg_list(statement, preserve_quotes) + statement, parsed_arglist = cmd2_instance.statement_parser.get_command_arg_list(command_name, + statement, + preserve_quotes) try: args, unknown = argparser.parse_known_args(parsed_arglist) except SystemExit: return else: + setattr(args, '__statement__', statement) return func(cmd2_instance, args, unknown) # argparser defaults the program name to sys.argv[0] # we want it to be the name of our command - argparser.prog = func.__name__[len(COMMAND_FUNC_PREFIX):] + command_name = func.__name__[len(COMMAND_FUNC_PREFIX):] + argparser.prog = command_name # If the description has not been set, then use the method docstring if one exists if argparser.description is None and func.__doc__: @@ -236,7 +247,9 @@ def with_argparser(argparser: argparse.ArgumentParser, :param argparser: unique instance of ArgumentParser :param preserve_quotes: if True, then arguments passed to argparse maintain their quotes - :return: function that gets passed the argparse-parsed args + :return: function that gets passed the argparse-parsed args in a Namespace + A member called __statement__ is added to the Namespace to provide command functions access to the + Statement object. This can be useful when knowledge of the command line is needed. """ import functools @@ -244,19 +257,21 @@ def with_argparser(argparser: argparse.ArgumentParser, def arg_decorator(func: Callable): @functools.wraps(func) def cmd_wrapper(cmd2_instance, statement: Union[Statement, str]): - - parsed_arglist = get_command_arg_list(statement, preserve_quotes) - + statement, parsed_arglist = cmd2_instance.statement_parser.get_command_arg_list(command_name, + statement, + preserve_quotes) try: args = argparser.parse_args(parsed_arglist) except SystemExit: return else: + setattr(args, '__statement__', statement) return func(cmd2_instance, args) # argparser defaults the program name to sys.argv[0] # we want it to be the name of our command - argparser.prog = func.__name__[len(COMMAND_FUNC_PREFIX):] + command_name = func.__name__[len(COMMAND_FUNC_PREFIX):] + argparser.prog = command_name # If the description has not been set, then use the method docstring if one exists if argparser.description is None and func.__doc__: diff --git a/cmd2/parsing.py b/cmd2/parsing.py index d72ca4ec..cbb220fb 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -236,34 +236,6 @@ class Statement(str): return rtn -def get_command_arg_list(to_parse: Union[Statement, str], preserve_quotes: bool) -> List[str]: - """ - Called by the argument_list and argparse wrappers to retrieve just the arguments being - passed to their do_* methods as a list. - - :param to_parse: what is being passed to the do_* method. It can be one of two types: - 1. An already parsed Statement - 2. An argument string in cases where a do_* method is explicitly called - e.g.: Calling do_help('alias create') would cause to_parse to be 'alias create' - - :param preserve_quotes: if True, then quotes will not be stripped from the arguments - :return: the arguments in a list - """ - if isinstance(to_parse, Statement): - # In the case of a Statement, we already have what we need - if preserve_quotes: - return to_parse.arg_list - else: - return to_parse.argv[1:] - else: - # We have the arguments in a string. Use shlex to split it. - parsed_arglist = shlex_split(to_parse) - if not preserve_quotes: - parsed_arglist = [utils.strip_quotes(arg) for arg in parsed_arglist] - - return parsed_arglist - - class StatementParser: """Parse raw text into command components. @@ -371,16 +343,21 @@ class StatementParser: errmsg = '' return valid, errmsg - def tokenize(self, line: str) -> List[str]: - """Lex a string into a list of tokens. - - shortcuts and aliases are expanded and comments are removed - - Raises ValueError if there are unclosed quotation marks. + def tokenize(self, line: str, expand: bool = True) -> List[str]: + """ + Lex a string into a list of tokens. Shortcuts and aliases are expanded and comments are removed + + :param line: the command line being lexed + :param expand: if True, then aliases and shortcuts will be expanded + set this to False if the first token does not need to be expanded + because the command name is already known (Defaults to True) + :return: A list of tokens + :raises ValueError if there are unclosed quotation marks. """ # expand shortcuts and aliases - line = self._expand(line) + if expand: + line = self._expand(line) # check if this line is a comment if line.strip().startswith(constants.COMMENT_CHAR): @@ -393,12 +370,18 @@ class StatementParser: tokens = self._split_on_punctuation(tokens) return tokens - def parse(self, line: str) -> Statement: - """Tokenize the input and parse it into a Statement object, stripping + def parse(self, line: str, expand: bool = True) -> Statement: + """ + Tokenize the input and parse it into a Statement object, stripping comments, expanding aliases and shortcuts, and extracting output redirection directives. - Raises ValueError if there are unclosed quotation marks. + :param line: the command line being parsed + :param expand: if True, then aliases and shortcuts will be expanded + set this to False if the first token does not need to be expanded + because the command name is already known (Defaults to True) + :return: A parsed Statement + :raises ValueError if there are unclosed quotation marks """ # handle the special case/hardcoded terminator of a blank line @@ -413,7 +396,7 @@ class StatementParser: arg_list = [] # lex the input into a list of tokens - tokens = self.tokenize(line) + tokens = self.tokenize(line, expand) # of the valid terminators, find the first one to occur in the input terminator_pos = len(tokens) + 1 @@ -594,6 +577,35 @@ class StatementParser: ) return statement + def get_command_arg_list(self, command_name: str, to_parse: Union[Statement, str], + preserve_quotes: bool) -> Tuple[Statement, List[str]]: + """ + Called by the argument_list and argparse wrappers to retrieve just the arguments being + passed to their do_* methods as a list. + + :param command_name: name of the command being run + :param to_parse: what is being passed to the do_* method. It can be one of two types: + 1. An already parsed Statement + 2. An argument string in cases where a do_* method is explicitly called + e.g.: Calling do_help('alias create') would cause to_parse to be 'alias create' + + In this case, the string will be converted to a Statement and returned along + with the argument list. + + :param preserve_quotes: if True, then quotes will not be stripped from the arguments + :return: A tuple containing: + The Statement used to retrieve the arguments + The argument list + """ + # Check if to_parse needs to be converted to a Statement + if not isinstance(to_parse, Statement): + to_parse = self.parse(command_name + ' ' + to_parse, expand=False) + + if preserve_quotes: + return to_parse, to_parse.arg_list + else: + return to_parse, to_parse.argv[1:] + def _expand(self, line: str) -> str: """Expand shortcuts and aliases""" |