1010import inspect
1111import numbers
1212import shutil
13+ from collections import deque
1314from typing import Dict , List , Optional , Union
1415
1516from . import cmd2
@@ -116,13 +117,14 @@ def __init__(self, parser: argparse.ArgumentParser, cmd2_app: cmd2.Cmd, *,
116117 parent_tokens = dict ()
117118 self ._parent_tokens = parent_tokens
118119
119- self ._flags = [] # all flags in this command
120- self ._flag_to_action = {} # maps flags to the argparse action object
121- self ._positional_actions = [] # actions for positional arguments (by position index)
122- self ._subcommand_action = None # this will be set if self._parser has subcommands
120+ self ._flags = [] # all flags in this command
121+ self ._flag_to_action = {} # maps flags to the argparse action object
122+ self ._positional_actions = [] # actions for positional arguments (by position index)
123+ self ._mutually_exclusive_groups = [] # Each item is a list of actions
124+ self ._subcommand_action = None # this will be set if self._parser has subcommands
123125
124126 # Start digging through the argparse structures.
125- # _actions is the top level container of parameter definitions
127+ # _actions is the top level container of parameter definitions
126128 for action in self ._parser ._actions :
127129 # if the parameter is flag based, it will have option_strings
128130 if action .option_strings :
@@ -138,14 +140,17 @@ def __init__(self, parser: argparse.ArgumentParser, cmd2_app: cmd2.Cmd, *,
138140 if isinstance (action , argparse ._SubParsersAction ):
139141 self ._subcommand_action = action
140142
143+ # Keep track of what actions are in mutually exclusive groups
144+ for group in self ._parser ._mutually_exclusive_groups :
145+ self ._mutually_exclusive_groups .append (group ._group_actions )
146+
141147 def complete_command (self , tokens : List [str ], text : str , line : str , begidx : int , endidx : int ) -> List [str ]:
142148 """Complete the command using the argparse metadata and provided argument dictionary"""
143149 if not tokens :
144150 return []
145151
146- # Count which positional argument index we're at now. Loop through all tokens on the command line so far
147- # Skip any flags or flag parameter tokens
148- next_pos_arg_index = 0
152+ # Positionals args that are left to parse
153+ remaining_positionals = deque (self ._positional_actions )
149154
150155 # This gets set to True when flags will no longer be processed as argparse flags
151156 # That can happen when -- is used or an argument with nargs=argparse.REMAINDER is used
@@ -229,7 +234,7 @@ def consume_argument(arg_state: AutoCompleter._ArgumentState) -> None:
229234 # Therefore don't erase any tokens already consumed for this flag
230235 consumed_arg_values .setdefault (action .dest , [])
231236 else :
232- # This flag is not resusable , so mark that we've seen it
237+ # This flag is not reusable , so mark that we've seen it
233238 matched_flags .extend (action .option_strings )
234239
235240 # It's possible we already have consumed values for this flag if it was used
@@ -255,12 +260,9 @@ def consume_argument(arg_state: AutoCompleter._ArgumentState) -> None:
255260 else :
256261 # If we aren't current tracking a positional, then get the next positional arg to handle this token
257262 if pos_arg_state is None :
258- pos_index = next_pos_arg_index
259- next_pos_arg_index += 1
260-
261- # Make sure we are still have positional arguments to fill
262- if pos_index < len (self ._positional_actions ):
263- action = self ._positional_actions [pos_index ]
263+ # Make sure we are still have positional arguments to parse
264+ if remaining_positionals :
265+ action = remaining_positionals .popleft ()
264266
265267 # Are we at a subcommand? If so, forward to the matching completer
266268 if action == self ._subcommand_action :
@@ -295,10 +297,9 @@ def consume_argument(arg_state: AutoCompleter._ArgumentState) -> None:
295297 elif pos_arg_state .count >= pos_arg_state .max :
296298 pos_arg_state = None
297299
298- # Check if this a case in which we've finished all positionals before one that has nargs
299- # set to argparse.REMAINDER. At this point argparse allows no more flags to be processed.
300- if next_pos_arg_index < len (self ._positional_actions ) and \
301- self ._positional_actions [next_pos_arg_index ].nargs == argparse .REMAINDER :
300+ # Check if the next positional has nargs set to argparse.REMAINDER.
301+ # At this point argparse allows no more flags to be processed.
302+ if remaining_positionals and remaining_positionals [0 ].nargs == argparse .REMAINDER :
302303 skip_remaining_flags = True
303304
304305 #############################################################################################
@@ -338,12 +339,11 @@ def consume_argument(arg_state: AutoCompleter._ArgumentState) -> None:
338339 return []
339340
340341 # Otherwise check if we have a positional to complete
341- elif pos_arg_state is not None or next_pos_arg_index < len ( self . _positional_actions ) :
342+ elif pos_arg_state is not None or remaining_positionals :
342343
343344 # If we aren't current tracking a positional, then get the next positional arg to handle this token
344345 if pos_arg_state is None :
345- pos_index = next_pos_arg_index
346- action = self ._positional_actions [pos_index ]
346+ action = remaining_positionals .popleft ()
347347 pos_arg_state = AutoCompleter ._ArgumentState (action )
348348
349349 try :
0 commit comments