Skip to content

Commit 9b1bfab

Browse files
committed
Added ability for argcompleter to determine difference between flag and negative number
1 parent ad3ca32 commit 9b1bfab

File tree

2 files changed

+37
-15
lines changed

2 files changed

+37
-15
lines changed

cmd2/argparse_completer.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,33 @@ def register_custom_actions(parser: argparse.ArgumentParser) -> None:
209209
parser.register('action', 'append', _AppendRangeAction)
210210

211211

212+
def token_resembles_flag(token: str, parser: argparse.ArgumentParser):
213+
"""Determine if a token looks like a flag. Based on argparse._parse_optional()."""
214+
# if it's an empty string, it was meant to be a positional
215+
if not token:
216+
return False
217+
218+
# if it doesn't start with a prefix, it was meant to be positional
219+
if not token[0] in parser.prefix_chars:
220+
return False
221+
222+
# if it's just a single character, it was meant to be positional
223+
if len(token) == 1:
224+
return False
225+
226+
# if it looks like a negative number, it was meant to be positional
227+
# unless there are negative-number-like options
228+
if parser._negative_number_matcher.match(token):
229+
if not parser._has_negative_number_optionals:
230+
return False
231+
232+
# if it contains a space, it was meant to be a positional
233+
if ' ' in token:
234+
return False
235+
236+
# Looks like a flag
237+
return True
238+
212239
class AutoCompleter(object):
213240
"""Automatically command line tab completion based on argparse parameters"""
214241

@@ -334,9 +361,8 @@ def complete_command(self, tokens: List[str], text: str, line: str, begidx: int,
334361
def consume_flag_argument() -> None:
335362
"""Consuming token as a flag argument"""
336363
# we're consuming flag arguments
337-
# if this is not empty and is not another potential flag, count towards flag arguments
338-
# if the token is a single character, it doesn't matter whether 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:
364+
# if the token does not look like a new flag, then count towards flag arguments
365+
if not token_resembles_flag(token, self._parser) and flag_action is not None:
340366
flag_arg.count += 1
341367

342368
# does this complete a option item for the flag
@@ -431,12 +457,7 @@ def process_action_nargs(action: argparse.Action, arg_state: AutoCompleter._Argu
431457
skip_flag = True
432458

433459
# At this point we're no longer consuming flag arguments. Is the current argument a potential flag?
434-
# If the argument is the start of a flag and this is the last token, we proceed forward to try
435-
# and match against our known flags.
436-
# If this argument is not the last token and the argument is exactly a flag prefix, then this
437-
# token should be consumed as an argument to a prior flag or positional argument.
438-
if len(token) > 0 and token[0] in self._parser.prefix_chars and not skip_flag and \
439-
(is_last_token or token not in self._parser.prefix_chars):
460+
if token_resembles_flag(token, self._parser) and not skip_flag:
440461
# reset some tracking values
441462
flag_arg.reset()
442463
# don't reset positional tracking because flags can be interspersed anywhere between positionals

cmd2/pyscript_bridge.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import sys
1313
from typing import List, Callable, Optional
1414

15-
from .argparse_completer import _RangeAction
15+
from .argparse_completer import _RangeAction, token_resembles_flag
1616
from .utils import namedtuple_with_defaults, StdSim, quote_string_if_needed
1717

1818
# Python 3.4 require contextlib2 for temporarily redirecting stderr and stdout
@@ -225,9 +225,9 @@ def process_argument(action, value):
225225
if isinstance(value, List) or isinstance(value, tuple):
226226
for item in value:
227227
item = str(item).strip()
228-
if self._parser._parse_optional(item) is not None:
229-
raise ValueError('Value provided for {} ({}) appears to be an optional'.
230-
format(action.dest, item))
228+
if token_resembles_flag(item, self._parser):
229+
raise ValueError('{} appears to be a flag and should be supplied as a keyword argument '
230+
'to the function.'.format(item))
231231
item = quote_string_if_needed(item)
232232
cmd_str[0] += '{} '.format(item)
233233

@@ -240,8 +240,9 @@ def process_argument(action, value):
240240

241241
else:
242242
value = str(value).strip()
243-
if self._parser._parse_optional(value) is not None:
244-
raise ValueError('Value provided for {} ({}) appears to be an optional'.format(action.dest, value))
243+
if token_resembles_flag(value, self._parser):
244+
raise ValueError('{} appears to be a flag and should be supplied as a keyword argument '
245+
'to the function.'.format(value))
245246
value = quote_string_if_needed(value)
246247
cmd_str[0] += '{} '.format(value)
247248

0 commit comments

Comments
 (0)