1010import inspect
1111import numbers
1212import shutil
13- from typing import Dict , List , Union
13+ from typing import Dict , List , Optional , Union
1414
1515from . import cmd2
1616from . import utils
2222# If no descriptive header is supplied, then this will be used instead
2323DEFAULT_DESCRIPTIVE_HEADER = 'Description'
2424
25- # Name of the choice/completer function argument that, if present, will be passed a Namespace of
26- # command line tokens up through the token being completed mapped to their argparse destination.
25+ # Name of the choice/completer function argument that, if present, will be passed a dictionary of
26+ # command line tokens up through the token being completed mapped to their argparse destination name .
2727ARG_TOKENS = 'arg_tokens'
2828
2929
@@ -97,23 +97,31 @@ def __init__(self, arg_action: argparse.Action) -> None:
9797 self .min = self .action .nargs
9898 self .max = self .action .nargs
9999
100- def __init__ (self , parser : argparse .ArgumentParser , cmd2_app : cmd2 .Cmd ) -> None :
100+ def __init__ (self , parser : argparse .ArgumentParser , cmd2_app : cmd2 .Cmd , * ,
101+ parent_tokens : Optional [Dict [str , List [str ]]] = None ) -> None :
101102 """
102103 Create an AutoCompleter
103104
104105 :param parser: ArgumentParser instance
105106 :param cmd2_app: reference to the Cmd2 application that owns this AutoCompleter
107+ :param parent_tokens: optional dictionary mapping parent parsers' arg names to their tokens
108+ this is only used by AutoCompleter when recursing on subcommand parsers
109+ Defaults to None
106110 """
107111 self ._parser = parser
108112 self ._cmd2_app = cmd2_app
109113
114+ if parent_tokens is None :
115+ parent_tokens = dict ()
116+ self ._parent_tokens = parent_tokens
117+
110118 self ._flags = [] # all flags in this command
111119 self ._flag_to_action = {} # maps flags to the argparse action object
112120 self ._positional_actions = [] # actions for positional arguments (by position index)
113121 self ._subcommand_action = None # this will be set if self._parser has subcommands
114122
115123 # Start digging through the argparse structures.
116- # _actions is the top level container of parameter definitions
124+ # _actions is the top level container of parameter definitions
117125 for action in self ._parser ._actions :
118126 # if the parameter is flag based, it will have option_strings
119127 if action .option_strings :
@@ -152,13 +160,13 @@ def complete_command(self, tokens: List[str], text: str, line: str, begidx: int,
152160 matched_flags = []
153161
154162 # Keeps track of arguments we've seen and any tokens they consumed
155- consumed_arg_values = dict () # dict(action -> tokens)
163+ consumed_arg_values = dict () # dict(arg_name -> List[ tokens] )
156164
157165 def consume_argument (arg_state : AutoCompleter ._ArgumentState ) -> None :
158166 """Consuming token as an argument"""
159167 arg_state .count += 1
160- consumed_arg_values .setdefault (arg_state .action , [])
161- consumed_arg_values [arg_state .action ].append (token )
168+ consumed_arg_values .setdefault (arg_state .action . dest , [])
169+ consumed_arg_values [arg_state .action . dest ].append (token )
162170
163171 #############################################################################################
164172 # Parse all but the last token
@@ -218,14 +226,14 @@ def consume_argument(arg_state: AutoCompleter._ArgumentState) -> None:
218226 argparse ._CountAction )):
219227 # Flags with action set to append, append_const, and count can be reused
220228 # Therefore don't erase any tokens already consumed for this flag
221- consumed_arg_values .setdefault (action , [])
229+ consumed_arg_values .setdefault (action . dest , [])
222230 else :
223231 # This flag is not resusable, so mark that we've seen it
224232 matched_flags .extend (action .option_strings )
225233
226234 # It's possible we already have consumed values for this flag if it was used
227235 # earlier in the command line. Reset them now for this use of it.
228- consumed_arg_values [action ] = []
236+ consumed_arg_values [action . dest ] = []
229237
230238 new_arg_state = AutoCompleter ._ArgumentState (action )
231239
@@ -256,7 +264,15 @@ def consume_argument(arg_state: AutoCompleter._ArgumentState) -> None:
256264 # Are we at a subcommand? If so, forward to the matching completer
257265 if action == self ._subcommand_action :
258266 if token in self ._subcommand_action .choices :
259- completer = AutoCompleter (self ._subcommand_action .choices [token ], self ._cmd2_app )
267+ # Merge self._parent_tokens and consumed_arg_values
268+ parent_tokens = {** self ._parent_tokens , ** consumed_arg_values }
269+
270+ # Include the subcommand name if its destination was set
271+ if action .dest != argparse .SUPPRESS :
272+ parent_tokens [action .dest ] = [token ]
273+
274+ completer = AutoCompleter (self ._subcommand_action .choices [token ], self ._cmd2_app ,
275+ parent_tokens = parent_tokens )
260276 return completer .complete_command (tokens [token_index :], text , line , begidx , endidx )
261277 else :
262278 # Invalid subcommand entered, so no way to complete remaining tokens
@@ -439,7 +455,7 @@ def format_help(self, tokens: List[str]) -> str:
439455
440456 def _complete_for_arg (self , arg_action : argparse .Action ,
441457 text : str , line : str , begidx : int , endidx : int ,
442- consumed_arg_values : Dict [argparse . Action , List [str ]]) -> List [str ]:
458+ consumed_arg_values : Dict [str , List [str ]]) -> List [str ]:
443459 """Tab completion routine for an argparse argument"""
444460 # Check if the arg provides choices to the user
445461 if arg_action .choices is not None :
@@ -457,18 +473,15 @@ def _complete_for_arg(self, arg_action: argparse.Action,
457473 if arg_choices .is_method :
458474 args .append (self ._cmd2_app )
459475
460- # If arg_choices.to_call accepts an argument called arg_tokens, then convert
461- # consumed_arg_values into an argparse Namespace and pass it to the function
476+ # Check if arg_choices.to_call expects arg_tokens
462477 to_call_params = inspect .signature (arg_choices .to_call ).parameters
463478 if ARG_TOKENS in to_call_params :
464- arg_tokens = argparse .Namespace ()
465- for action , tokens in consumed_arg_values .items ():
466- setattr (arg_tokens , action .dest , tokens )
479+ # Merge self._parent_tokens and consumed_arg_values
480+ arg_tokens = {** self ._parent_tokens , ** consumed_arg_values }
467481
468- # Include the token being completed in the Namespace
469- tokens = getattr (arg_tokens , arg_action .dest , [])
470- tokens .append (text )
471- setattr (arg_tokens , arg_action .dest , tokens )
482+ # Include the token being completed
483+ arg_tokens .setdefault (arg_action .dest , [])
484+ arg_tokens [arg_action .dest ].append (text )
472485
473486 # Add the namespace to the keyword arguments for the function we are calling
474487 kwargs [ARG_TOKENS ] = arg_tokens
@@ -498,7 +511,7 @@ def _complete_for_arg(self, arg_action: argparse.Action,
498511 arg_choices [index ] = str (choice )
499512
500513 # Filter out arguments we already used
501- used_values = consumed_arg_values .get (arg_action , [])
514+ used_values = consumed_arg_values .get (arg_action . dest , [])
502515 arg_choices = [choice for choice in arg_choices if choice not in used_values ]
503516
504517 # Do tab completion on the choices
0 commit comments