@@ -120,7 +120,6 @@ def __init__(self, parser: argparse.ArgumentParser, cmd2_app: cmd2.Cmd, *,
120120 self ._flags = [] # all flags in this command
121121 self ._flag_to_action = {} # maps flags to the argparse action object
122122 self ._positional_actions = [] # actions for positional arguments (by position index)
123- self ._mutually_exclusive_groups = [] # Each item is a list of actions
124123 self ._subcommand_action = None # this will be set if self._parser has subcommands
125124
126125 # Start digging through the argparse structures.
@@ -140,10 +139,6 @@ def __init__(self, parser: argparse.ArgumentParser, cmd2_app: cmd2.Cmd, *,
140139 if isinstance (action , argparse ._SubParsersAction ):
141140 self ._subcommand_action = action
142141
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-
147142 def complete_command (self , tokens : List [str ], text : str , line : str , begidx : int , endidx : int ) -> List [str ]:
148143 """Complete the command using the argparse metadata and provided argument dictionary"""
149144 if not tokens :
@@ -168,12 +163,52 @@ def complete_command(self, tokens: List[str], text: str, line: str, begidx: int,
168163 # Keeps track of arguments we've seen and any tokens they consumed
169164 consumed_arg_values = dict () # dict(arg_name -> List[tokens])
170165
166+ # Completed mutually exclusive groups
167+ completed_mutex_groups = dict () # dict(argparse._MutuallyExclusiveGroup -> Action which completed group)
168+
171169 def consume_argument (arg_state : AutoCompleter ._ArgumentState ) -> None :
172170 """Consuming token as an argument"""
173171 arg_state .count += 1
174172 consumed_arg_values .setdefault (arg_state .action .dest , [])
175173 consumed_arg_values [arg_state .action .dest ].append (token )
176174
175+ def update_mutex_groups (arg_action : argparse .Action ) -> bool :
176+ """
177+ Check if an argument belongs to a mutually exclusive group and either mark that group
178+ as complete or print an error if the group has already been completed
179+ :param arg_action: the action of the argument
180+ :return: False if the group has already been completed and there is a conflict, otherwise True
181+ """
182+ # Check if this action is in a mutually exclusive group
183+ for group in self ._parser ._mutually_exclusive_groups :
184+ if arg_action in group ._group_actions :
185+
186+ # Check if the group this action belongs to has already been completed
187+ if group in completed_mutex_groups :
188+ group_action = completed_mutex_groups [group ]
189+ error = style_error ("\n Error: argument {}: not allowed with argument {}\n " .
190+ format (argparse ._get_action_name (arg_action ),
191+ argparse ._get_action_name (group_action )))
192+ self ._print_message (error )
193+ return False
194+
195+ # Mark that this action completed the group
196+ completed_mutex_groups [group ] = arg_action
197+
198+ # Don't tab complete any of the other args in the group
199+ for group_action in group ._group_actions :
200+ if group_action == arg_action :
201+ continue
202+ elif group_action in self ._flag_to_action .values ():
203+ matched_flags .extend (group_action .option_strings )
204+ elif group_action in remaining_positionals :
205+ remaining_positionals .remove (group_action )
206+
207+ # Arg can only be in one group, so we are done
208+ break
209+
210+ return True
211+
177212 #############################################################################################
178213 # Parse all but the last token
179214 #############################################################################################
@@ -227,6 +262,9 @@ def consume_argument(arg_state: AutoCompleter._ArgumentState) -> None:
227262 action = self ._flag_to_action [candidates_flags [0 ]]
228263
229264 if action is not None :
265+ if not update_mutex_groups (action ):
266+ return []
267+
230268 if isinstance (action , (argparse ._AppendAction ,
231269 argparse ._AppendConstAction ,
232270 argparse ._CountAction )):
@@ -287,6 +325,9 @@ def consume_argument(arg_state: AutoCompleter._ArgumentState) -> None:
287325
288326 # Check if we have a positional to consume this token
289327 if pos_arg_state is not None :
328+ if not update_mutex_groups (pos_arg_state .action ):
329+ return []
330+
290331 consume_argument (pos_arg_state )
291332
292333 # No more flags are allowed if this is a REMAINDER argument
0 commit comments