Skip to content

Commit b0d4437

Browse files
committed
Fixed a few edge cases:
- Once the argument list can match a positional and that positional is tagged with nargs=argparse.REMAINDER it will consume all tokens including flag tokens. AutoCompleter now correctly detects this case will no longer attempt to complete flag tokens - A single-character token that is a flag prefix doesn't count as a flag and is parsed as a value. AutoCompleter now correctly detects this case.
1 parent 96bcfe0 commit b0d4437

File tree

2 files changed

+19
-6
lines changed

2 files changed

+19
-6
lines changed

cmd2/argparse_completer.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ def complete_command(self, tokens: List[str], text: str, line: str, begidx: int,
318318
flag_arg = AutoCompleter._ArgumentState()
319319
flag_action = None
320320

321+
# dict is used because object wrapper is necessary to allow inner functions to modify outer variables
321322
remainder = {'arg': None, 'action': None}
322323

323324
matched_flags = []
@@ -334,7 +335,8 @@ def consume_flag_argument() -> None:
334335
"""Consuming token as a flag argument"""
335336
# we're consuming flag arguments
336337
# if this is not empty and is not another potential flag, count towards flag arguments
337-
if token and token[0] not in self._parser.prefix_chars and flag_action is not None:
338+
# if the token is a single character length, it doesn't matter that it matches a flag prefix
339+
if token and (len(token) == 1 or token[0] not in self._parser.prefix_chars) and flag_action is not None:
338340
flag_arg.count += 1
339341

340342
# does this complete a option item for the flag
@@ -377,7 +379,6 @@ def process_action_nargs(action: argparse.Action, arg_state: AutoCompleter._Argu
377379
arg_state.max = float('inf')
378380
arg_state.variable = True
379381
if action.nargs == argparse.REMAINDER:
380-
print("Setting remainder")
381382
remainder['action'] = action
382383
remainder['arg'] = arg_state
383384
elif action.nargs == '?':
@@ -407,25 +408,35 @@ def process_action_nargs(action: argparse.Action, arg_state: AutoCompleter._Argu
407408
is_last_token = idx >= len(tokens) - 1
408409
# Only start at the start token index
409410
if idx >= self._token_start_index:
411+
# If a remainder action is found, force all future tokens to go to that
410412
if remainder['arg'] is not None:
411-
print("In Remainder mode")
412413
if remainder['action'] == pos_action:
413414
consume_positional_argument()
414415
continue
415416
elif remainder['action'] == flag_action:
416417
consume_flag_argument()
417418
continue
418-
else:
419-
print("!!")
420419
current_is_positional = False
421420
# Are we consuming flag arguments?
422421
if not flag_arg.needed:
422+
# Special case when each of the following is true:
423+
# - We're not in the middle of consuming flag arguments
424+
# - The current positional argument count has hit the max count
425+
# - The next positional argument is a REMAINDER argument
426+
# Argparse will now treat all future tokens as arguments to the positional including tokens that look like flags
427+
# so the completer should skip any flag related processing once this happens
428+
skip_flag = False
429+
if (pos_action is not None) and pos_arg.count >= pos_arg.max and next_pos_arg_index < len(self._positional_actions) and \
430+
self._positional_actions[next_pos_arg_index].nargs == argparse.REMAINDER:
431+
skip_flag = True
432+
433+
423434
# At this point we're no longer consuming flag arguments. Is the current argument a potential flag?
424435
# If the argument is the start of a flag and this is the last token, we proceed forward to try
425436
# and match against our known flags.
426437
# If this argument is not the last token and the argument is exactly a flag prefix, then this
427438
# token should be consumed as an argument to a prior flag or positional argument.
428-
if len(token) > 0 and token[0] in self._parser.prefix_chars and\
439+
if len(token) > 0 and token[0] in self._parser.prefix_chars and not skip_flag and\
429440
(is_last_token or (not is_last_token and token not in self._parser.prefix_chars)):
430441
# reset some tracking values
431442
flag_arg.reset()

examples/tab_autocompletion.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,8 @@ def _do_media_movies(self, args) -> None:
298298
.format(movie['title'], movie['rating'], movie_id,
299299
', '.join(movie['director']),
300300
'\n '.join(movie['actor'])))
301+
elif args.command == 'add':
302+
print('Adding Movie\n----------------\nTitle: {}\nRating: {}\nDirectors: {}\nActors: {}\n\n'.format(args.title, args.rating, ', '.join(args.director), ', '.join(args.actor)))
301303

302304
def _do_media_shows(self, args) -> None:
303305
if not args.command:

0 commit comments

Comments
 (0)