diff options
author | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2019-09-16 15:36:39 -0400 |
---|---|---|
committer | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2019-09-16 15:36:39 -0400 |
commit | e6585d155810f9afb2a246a71f939e1c0d511694 (patch) | |
tree | 1d41d0c8e2cdd6a1ff50edbdf48f50a3b22892b4 /cmd2 | |
parent | 9a7818b5a0e22e4ee5b107f6fdcceb3d3612ffd4 (diff) | |
download | cmd2-git-e6585d155810f9afb2a246a71f939e1c0d511694.tar.gz |
Changed arg_tokens to a dictionary
Including tokens from parent parsers in arg_tokens when subcommands are used
Diffstat (limited to 'cmd2')
-rw-r--r-- | cmd2/argparse_completer.py | 57 | ||||
-rw-r--r-- | cmd2/argparse_custom.py | 8 | ||||
-rwxr-xr-x | cmd2/cmd2.py | 6 |
3 files changed, 42 insertions, 29 deletions
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index f1858f23..fb485348 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -10,7 +10,7 @@ import argparse import inspect import numbers import shutil -from typing import Dict, List, Union +from typing import Dict, List, Optional, Union from . import cmd2 from . import utils @@ -22,8 +22,8 @@ from .rl_utils import rl_force_redisplay # If no descriptive header is supplied, then this will be used instead DEFAULT_DESCRIPTIVE_HEADER = 'Description' -# Name of the choice/completer function argument that, if present, will be passed a Namespace of -# command line tokens up through the token being completed mapped to their argparse destination. +# Name of the choice/completer function argument that, if present, will be passed a dictionary of +# command line tokens up through the token being completed mapped to their argparse destination name. ARG_TOKENS = 'arg_tokens' @@ -97,23 +97,31 @@ class AutoCompleter(object): self.min = self.action.nargs self.max = self.action.nargs - def __init__(self, parser: argparse.ArgumentParser, cmd2_app: cmd2.Cmd) -> None: + def __init__(self, parser: argparse.ArgumentParser, cmd2_app: cmd2.Cmd, *, + parent_tokens: Optional[Dict[str, List[str]]] = None) -> None: """ Create an AutoCompleter :param parser: ArgumentParser instance :param cmd2_app: reference to the Cmd2 application that owns this AutoCompleter + :param parent_tokens: optional dictionary mapping parent parsers' arg names to their tokens + this is only used by AutoCompleter when recursing on subcommand parsers + Defaults to None """ self._parser = parser self._cmd2_app = cmd2_app + if parent_tokens is None: + parent_tokens = dict() + self._parent_tokens = parent_tokens + self._flags = [] # all flags in this command self._flag_to_action = {} # maps flags to the argparse action object self._positional_actions = [] # actions for positional arguments (by position index) self._subcommand_action = None # this will be set if self._parser has subcommands # Start digging through the argparse structures. - # _actions is the top level container of parameter definitions + # _actions is the top level container of parameter definitions for action in self._parser._actions: # if the parameter is flag based, it will have option_strings if action.option_strings: @@ -152,13 +160,13 @@ class AutoCompleter(object): matched_flags = [] # Keeps track of arguments we've seen and any tokens they consumed - consumed_arg_values = dict() # dict(action -> tokens) + consumed_arg_values = dict() # dict(arg_name -> List[tokens]) def consume_argument(arg_state: AutoCompleter._ArgumentState) -> None: """Consuming token as an argument""" arg_state.count += 1 - consumed_arg_values.setdefault(arg_state.action, []) - consumed_arg_values[arg_state.action].append(token) + consumed_arg_values.setdefault(arg_state.action.dest, []) + consumed_arg_values[arg_state.action.dest].append(token) ############################################################################################# # Parse all but the last token @@ -218,14 +226,14 @@ class AutoCompleter(object): argparse._CountAction)): # Flags with action set to append, append_const, and count can be reused # Therefore don't erase any tokens already consumed for this flag - consumed_arg_values.setdefault(action, []) + consumed_arg_values.setdefault(action.dest, []) else: # This flag is not resusable, so mark that we've seen it matched_flags.extend(action.option_strings) # It's possible we already have consumed values for this flag if it was used # earlier in the command line. Reset them now for this use of it. - consumed_arg_values[action] = [] + consumed_arg_values[action.dest] = [] new_arg_state = AutoCompleter._ArgumentState(action) @@ -256,7 +264,15 @@ class AutoCompleter(object): # Are we at a subcommand? If so, forward to the matching completer if action == self._subcommand_action: if token in self._subcommand_action.choices: - completer = AutoCompleter(self._subcommand_action.choices[token], self._cmd2_app) + # Merge self._parent_tokens and consumed_arg_values + parent_tokens = {**self._parent_tokens, **consumed_arg_values} + + # Include the subcommand name if its destination was set + if action.dest != argparse.SUPPRESS: + parent_tokens[action.dest] = [token] + + completer = AutoCompleter(self._subcommand_action.choices[token], self._cmd2_app, + parent_tokens=parent_tokens) return completer.complete_command(tokens[token_index:], text, line, begidx, endidx) else: # Invalid subcommand entered, so no way to complete remaining tokens @@ -439,7 +455,7 @@ class AutoCompleter(object): def _complete_for_arg(self, arg_action: argparse.Action, text: str, line: str, begidx: int, endidx: int, - consumed_arg_values: Dict[argparse.Action, List[str]]) -> List[str]: + consumed_arg_values: Dict[str, List[str]]) -> List[str]: """Tab completion routine for an argparse argument""" # Check if the arg provides choices to the user if arg_action.choices is not None: @@ -457,18 +473,15 @@ class AutoCompleter(object): if arg_choices.is_method: args.append(self._cmd2_app) - # If arg_choices.to_call accepts an argument called arg_tokens, then convert - # consumed_arg_values into an argparse Namespace and pass it to the function + # Check if arg_choices.to_call expects arg_tokens to_call_params = inspect.signature(arg_choices.to_call).parameters if ARG_TOKENS in to_call_params: - arg_tokens = argparse.Namespace() - for action, tokens in consumed_arg_values.items(): - setattr(arg_tokens, action.dest, tokens) + # Merge self._parent_tokens and consumed_arg_values + arg_tokens = {**self._parent_tokens, **consumed_arg_values} - # Include the token being completed in the Namespace - tokens = getattr(arg_tokens, arg_action.dest, []) - tokens.append(text) - setattr(arg_tokens, arg_action.dest, tokens) + # Include the token being completed + arg_tokens.setdefault(arg_action.dest, []) + arg_tokens[arg_action.dest].append(text) # Add the namespace to the keyword arguments for the function we are calling kwargs[ARG_TOKENS] = arg_tokens @@ -498,7 +511,7 @@ class AutoCompleter(object): arg_choices[index] = str(choice) # Filter out arguments we already used - used_values = consumed_arg_values.get(arg_action, []) + used_values = consumed_arg_values.get(arg_action.dest, []) arg_choices = [choice for choice in arg_choices if choice not in used_values] # Do tab completion on the choices diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py index 59a00f4c..940d6064 100644 --- a/cmd2/argparse_custom.py +++ b/cmd2/argparse_custom.py @@ -103,17 +103,17 @@ Tab Completion: set_completer_method(action, method) There are times when what's being tab completed is determined by a previous argument on the command line. - In theses cases, Autocompleter can pass an argparse Namespace that maps the command line tokens up through the - one being completed to their argparse argument. To receive this Namespace, your choices/completer function + In theses cases, Autocompleter can pass a dictionary that maps the command line tokens up through the one + being completed to their argparse argument name. To receive this dictionary, your choices/completer function should have an argument called arg_tokens. Example: def my_choices_method(self, arg_tokens) def my_completer_method(self, text, line, begidx, endidx, arg_tokens) - All members of the arg_tokens Namespace are lists, even if a particular argument expects only 1 token. Since + All values of the arg_tokens dictionary are lists, even if a particular argument expects only 1 token. Since AutoCompleter is for tab completion, it does not convert the tokens to their actual argument types or validate - their values. All tokens are stored in the Namespace as the raw strings provided on the command line. It is up to + their values. All tokens are stored in the dictionary as the raw strings provided on the command line. It is up to the developer to determine if the user entered the correct argument type (e.g. int) and validate their values. CompletionItem Class: diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index e001e75e..2cc412a9 100755 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -2669,11 +2669,11 @@ class Cmd(cmd.Cmd): return utils.basic_complete(text, line, begidx, endidx, strs_to_match) def complete_help_subcommand(self, text: str, line: str, begidx: int, endidx: int, - arg_tokens: argparse.Namespace) -> List[str]: + arg_tokens: Dict[str, List[str]]) -> List[str]: """Completes the subcommand argument of help""" # Make sure we have a command whose subcommands we will complete - command = arg_tokens.command[0] + command = arg_tokens['command'][0] if not command: return [] @@ -2684,7 +2684,7 @@ class Cmd(cmd.Cmd): return [] # Combine the command and its subcommand tokens for the AutoCompleter - tokens = [command] + arg_tokens.subcommand + tokens = [command] + arg_tokens['subcommand'] from .argparse_completer import AutoCompleter completer = AutoCompleter(argparser, self) |