summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Van Brunt <kmvanbrunt@gmail.com>2018-10-10 18:08:11 -0400
committerKevin Van Brunt <kmvanbrunt@gmail.com>2018-10-10 18:08:11 -0400
commit9b43502ab70d1bd013a4cdede4805c2c0819c8c4 (patch)
treec8c12774764fa0b9f226b1f02c3614236bb03a23
parentf38e100fd77f4a136a4883d23b2f4f8b3cd934b7 (diff)
downloadcmd2-git-9b43502ab70d1bd013a4cdede4805c2c0819c8c4.tar.gz
Added code to handle -- in argparse completer
-rw-r--r--[-rwxr-xr-x]cmd2/argparse_completer.py59
-rw-r--r--cmd2/pyscript_bridge.py6
-rwxr-xr-xexamples/tab_autocompletion.py2
-rw-r--r--tests/test_acargparse.py18
-rw-r--r--tests/test_autocompletion.py48
-rw-r--r--tests/test_completion.py25
6 files changed, 101 insertions, 57 deletions
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py
index 168a555f..d98ab8c5 100755..100644
--- a/cmd2/argparse_completer.py
+++ b/cmd2/argparse_completer.py
@@ -209,8 +209,8 @@ def register_custom_actions(parser: argparse.ArgumentParser) -> None:
parser.register('action', 'append', _AppendRangeAction)
-def token_resembles_flag(token: str, parser: argparse.ArgumentParser) -> bool:
- """Determine if a token looks like a flag. Based on argparse._parse_optional()."""
+def is_potential_flag(token: str, parser: argparse.ArgumentParser) -> bool:
+ """Determine if a token looks like a potential flag. Based on argparse._parse_optional()."""
# if it's an empty string, it was meant to be a positional
if not token:
return False
@@ -340,6 +340,10 @@ class AutoCompleter(object):
# Skip any flags or flag parameter tokens
next_pos_arg_index = 0
+ # This gets set to True when flags will no longer be processed as argparse flags
+ # That can happen when -- is used or an argument with nargs=argparse.REMAINDER is used
+ skip_remaining_flags = False
+
pos_arg = AutoCompleter._ArgumentState()
pos_action = None
@@ -363,7 +367,7 @@ class AutoCompleter(object):
"""Consuming token as a flag argument"""
# we're consuming flag arguments
# if the token does not look like a new flag, then count towards flag arguments
- if not token_resembles_flag(token, self._parser) and flag_action is not None:
+ if not is_potential_flag(token, self._parser) and flag_action is not None:
flag_arg.count += 1
# does this complete a option item for the flag
@@ -432,8 +436,20 @@ class AutoCompleter(object):
for idx, token in enumerate(tokens):
is_last_token = idx >= len(tokens) - 1
+
# Only start at the start token index
if idx >= self._token_start_index:
+
+ # all args after -- are non-flags
+ if remainder['arg'] is None and token == '--':
+ flag_action = None
+ flag_arg.reset()
+ if is_last_token:
+ break
+ else:
+ skip_remaining_flags = True
+ continue
+
# If a remainder action is found, force all future tokens to go to that
if remainder['arg'] is not None:
if remainder['action'] == pos_action:
@@ -442,23 +458,25 @@ class AutoCompleter(object):
elif remainder['action'] == flag_action:
consume_flag_argument()
continue
+
current_is_positional = False
# Are we consuming flag arguments?
if not flag_arg.needed:
- # Special case when each of the following is true:
- # - We're not in the middle of consuming flag arguments
- # - The current positional argument count has hit the max count
- # - The next positional argument is a REMAINDER argument
- # Argparse will now treat all future tokens as arguments to the positional including tokens that
- # look like flags so the completer should skip any flag related processing once this happens
- skip_flag = False
- if (pos_action is not None) and pos_arg.count >= pos_arg.max and \
- next_pos_arg_index < len(self._positional_actions) and \
- self._positional_actions[next_pos_arg_index].nargs == argparse.REMAINDER:
- skip_flag = True
+
+ if not skip_remaining_flags:
+ # Special case when each of the following is true:
+ # - We're not in the middle of consuming flag arguments
+ # - The current positional argument count has hit the max count
+ # - The next positional argument is a REMAINDER argument
+ # Argparse will now treat all future tokens as arguments to the positional including tokens that
+ # look like flags so the completer should skip any flag related processing once this happens
+ if (pos_action is not None) and pos_arg.count >= pos_arg.max and \
+ next_pos_arg_index < len(self._positional_actions) and \
+ self._positional_actions[next_pos_arg_index].nargs == argparse.REMAINDER:
+ skip_remaining_flags = True
# At this point we're no longer consuming flag arguments. Is the current argument a potential flag?
- if token_resembles_flag(token, self._parser) and not skip_flag:
+ if is_potential_flag(token, self._parser) and not skip_remaining_flags:
# reset some tracking values
flag_arg.reset()
# don't reset positional tracking because flags can be interspersed anywhere between positionals
@@ -524,22 +542,25 @@ class AutoCompleter(object):
else:
consume_flag_argument()
+ if remainder['arg'] is not None:
+ skip_remaining_flags = True
+
# don't reset this if we're on the last token - this allows completion to occur on the current token
- if not is_last_token and flag_arg.min is not None:
+ elif not is_last_token and flag_arg.min is not None:
flag_arg.needed = flag_arg.count < flag_arg.min
# Here we're done parsing all of the prior arguments. We know what the next argument is.
+ completion_results = []
+
# if we don't have a flag to populate with arguments and the last token starts with
# a flag prefix then we'll complete the list of flag options
- completion_results = []
if not flag_arg.needed and len(tokens[-1]) > 0 and tokens[-1][0] in self._parser.prefix_chars and \
- remainder['arg'] is None:
+ not skip_remaining_flags:
return AutoCompleter.basic_complete(text, line, begidx, endidx,
[flag for flag in self._flags if flag not in matched_flags])
# we're not at a positional argument, see if we're in a flag argument
elif not current_is_positional:
- # current_items = []
if flag_action is not None:
consumed = consumed_arg_values[flag_action.dest]\
if flag_action.dest in consumed_arg_values else []
diff --git a/cmd2/pyscript_bridge.py b/cmd2/pyscript_bridge.py
index 11a2cbb3..3292976e 100644
--- a/cmd2/pyscript_bridge.py
+++ b/cmd2/pyscript_bridge.py
@@ -12,7 +12,7 @@ import functools
import sys
from typing import List, Callable, Optional
-from .argparse_completer import _RangeAction, token_resembles_flag
+from .argparse_completer import _RangeAction, is_potential_flag
from .utils import namedtuple_with_defaults, StdSim, quote_string_if_needed
# Python 3.4 require contextlib2 for temporarily redirecting stderr and stdout
@@ -225,7 +225,7 @@ class ArgparseFunctor:
if isinstance(value, List) or isinstance(value, tuple):
for item in value:
item = str(item).strip()
- if token_resembles_flag(item, self._parser):
+ if is_potential_flag(item, self._parser):
raise ValueError('{} appears to be a flag and should be supplied as a keyword argument '
'to the function.'.format(item))
item = quote_string_if_needed(item)
@@ -240,7 +240,7 @@ class ArgparseFunctor:
else:
value = str(value).strip()
- if token_resembles_flag(value, self._parser):
+ if is_potential_flag(value, self._parser):
raise ValueError('{} appears to be a flag and should be supplied as a keyword argument '
'to the function.'.format(value))
value = quote_string_if_needed(value)
diff --git a/examples/tab_autocompletion.py b/examples/tab_autocompletion.py
index 571b4082..dad9e90d 100755
--- a/examples/tab_autocompletion.py
+++ b/examples/tab_autocompletion.py
@@ -163,7 +163,7 @@ class TabCompleteExample(cmd2.Cmd):
# This variant demonstrates the AutoCompleter working with the orginial argparse.
# Base argparse is unable to specify narg ranges. Autocompleter will keep expecting additional arguments
- # for the -d/--duration flag until you specify a new flaw or end the list it with '--'
+ # for the -d/--duration flag until you specify a new flag or end processing of flags with '--'
suggest_parser_orig = argparse.ArgumentParser()
diff --git a/tests/test_acargparse.py b/tests/test_acargparse.py
index 617afd4f..b6abc444 100644
--- a/tests/test_acargparse.py
+++ b/tests/test_acargparse.py
@@ -5,7 +5,7 @@ Copyright 2018 Eric Lin <anselor@gmail.com>
Released under MIT license, see LICENSE file
"""
import pytest
-from cmd2.argparse_completer import ACArgumentParser, token_resembles_flag
+from cmd2.argparse_completer import ACArgumentParser, is_potential_flag
def test_acarg_narg_empty_tuple():
@@ -53,16 +53,16 @@ def test_acarg_narg_tuple_zero_to_one():
parser.add_argument('tuple', nargs=(0, 1))
-def test_token_resembles_flag():
+def test_is_potential_flag():
parser = ACArgumentParser()
# Not valid flags
- assert not token_resembles_flag('', parser)
- assert not token_resembles_flag('non-flag', parser)
- assert not token_resembles_flag('-', parser)
- assert not token_resembles_flag('--has space', parser)
- assert not token_resembles_flag('-2', parser)
+ assert not is_potential_flag('', parser)
+ assert not is_potential_flag('non-flag', parser)
+ assert not is_potential_flag('-', parser)
+ assert not is_potential_flag('--has space', parser)
+ assert not is_potential_flag('-2', parser)
# Valid flags
- assert token_resembles_flag('-flag', parser)
- assert token_resembles_flag('--flag', parser)
+ assert is_potential_flag('-flag', parser)
+ assert is_potential_flag('--flag', parser)
diff --git a/tests/test_autocompletion.py b/tests/test_autocompletion.py
index 3473ab38..34155d88 100644
--- a/tests/test_autocompletion.py
+++ b/tests/test_autocompletion.py
@@ -279,3 +279,51 @@ def test_autcomp_custom_func_list_and_dict_arg(cmd2_app):
cmd2_app.completion_matches == ['S01E02', 'S01E03', 'S02E01', 'S02E03']
+def test_argparse_remainder_completion(cmd2_app):
+ import cmd2
+ import argparse
+
+ # First test a positional with nargs=argparse.REMAINDER
+ text = '--h'
+ line = 'help command subcommand {}'.format(text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ # --h should not complete into --help because we are in the argparse.REMAINDER sections
+ assert complete_tester(text, line, begidx, endidx, cmd2_app) is None
+
+ # Now test a flag with nargs=argparse.REMAINDER
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-f', nargs=argparse.REMAINDER)
+
+ # Overwrite eof's parser for this test
+ cmd2.Cmd.do_eof.argparser = parser
+
+ text = '--h'
+ line = 'eof -f {}'.format(text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ # --h should not complete into --help because we are in the argparse.REMAINDER sections
+ assert complete_tester(text, line, begidx, endidx, cmd2_app) is None
+
+
+def test_completion_after_double_dash(cmd2_app):
+ # Test -- as the last token before an argparse.REMAINDER sections
+ text = '--'
+ line = 'help {}'.format(text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ # Since -- is the last token in a non-remainder section, then it should show flag choices
+ first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
+ assert first_match is not None and '--help' in cmd2_app.completion_matches
+
+ # Test -- to end all flag completion
+ text = '--'
+ line = 'help -- {}'.format(text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ # Since -- appeared before the -- being completed, no more flags should be completed
+ assert complete_tester(text, line, begidx, endidx, cmd2_app) is None
diff --git a/tests/test_completion.py b/tests/test_completion.py
index ed36eb01..0df06423 100644
--- a/tests/test_completion.py
+++ b/tests/test_completion.py
@@ -716,31 +716,6 @@ def test_add_opening_quote_delimited_space_in_prefix(cmd2_app):
os.path.commonprefix(cmd2_app.completion_matches) == expected_common_prefix and \
cmd2_app.display_matches == expected_display
-def test_argparse_remainder_completion(cmd2_app):
- # First test a positional with nargs=argparse.REMAINDER
- text = '--h'
- line = 'help command subcommand {}'.format(text)
- endidx = len(line)
- begidx = endidx - len(text)
-
- # --h should not complete into --help because we are in the argparse.REMAINDER sections
- assert complete_tester(text, line, begidx, endidx, cmd2_app) is None
-
- # Now test a flag with nargs=argparse.REMAINDER
- parser = argparse.ArgumentParser()
- parser.add_argument('-f', nargs=argparse.REMAINDER)
-
- # Overwrite eof's parser for this test
- cmd2.Cmd.do_eof.argparser = parser
-
- text = '--h'
- line = 'eof -f {}'.format(text)
- endidx = len(line)
- begidx = endidx - len(text)
-
- # --h should not complete into --help because we are in the argparse.REMAINDER sections
- assert complete_tester(text, line, begidx, endidx, cmd2_app) is None
-
@pytest.fixture
def sc_app():
c = SubcommandsExample()