summaryrefslogtreecommitdiff
path: root/tests/test_argparse_completer.py
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2019-07-15 22:59:54 -0400
committerGitHub <noreply@github.com>2019-07-15 22:59:54 -0400
commit9bbebbd312dbe0331510f39cd6de70f4d9dcefa8 (patch)
treeef224bb15175be33c5fff45cf3b2dcfbb6b04471 /tests/test_argparse_completer.py
parentab3a01517a18582d1bcd35d728482e73ac707b20 (diff)
parent94b424e9c41f99c6eb268c6c97f09e99a8342de8 (diff)
downloadcmd2-git-9bbebbd312dbe0331510f39cd6de70f4d9dcefa8.tar.gz
Merge branch 'master' into migrating_docs
Diffstat (limited to 'tests/test_argparse_completer.py')
-rw-r--r--tests/test_argparse_completer.py739
1 files changed, 739 insertions, 0 deletions
diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py
new file mode 100644
index 00000000..4ad4c560
--- /dev/null
+++ b/tests/test_argparse_completer.py
@@ -0,0 +1,739 @@
+# coding=utf-8
+# flake8: noqa E302
+"""
+Unit/functional testing for argparse completer in cmd2
+"""
+import argparse
+from typing import List
+
+import pytest
+
+import cmd2
+from cmd2 import with_argparser, CompletionItem
+from cmd2.utils import StdSim, basic_complete
+from .conftest import run_cmd, complete_tester
+
+# Lists used in our tests
+static_int_choices_list = [-12, -1, -2, 0, 1, 2]
+static_choices_list = ['static', 'choices', 'stop', 'here']
+choices_from_function = ['choices', 'function', 'chatty', 'smith']
+choices_from_method = ['choices', 'method', 'most', 'improved']
+
+set_value_choices = ['set', 'value', 'choices']
+one_or_more_choices = ['one', 'or', 'more', 'choices']
+optional_choices = ['a', 'few', 'optional', 'choices']
+range_choices = ['some', 'range', 'choices']
+remainder_choices = ['remainder', 'choices']
+
+positional_choices = ['the', 'positional', 'choices']
+
+completions_from_function = ['completions', 'function', 'fairly', 'complete']
+completions_from_method = ['completions', 'method', 'missed', 'spot']
+
+
+def choices_function() -> List[str]:
+ """Function that provides choices"""
+ return choices_from_function
+
+
+def completer_function(text: str, line: str, begidx: int, endidx: int) -> List[str]:
+ """Tab completion function"""
+ return basic_complete(text, line, begidx, endidx, completions_from_function)
+
+
+# noinspection PyMethodMayBeStatic,PyUnusedLocal
+class AutoCompleteTester(cmd2.Cmd):
+ """Cmd2 app that exercises AutoCompleter class"""
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ ############################################################################################################
+ # Begin code related to help and command name completion
+ ############################################################################################################
+ def _music_create(self, args: argparse.Namespace) -> None:
+ """Implements the 'music create' command"""
+ self.poutput('music create')
+
+ def _music_create_jazz(self, args: argparse.Namespace) -> None:
+ """Implements the 'music create jazz' command"""
+ self.poutput('music create jazz')
+
+ def _music_create_rock(self, args: argparse.Namespace) -> None:
+ """Implements the 'music create rock' command"""
+ self.poutput('music create rock')
+
+ # Top level parser for music command
+ music_parser = cmd2.ArgParser(description='Manage music', prog='music')
+
+ # Add sub-commands to music
+ music_subparsers = music_parser.add_subparsers()
+
+ # music -> create
+ music_create_parser = music_subparsers.add_parser('create', help='Create music')
+ music_create_parser.set_defaults(func=_music_create)
+
+ # Add sub-commands to music -> create
+ music_create_subparsers = music_create_parser.add_subparsers()
+
+ # music -> create -> jazz
+ music_create_jazz_parser = music_create_subparsers.add_parser('jazz', help='Create jazz')
+ music_create_jazz_parser.set_defaults(func=_music_create_jazz)
+
+ # music -> create -> rock
+ music_create_rock_parser = music_create_subparsers.add_parser('rock', help='Create rocks')
+ music_create_rock_parser.set_defaults(func=_music_create_rock)
+
+ @with_argparser(music_parser)
+ def do_music(self, args: argparse.Namespace) -> None:
+ """Music command"""
+ func = getattr(args, 'func', None)
+ if func is not None:
+ # Call whatever sub-command function was selected
+ func(self, args)
+ else:
+ # No sub-command was provided, so call help
+ # noinspection PyTypeChecker
+ self.do_help('music')
+
+ ############################################################################################################
+ # Begin code related to flag completion
+ ############################################################################################################
+
+ # Uses default flag prefix value (-)
+ flag_parser = cmd2.ArgParser()
+ flag_parser.add_argument('-n', '--normal_flag', help='A normal flag', action='store_true')
+ flag_parser.add_argument('-a', '--append_flag', help='Append flag', action='append')
+ flag_parser.add_argument('-o', '--append_const_flag', help='Append const flag', action='append_const', const=True)
+ flag_parser.add_argument('-c', '--count_flag', help='Count flag', action='count')
+ flag_parser.add_argument('-s', '--suppressed_flag', help=argparse.SUPPRESS, action='store_true')
+ flag_parser.add_argument('-r', '--remainder_flag', nargs=argparse.REMAINDER, help='a remainder flag')
+
+ @with_argparser(flag_parser)
+ def do_flag(self, args: argparse.Namespace) -> None:
+ pass
+
+ # Uses non-default flag prefix value (+)
+ plus_flag_parser = cmd2.ArgParser(prefix_chars='+')
+ plus_flag_parser.add_argument('+n', '++normal_flag', help='A normal flag', action='store_true')
+
+ @with_argparser(plus_flag_parser)
+ def do_plus_flag(self, args: argparse.Namespace) -> None:
+ pass
+
+ ############################################################################################################
+ # Begin code related to testing choices, choices_function, and choices_method parameters
+ ############################################################################################################
+ def choices_method(self) -> List[str]:
+ """Method that provides choices"""
+ return choices_from_method
+
+ def completion_item_method(self) -> List[CompletionItem]:
+ """Choices method that returns CompletionItems"""
+ items = []
+ for i in range(0, 10):
+ main_str = 'main_str{}'.format(i)
+ items.append(CompletionItem(main_str, desc='blah blah'))
+ return items
+
+ choices_parser = cmd2.ArgParser()
+
+ # Flag args for choices command. Include string and non-string arg types.
+ choices_parser.add_argument("-l", "--list", help="a flag populated with a choices list",
+ choices=static_choices_list)
+ choices_parser.add_argument("-f", "--function", help="a flag populated with a choices function",
+ choices_function=choices_function)
+ choices_parser.add_argument("-m", "--method", help="a flag populated with a choices method",
+ choices_method=choices_method)
+ choices_parser.add_argument('-n', "--no_header", help='this arg has a no descriptive header',
+ choices_method=completion_item_method)
+ choices_parser.add_argument('-i', '--int', type=int, help='a flag with an int type',
+ choices=static_int_choices_list)
+
+ # Positional args for choices command
+ choices_parser.add_argument("list_pos", help="a positional populated with a choices list",
+ choices=static_choices_list)
+ choices_parser.add_argument("function_pos", help="a positional populated with a choices function",
+ choices_function=choices_function)
+ choices_parser.add_argument("method_pos", help="a positional populated with a choices method",
+ choices_method=choices_method)
+
+ @with_argparser(choices_parser)
+ def do_choices(self, args: argparse.Namespace) -> None:
+ pass
+
+ ############################################################################################################
+ # Begin code related to testing completer_function and completer_method parameters
+ ############################################################################################################
+ def completer_method(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
+ """Tab completion method"""
+ return basic_complete(text, line, begidx, endidx, completions_from_method)
+
+ completer_parser = cmd2.ArgParser()
+
+ # Flag args for completer command
+ completer_parser.add_argument("-f", "--function", help="a flag using a completer function",
+ completer_function=completer_function)
+ completer_parser.add_argument("-m", "--method", help="a flag using a completer method",
+ completer_method=completer_method)
+
+ # Positional args for completer command
+ completer_parser.add_argument("function_pos", help="a positional using a completer function",
+ completer_function=completer_function)
+ completer_parser.add_argument("method_pos", help="a positional using a completer method",
+ completer_method=completer_method)
+
+ @with_argparser(completer_parser)
+ def do_completer(self, args: argparse.Namespace) -> None:
+ pass
+
+ ############################################################################################################
+ # Begin code related to nargs
+ ############################################################################################################
+ nargs_parser = cmd2.ArgParser()
+
+ # Flag args for nargs command
+ nargs_parser.add_argument("--set_value", help="a flag with a set value for nargs", nargs=2,
+ choices=set_value_choices)
+ nargs_parser.add_argument("--one_or_more", help="a flag wanting one or more args", nargs=argparse.ONE_OR_MORE,
+ choices=one_or_more_choices)
+ nargs_parser.add_argument("--optional", help="a flag with an optional value", nargs=argparse.OPTIONAL,
+ choices=optional_choices)
+ nargs_parser.add_argument("--range", help="a flag with nargs range", nargs=(1, 2),
+ choices=range_choices)
+ nargs_parser.add_argument("--remainder", help="a flag wanting remaining", nargs=argparse.REMAINDER,
+ choices=remainder_choices)
+
+ nargs_parser.add_argument("normal_pos", help="a remainder positional", nargs=2,
+ choices=positional_choices)
+ nargs_parser.add_argument("remainder_pos", help="a remainder positional", nargs=argparse.REMAINDER,
+ choices=remainder_choices)
+
+ @with_argparser(nargs_parser)
+ def do_nargs(self, args: argparse.Namespace) -> None:
+ pass
+
+ ############################################################################################################
+ # Begin code related to testing tab hints
+ ############################################################################################################
+ hint_parser = cmd2.ArgParser()
+ hint_parser.add_argument('-f', '--flag', help='a flag arg')
+ hint_parser.add_argument('-s', '--suppressed_help', help=argparse.SUPPRESS)
+ hint_parser.add_argument('-t', '--suppressed_hint', help='a flag arg', suppress_tab_hint=True)
+
+ hint_parser.add_argument('hint_pos', help='here is a hint\nwith new lines')
+ hint_parser.add_argument('no_help_pos')
+
+ @with_argparser(hint_parser)
+ def do_hint(self, args: argparse.Namespace) -> None:
+ pass
+
+
+@pytest.fixture
+def ac_app():
+ app = AutoCompleteTester()
+ app.stdout = StdSim(app.stdout)
+ return app
+
+
+@pytest.mark.parametrize('command', [
+ 'music',
+ 'music create',
+ 'music create rock',
+ 'music create jazz'
+])
+def test_help(ac_app, command):
+ out1, err1 = run_cmd(ac_app, '{} -h'.format(command))
+ out2, err2 = run_cmd(ac_app, 'help {}'.format(command))
+ assert out1 == out2
+
+
+@pytest.mark.parametrize('command, text, completions', [
+ ('', 'mu', ['music ']),
+ ('music', 'cre', ['create ']),
+ ('music create', '', ['jazz', 'rock'])
+])
+def test_complete_help(ac_app, command, text, completions):
+ line = 'help {} {}'.format(command, text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ first_match = complete_tester(text, line, begidx, endidx, ac_app)
+ if completions:
+ assert first_match is not None
+ else:
+ assert first_match is None
+
+ assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key)
+
+
+@pytest.mark.parametrize('command_and_args, text, completions', [
+ # Complete all flags (suppressed will not show)
+ ('flag', '-', ['--append_const_flag', '--append_flag', '--count_flag', '--help', '--normal_flag',
+ '--remainder_flag', '-a', '-c', '-h', '-n', '-o', '-r']),
+ ('flag', '--', ['--append_const_flag', '--append_flag', '--count_flag', '--help',
+ '--normal_flag', '--remainder_flag']),
+
+ # Complete individual flag
+ ('flag', '-n', ['-n ']),
+ ('flag', '--n', ['--normal_flag ']),
+
+ # No flags should complete until current flag has its args
+ ('flag --append_flag', '-', []),
+
+ # Complete REMAINDER flag name
+ ('flag', '-r', ['-r ']),
+ ('flag', '--r', ['--remainder_flag ']),
+
+ # No flags after a REMAINDER should complete
+ ('flag -r value', '-', []),
+ ('flag --remainder_flag value', '--', []),
+
+ # Suppressed flag should not complete
+ ('flag', '-s', []),
+ ('flag', '--s', []),
+
+ # A used flag should not show in completions
+ ('flag -n', '--', ['--append_const_flag', '--append_flag', '--count_flag', '--help', '--remainder_flag']),
+
+ # Flags with actions set to append, append_const, and count will always show even if they've been used
+ ('flag --append_const_flag -c --append_flag value', '--', ['--append_const_flag', '--append_flag', '--count_flag',
+ '--help', '--normal_flag', '--remainder_flag']),
+
+ # Non-default flag prefix character (+)
+ ('plus_flag', '+', ['++help', '++normal_flag', '+h', '+n']),
+ ('plus_flag', '++', ['++help', '++normal_flag']),
+
+ # Flag completion should not occur after '--' since that tells argparse all remaining arguments are non-flags
+ ('flag --', '--', []),
+ ('flag --help --', '--', []),
+ ('plus_flag --', '++', []),
+ ('plus_flag ++help --', '++', [])
+])
+def test_autcomp_flag_completion(ac_app, command_and_args, text, completions):
+ line = '{} {}'.format(command_and_args, text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ first_match = complete_tester(text, line, begidx, endidx, ac_app)
+ if completions:
+ assert first_match is not None
+ else:
+ assert first_match is None
+
+ assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key)
+
+
+@pytest.mark.parametrize('flag, text, completions', [
+ ('-l', '', static_choices_list),
+ ('--list', 's', ['static', 'stop']),
+ ('-f', '', choices_from_function),
+ ('--function', 'ch', ['choices', 'chatty']),
+ ('-m', '', choices_from_method),
+ ('--method', 'm', ['method', 'most']),
+ ('-i', '', [str(i) for i in static_int_choices_list]),
+ ('--int', '1', ['1 ']),
+ ('--int', '-', ['-12', '-1', '-2']),
+ ('--int', '-1', ['-12', '-1'])
+])
+def test_autocomp_flag_choices_completion(ac_app, flag, text, completions):
+ line = 'choices {} {}'.format(flag, text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ first_match = complete_tester(text, line, begidx, endidx, ac_app)
+ if completions:
+ assert first_match is not None
+ else:
+ assert first_match is None
+
+ assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key)
+
+
+@pytest.mark.parametrize('pos, text, completions', [
+ (1, '', static_choices_list),
+ (1, 's', ['static', 'stop']),
+ (2, '', choices_from_function),
+ (2, 'ch', ['choices', 'chatty']),
+ (3, '', choices_from_method),
+ (3, 'm', ['method', 'most'])
+])
+def test_autocomp_positional_choices_completion(ac_app, pos, text, completions):
+ # Generate line were preceding positionals are already filled
+ line = 'choices {} {}'.format('foo ' * (pos - 1), text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ first_match = complete_tester(text, line, begidx, endidx, ac_app)
+ if completions:
+ assert first_match is not None
+ else:
+ assert first_match is None
+
+ assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key)
+
+
+@pytest.mark.parametrize('flag, text, completions', [
+ ('-f', '', completions_from_function),
+ ('--function', 'f', ['function', 'fairly']),
+ ('-m', '', completions_from_method),
+ ('--method', 'm', ['method', 'missed'])
+])
+def test_autocomp_flag_completers(ac_app, flag, text, completions):
+ line = 'completer {} {}'.format(flag, text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ first_match = complete_tester(text, line, begidx, endidx, ac_app)
+ if completions:
+ assert first_match is not None
+ else:
+ assert first_match is None
+
+ assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key)
+
+
+@pytest.mark.parametrize('pos, text, completions', [
+ (1, '', completions_from_function),
+ (1, 'c', ['completions', 'complete']),
+ (2, '', completions_from_method),
+ (2, 'm', ['method', 'missed'])
+])
+def test_autocomp_positional_completers(ac_app, pos, text, completions):
+ # Generate line were preceding positionals are already filled
+ line = 'completer {} {}'.format('foo ' * (pos - 1), text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ first_match = complete_tester(text, line, begidx, endidx, ac_app)
+ if completions:
+ assert first_match is not None
+ else:
+ assert first_match is None
+
+ assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key)
+
+
+def test_autocomp_blank_token(ac_app):
+ """Force a blank token to make sure AutoCompleter consumes them like argparse does"""
+ from cmd2.argparse_completer import AutoCompleter
+
+ blank = ''
+
+ # Blank flag arg
+ text = ''
+ line = 'completer -m {} {}'.format(blank, text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ completer = AutoCompleter(ac_app.completer_parser, ac_app)
+ tokens = ['completer', '-f', blank, text]
+ completions = completer.complete_command(tokens, text, line, begidx, endidx)
+ assert completions == completions_from_function
+
+ # Blank positional arg
+ text = ''
+ line = 'completer {} {}'.format(blank, text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ completer = AutoCompleter(ac_app.completer_parser, ac_app)
+ tokens = ['completer', blank, text]
+ completions = completer.complete_command(tokens, text, line, begidx, endidx)
+ assert completions == completions_from_method
+
+
+@pytest.mark.parametrize('num_aliases, show_description', [
+ # The number of completion results determines if the description field of CompletionItems gets displayed
+ # in the tab completions. The count must be greater than 1 and less than ac_app.max_completion_items,
+ # which defaults to 50.
+ (1, False),
+ (5, True),
+ (100, False)
+])
+def test_completion_items(ac_app, num_aliases, show_description):
+ # Create aliases
+ for i in range(0, num_aliases):
+ run_cmd(ac_app, 'alias create fake{} help'.format(i))
+
+ assert len(ac_app.aliases) == num_aliases
+
+ text = 'fake'
+ line = 'alias list {}'.format(text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ first_match = complete_tester(text, line, begidx, endidx, ac_app)
+ assert first_match is not None
+ assert len(ac_app.completion_matches) == num_aliases
+ assert len(ac_app.display_matches) == num_aliases
+
+ # If show_description is True, the alias's value will be in the display text
+ assert ('help' in ac_app.display_matches[0]) == show_description
+
+
+@pytest.mark.parametrize('args, completions', [
+ # Flag with nargs = 2
+ ('--set_value', set_value_choices),
+ ('--set_value set', ['value', 'choices']),
+
+ # Both args are filled. At positional arg now.
+ ('--set_value set value', positional_choices),
+
+ # Using the flag again will reset the choices available
+ ('--set_value set value --set_value', set_value_choices),
+
+ # Flag with nargs = ONE_OR_MORE
+ ('--one_or_more', one_or_more_choices),
+ ('--one_or_more one', ['or', 'more', 'choices']),
+
+ # Flag with nargs = OPTIONAL
+ ('--optional', optional_choices),
+
+ # Only one arg allowed for an OPTIONAL. At positional now.
+ ('--optional optional', positional_choices),
+
+ # Flag with nargs range (1, 2)
+ ('--range', range_choices),
+ ('--range some', ['range', 'choices']),
+
+ # Already used 2 args so at positional
+ ('--range some range', positional_choices),
+
+ # Flag with nargs = REMAINDER
+ ('--remainder', remainder_choices),
+ ('--remainder remainder ', ['choices ']),
+
+ # No more flags can appear after a REMAINDER flag)
+ ('--remainder choices --set_value', ['remainder ']),
+
+ # Double dash ends the current flag
+ ('--range choice --', positional_choices),
+
+ # Double dash ends a REMAINDER flag
+ ('--remainder remainder --', positional_choices),
+
+ # No more flags after a double dash
+ ('-- --one_or_more ', positional_choices),
+
+ # Consume positional
+ ('', positional_choices),
+ ('positional', ['the', 'choices']),
+
+ # Intermixed flag and positional
+ ('positional --set_value', set_value_choices),
+ ('positional --set_value set', ['choices', 'value']),
+
+ # Intermixed flag and positional with flag finishing
+ ('positional --set_value set value', ['the', 'choices']),
+ ('positional --range choice --', ['the', 'choices']),
+
+ # REMAINDER positional
+ ('the positional', remainder_choices),
+ ('the positional remainder', ['choices ']),
+ ('the positional remainder choices', []),
+
+ # REMAINDER positional. Flags don't work in REMAINDER
+ ('the positional --set_value', remainder_choices),
+ ('the positional remainder --set_value', ['choices '])
+])
+def test_autcomp_nargs(ac_app, args, completions):
+ text = ''
+ line = 'nargs {} {}'.format(args, text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ first_match = complete_tester(text, line, begidx, endidx, ac_app)
+ if completions:
+ assert first_match is not None
+ else:
+ assert first_match is None
+
+ assert ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key)
+
+
+@pytest.mark.parametrize('command_and_args, text, is_error', [
+ # Flag is finished before moving on
+ ('hint --flag foo --', '', False),
+ ('hint --flag foo --help', '', False),
+ ('hint --flag foo', '--', False),
+
+ ('nargs --one_or_more one --', '', False),
+ ('nargs --one_or_more one or --set_value', '', False),
+ ('nargs --one_or_more one or more', '--', False),
+
+ ('nargs --set_value set value --', '', False),
+ ('nargs --set_value set value --one_or_more', '', False),
+ ('nargs --set_value set value', '--', False),
+ ('nargs --set_val set value', '--', False), # This exercises our abbreviated flag detection
+
+ ('nargs --range choices --', '', False),
+ ('nargs --range choices range --set_value', '', False),
+ ('nargs --range range', '--', False),
+
+ # Flag is not finished before moving on
+ ('hint --flag --', '', True),
+ ('hint --flag --help', '', True),
+ ('hint --flag', '--', True),
+
+ ('nargs --one_or_more --', '', True),
+ ('nargs --one_or_more --set_value', '', True),
+ ('nargs --one_or_more', '--', True),
+
+ ('nargs --set_value set --', '', True),
+ ('nargs --set_value set --one_or_more', '', True),
+ ('nargs --set_value set', '--', True),
+ ('nargs --set_val set', '--', True), # This exercises our abbreviated flag detection
+
+ ('nargs --range --', '', True),
+ ('nargs --range --set_value', '', True),
+ ('nargs --range', '--', True),
+])
+def test_unfinished_flag_error(ac_app, command_and_args, text, is_error, capsys):
+ line = '{} {}'.format(command_and_args, text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ complete_tester(text, line, begidx, endidx, ac_app)
+
+ out, err = capsys.readouterr()
+ assert is_error == all(x in out for x in ["Error:\n", "expected"])
+
+
+def test_completion_items_default_header(ac_app):
+ from cmd2.argparse_completer import DEFAULT_DESCRIPTIVE_HEADER
+
+ text = ''
+ line = 'choices -n {}'.format(text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ # This positional argument did not provide a descriptive header, so it should be DEFAULT_DESCRIPTIVE_HEADER
+ complete_tester(text, line, begidx, endidx, ac_app)
+ assert DEFAULT_DESCRIPTIVE_HEADER in ac_app.completion_header
+
+
+@pytest.mark.parametrize('command_and_args, text, has_hint', [
+ # Normal cases
+ ('hint', '', True),
+ ('hint --flag', '', True),
+ ('hint --suppressed_help', '', False),
+ ('hint --suppressed_hint', '--', False),
+
+ # Hint because flag does not have enough values to be considered finished
+ ('nargs --one_or_more', '-', True),
+
+ # This flag has reached its minimum value count and therefore a new flag could start.
+ # However the flag can still consume values and the text is not a single prefix character.
+ # Therefor a hint will be shown.
+ ('nargs --one_or_more choices', 'bad_completion', True),
+
+ # Like the previous case, but this time text is a single prefix character which will cause flag
+ # name completion to occur instead of a hint for the current flag.
+ ('nargs --one_or_more choices', '-', False),
+
+ # Hint because this is a REMAINDER flag and therefore no more flag name completions occur.
+ ('nargs --remainder', '-', True),
+
+ # No hint for the positional because text is a single prefix character which results in flag name completion
+ ('hint', '-', False),
+
+ # Hint because this is a REMAINDER positional and therefore no more flag name completions occur.
+ ('nargs the choices', '-', True),
+ ('nargs the choices remainder', '-', True),
+])
+def test_autocomp_hint(ac_app, command_and_args, text, has_hint, capsys):
+ line = '{} {}'.format(command_and_args, text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ complete_tester(text, line, begidx, endidx, ac_app)
+ out, err = capsys.readouterr()
+ assert has_hint == ("Hint:\n" in out)
+
+
+def test_autocomp_hint_multiple_lines(ac_app, capsys):
+ text = ''
+ line = 'hint {}'.format(text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ first_match = complete_tester(text, line, begidx, endidx, ac_app)
+ out, err = capsys.readouterr()
+
+ assert first_match is None
+ assert out == '''
+Hint:
+ HINT_POS here is a hint
+ with new lines
+
+'''
+
+
+def test_autocomp_hint_no_help_text(ac_app, capsys):
+ text = ''
+ line = 'hint foo {}'.format(text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ first_match = complete_tester(text, line, begidx, endidx, ac_app)
+ out, err = capsys.readouterr()
+
+ assert first_match is None
+ assert not out == '''
+Hint:
+ NO_HELP_POS
+
+'''
+
+
+def test_single_prefix_char():
+ from cmd2.argparse_completer import _single_prefix_char
+ parser = cmd2.ArgParser(prefix_chars='-+')
+
+ # Invalid
+ assert not _single_prefix_char('', parser)
+ assert not _single_prefix_char('--', parser)
+ assert not _single_prefix_char('-+', parser)
+ assert not _single_prefix_char('++has space', parser)
+ assert not _single_prefix_char('foo', parser)
+
+ # Valid
+ assert _single_prefix_char('-', parser)
+ assert _single_prefix_char('+', parser)
+
+
+def test_looks_like_flag():
+ from cmd2.argparse_completer import _looks_like_flag
+ parser = cmd2.ArgParser()
+
+ # Does not start like a flag
+ assert not _looks_like_flag('', parser)
+ assert not _looks_like_flag('non-flag', parser)
+ assert not _looks_like_flag('-', parser)
+ assert not _looks_like_flag('--has space', parser)
+ assert not _looks_like_flag('-2', parser)
+
+ # Does start like a flag
+ assert _looks_like_flag('--', parser)
+ assert _looks_like_flag('-flag', parser)
+ assert _looks_like_flag('--flag', parser)
+
+
+def test_complete_command_no_tokens(ac_app):
+ from cmd2.argparse_completer import AutoCompleter
+
+ parser = cmd2.ArgParser()
+ ac = AutoCompleter(parser, ac_app)
+
+ completions = ac.complete_command(tokens=[], text='', line='', begidx=0, endidx=0)
+ assert not completions
+
+
+def test_complete_command_help_no_tokens(ac_app):
+ from cmd2.argparse_completer import AutoCompleter
+
+ parser = cmd2.ArgParser()
+ ac = AutoCompleter(parser, ac_app)
+
+ completions = ac.complete_command_help(tokens=[], text='', line='', begidx=0, endidx=0)
+ assert not completions