Skip to content

Commit 29b5252

Browse files
committed
Initial steps to support mutually exclusive groups in AutoCompleter
1 parent b3408bc commit 29b5252

File tree

1 file changed

+22
-22
lines changed

1 file changed

+22
-22
lines changed

cmd2/argparse_completer.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import inspect
1111
import numbers
1212
import shutil
13+
from collections import deque
1314
from typing import Dict, List, Optional, Union
1415

1516
from . 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

Comments
 (0)