diff options
-rw-r--r-- | cmd2/argparse_completer.py | 30 | ||||
-rw-r--r-- | tests/test_argparse_completer.py | 88 |
2 files changed, 73 insertions, 45 deletions
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index 4300365c..9fa502db 100644 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -60,7 +60,6 @@ How to supply completion choice lists or functions for sub-commands: import argparse import os -from argparse import SUPPRESS from typing import List, Union from . import utils @@ -470,7 +469,7 @@ class AutoCompleter(object): if flag_action.dest in consumed_arg_values else [] completion_results = self._complete_for_arg(flag_action, text, line, begidx, endidx, consumed) if not completion_results: - self._print_action_help(flag_action) + self._print_arg_hint(flag_action) elif len(completion_results) > 1: completion_results = self._format_completions(flag_action, completion_results) @@ -481,7 +480,7 @@ class AutoCompleter(object): consumed = consumed_arg_values[pos_name] if pos_name in consumed_arg_values else [] completion_results = self._complete_for_arg(pos_action, text, line, begidx, endidx, consumed) if not completion_results: - self._print_action_help(pos_action) + self._print_arg_hint(pos_action) elif len(completion_results) > 1: completion_results = self._format_completions(pos_action, completion_results) @@ -611,33 +610,34 @@ class AutoCompleter(object): return [] - def _print_action_help(self, action: argparse.Action) -> None: + def _print_arg_hint(self, arg: argparse.Action) -> None: + """Print argument hint to the terminal when tab completion results in no results""" # is parameter hinting disabled globally? if not self._tab_for_arg_help: return # is parameter hinting disabled for this parameter? - suppress_hint = getattr(action, ATTR_SUPPRESS_TAB_HINT, False) + suppress_hint = getattr(arg, ATTR_SUPPRESS_TAB_HINT, False) if suppress_hint: return - if action.option_strings: - flags = ', '.join(action.option_strings) + # Check if this is a flag + if arg.option_strings: + flags = ', '.join(arg.option_strings) param = '' - if action.nargs is None or action.nargs != 0: - param += ' ' + str(action.dest).upper() + if arg.nargs is None or arg.nargs != 0: + param += ' ' + str(arg.dest).upper() prefix = '{}{}'.format(flags, param) + + # Otherwise this is a positional else: - if action.dest != SUPPRESS: - prefix = '{}'.format(str(action.dest).upper()) - else: - prefix = '' + prefix = '{}'.format(str(arg.dest).upper()) - if action.help is None: + if not arg.help or arg.help == argparse.SUPPRESS: help_text = '' else: - help_text = action.help + help_text = arg.help # is there anything to print for this parameter? if not prefix and not help_text: diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py index 4f1ed44a..f2aa40a3 100644 --- a/tests/test_argparse_completer.py +++ b/tests/test_argparse_completer.py @@ -31,30 +31,42 @@ class AutoCompleteTester(cmd2.Cmd): def __init__(self): super().__init__() + ############################################################################################################ + # Begin code related to testing help and subcommand completion + ############################################################################################################ + basic_parser = Cmd2ArgParser(prog='basic') + basic_subparsers = basic_parser.add_subparsers() + + + ############################################################################################################ + # 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 - # Basic command with no subcommands that exercises tab completing choices from various sources - basic_parser = Cmd2ArgParser() - basic_parser.add_argument("-n", "--no_choices", help="a flag with no choices") - basic_parser.add_argument("-l", "--choices_list", help="a flag populated with a choices list", - choices=static_choices_list) - basic_parser.add_argument("-f", "--choices_function", help="a flag populated with a choices function", - choices_function=choices_function) - basic_parser.add_argument("-m", "--choices_method", help="a flag populated with a choices method", - choices_method=choices_method) - - basic_parser.add_argument("no_choice_pos", help="a positional with no choices") - basic_parser.add_argument("choices_list_pos", help="a positional populated with a choices list", - choices=static_choices_list) - basic_parser.add_argument("choices_function_pos", help="a positional populated with a choices function", - choices_function=choices_function) - basic_parser.add_argument("choices_method_pos", help="a positional populated with a choices method", - choices_method=choices_method) - - @with_argparser(basic_parser) - def do_basic(self, args: argparse.Namespace) -> None: + choices_parser = Cmd2ArgParser() + + # Flags args for choices command + choices_parser.add_argument("-n", "--no_choices", help="a flag with no choices") + choices_parser.add_argument("-l", "--choices_list", help="a flag populated with a choices list", + choices=static_choices_list) + choices_parser.add_argument("-f", "--choices_function", help="a flag populated with a choices function", + choices_function=choices_function) + choices_parser.add_argument("-m", "--choices_method", help="a flag populated with a choices method", + choices_method=choices_method) + + # Positional args for choices command + choices_parser.add_argument("no_choice_pos", help="a positional with no choices") + choices_parser.add_argument("choices_list_pos", help="a positional populated with a choices list", + choices=static_choices_list) + choices_parser.add_argument("choices_function_pos", help="a positional populated with a choices function", + choices_function=choices_function) + choices_parser.add_argument("choices_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 @@ -66,14 +78,14 @@ def ac_app(): def test_help_basic(ac_app): - out1, err1 = run_cmd(ac_app, 'basic -h') - out2, err2 = run_cmd(ac_app, 'help basic') + out1, err1 = run_cmd(ac_app, 'choices -h') + out2, err2 = run_cmd(ac_app, 'help choices') assert out1 == out2 def test_autocomp_flags(ac_app): text = '-' - line = 'basic {}'.format(text) + line = 'choices {}'.format(text) endidx = len(line) begidx = endidx - len(text) @@ -83,9 +95,9 @@ def test_autocomp_flags(ac_app): '--no_choices', '-f', '-h', '-l', '-m', '-n'] -def test_autcomp_hint(ac_app, capsys): +def test_autcomp_flag_hint(ac_app, capsys): text = '' - line = 'basic -n {}'.format(text) + line = 'choices -n {}'.format(text) endidx = len(line) begidx = endidx - len(text) @@ -96,9 +108,9 @@ def test_autcomp_hint(ac_app, capsys): assert 'a flag with no choices' in out -def test_autcomp_flag_comp(ac_app): +def test_autcomp_flag_completion(ac_app): text = '--ch' - line = 'basic {}'.format(text) + line = 'choices {}'.format(text) endidx = len(line) begidx = endidx - len(text) @@ -106,7 +118,6 @@ def test_autcomp_flag_comp(ac_app): assert first_match is not None and \ ac_app.completion_matches == ['--choices_function', '--choices_list', '--choices_method'] - @pytest.mark.parametrize('flag, completions', [ ('-l', static_choices_list), ('--choices_list', static_choices_list), @@ -115,9 +126,26 @@ def test_autcomp_flag_comp(ac_app): ('-m', choices_from_method), ('--choices_method', choices_from_method), ]) -def test_autocomp_flags_choices(ac_app, flag, completions): +def test_autocomp_flag_choices_completion(ac_app, flag, completions): + text = '' + line = 'choices {} {}'.format(flag, text) + endidx = len(line) + begidx = endidx - len(text) + + first_match = complete_tester(text, line, begidx, endidx, ac_app) + assert first_match is not None and \ + ac_app.completion_matches == sorted(completions, key=ac_app.matches_sort_key) + + +@pytest.mark.parametrize('pos, completions', [ + (2, static_choices_list), # choices_list_pos + (3, choices_from_function), # choices_function_pos + (4, choices_from_method), # choices_method_pos +]) +def test_autocomp_positional_choices_completion(ac_app, pos, completions): + # Test completions of positional arguments by generating a line were preceding positionals are already filled text = '' - line = 'basic {} {}'.format(flag, text) + line = 'choices {} {}'.format('foo ' * (pos - 1), text) endidx = len(line) begidx = endidx - len(text) |