diff options
-rwxr-xr-x | cmd2/argparse_completer.py (renamed from cmd2/AutoCompleter.py) | 229 | ||||
-rw-r--r-- | cmd2/rl_utils.py | 4 | ||||
-rwxr-xr-x | examples/tab_autocompletion.py | 24 | ||||
-rw-r--r-- | tests/test_acargparse.py | 11 | ||||
-rw-r--r-- | tests/test_autocompletion.py | 13 | ||||
-rw-r--r-- | tests/test_completion.py | 23 |
6 files changed, 156 insertions, 148 deletions
diff --git a/cmd2/AutoCompleter.py b/cmd2/argparse_completer.py index a8d25895..03cc6483 100755 --- a/cmd2/AutoCompleter.py +++ b/cmd2/argparse_completer.py @@ -1,14 +1,71 @@ # coding=utf-8 +""" +AutoCompleter interprets the argparse.ArgumentParser internals to automatically +generate the completion options for each argument. + +How to supply completion options for each argument: + argparse Choices + - pass a list of values to the choices parameter of an argparse argument. + ex: parser.add_argument('-o', '--options', dest='options', choices=['An Option', 'SomeOtherOption']) + + arg_choices dictionary lookup + arg_choices is a dict() mapping from argument name to one of 3 possible values: + ex: + parser = argparse.ArgumentParser() + parser.add_argument('-o', '--options', dest='options') + choices = {} + mycompleter = AutoCompleter(parser, completer, 1, choices) + + - static list - provide a static list for each argument name + ex: + choices['options'] = ['An Option', 'SomeOtherOption'] + + - choices function - provide a function that returns a list for each argument name + ex: + def generate_choices(): + return ['An Option', 'SomeOtherOption'] + choices['options'] = generate_choices + + - custom completer function - provide a completer function that will return the list + of completion arguments + ex 1: + def my_completer(text: str, line: str, begidx: int, endidx:int): + my_choices = [...] + return my_choices + choices['options'] = (my_completer) + ex 2: + def my_completer(text: str, line: str, begidx: int, endidx:int, extra_param: str, another: int): + my_choices = [...] + return my_choices + completer_params = {'extra_param': 'my extra', 'another': 5} + choices['options'] = (my_completer, completer_params) + +How to supply completion choice lists or functions for sub-commands: + subcmd_args_lookup is used to supply a unique pair of arg_choices and subcmd_args_lookup + for each sub-command in an argparser subparser group. + This requires your subparser group to be named with the dest parameter + ex: + parser = ArgumentParser() + subparsers = parser.add_subparsers(title='Actions', dest='action') + + subcmd_args_lookup maps a named subparser group to a subcommand group dictionary + The subcommand group dictionary maps subcommand names to tuple(arg_choices, subcmd_args_lookup) + + For more details of this more complex approach see tab_autocompletion.py in the examples +""" + import argparse -import re as _re +from colorama import Fore import sys -from argparse import OPTIONAL, ZERO_OR_MORE, ONE_OR_MORE, REMAINDER, PARSER, ArgumentError, _ +from typing import List, Dict, Tuple, Callable, Union + + +# imports copied from argparse to support our customized argparse functions +from argparse import ZERO_OR_MORE, ONE_OR_MORE, ArgumentError, _ +import re as _re + + from .rl_utils import rl_force_redisplay -try: - from typing import List, Dict, Tuple, Callable, Union -except: - pass -from colorama import Fore class _RangeAction(object): @@ -128,57 +185,7 @@ class AutoCompleter(object): subcmd_args_lookup: dict = None, tab_for_arg_help: bool = True): """ - AutoCompleter interprets the argparse.ArgumentParser internals to automatically - generate the completion options for each argument. - - How to supply completion options for each argument: - argparse Choices - - pass a list of values to the choices parameter of an argparse argument. - ex: parser.add_argument('-o', '--options', dest='options', choices=['An Option', 'SomeOtherOption']) - - arg_choices dictionary lookup - arg_choices is a dict() mapping from argument name to one of 3 possible values: - ex: - parser = argparse.ArgumentParser() - parser.add_argument('-o', '--options', dest='options') - choices = {} - mycompleter = AutoCompleter(parser, completer, 1, choices) - - - static list - provide a static list for each argument name - ex: - choices['options'] = ['An Option', 'SomeOtherOption'] - - - choices function - provide a function that returns a list for each argument name - ex: - def generate_choices(): - return ['An Option', 'SomeOtherOption'] - choices['options'] = generate_choices - - - custom completer function - provide a completer function that will return the list - of completion arguments - ex 1: - def my_completer(text: str, line: str, begidx: int, endidx:int): - my_choices = [...] - return my_choices - choices['options'] = (my_completer) - ex 2: - def my_completer(text: str, line: str, begidx: int, endidx:int, extra_param: str, another: int): - my_choices = [...] - return my_choices - completer_params = {'extra_param': 'my extra', 'another': 5} - choices['options'] = (my_completer, completer_params) - - How to supply completion choice lists or functions for sub-commands: - subcmd_args_lookup is used to supply a unique pair of arg_choices and subcmd_args_lookup - for each sub-command in an argparser subparser group. - This requires your subparser group to be named with the dest parameter - ex: - parser = ArgumentParser() - subparsers = parser.add_subparsers(title='Actions', dest='action') - - subcmd_args_lookup maps a named subparser group to a subcommand group dictionary - The subcommand group dictionary maps subcommand names to tuple(arg_choices, subcmd_args_lookup) - + Create an AutoCompleter :param parser: ArgumentParser instance :param token_start_index: index of the token to start parsing at @@ -200,8 +207,9 @@ class AutoCompleter(object): self._flags_without_args = [] # all flags that don't take arguments self._flag_to_action = {} # maps flags to the argparse action object self._positional_actions = [] # argument names for positional arguments (by position index) - self._positional_completers = {} # maps action name to sub-command autocompleter: - # action_name -> dict(sub_command -> completer) + # maps action name to sub-command autocompleter: + # action_name -> dict(sub_command -> completer) + self._positional_completers = {} # Start digging through the argparse structures. # _actions is the top level container of parameter definitions @@ -225,10 +233,10 @@ class AutoCompleter(object): sub_completers = {} sub_commands = [] args_for_action = subcmd_args_lookup[action.dest]\ - if action.dest in subcmd_args_lookup.keys() else {} + if action.dest in subcmd_args_lookup else {} for subcmd in action.choices: (subcmd_args, subcmd_lookup) = args_for_action[subcmd] if \ - subcmd in args_for_action.keys() else \ + subcmd in args_for_action else \ (arg_choices, subcmd_args_lookup) if forward_arg_choices else ({}, {}) subcmd_start = token_start_index + len(self._positional_actions) sub_completers[subcmd] = AutoCompleter(action.choices[subcmd], subcmd_start, @@ -258,7 +266,7 @@ class AutoCompleter(object): """Consuming token as a flag argument""" # we're consuming flag arguments # if this is not empty and is not another potential flag, count towards flag arguments - if len(token) > 0 and not token[0] in self._parser.prefix_chars and flag_action is not None: + if token and token[0] not in self._parser.prefix_chars and flag_action is not None: flag_arg.count += 1 # does this complete a option item for the flag @@ -298,10 +306,10 @@ class AutoCompleter(object): flag_action = None # does the token fully match a known flag? - if token in self._flag_to_action.keys(): + if token in self._flag_to_action: flag_action = self._flag_to_action[token] elif hasattr(self._parser, 'allow_abbrev') and self._parser.allow_abbrev: - candidates_flags = [flag for flag in self._flag_to_action.keys() if flag.startswith(token)] + candidates_flags = [flag for flag in self._flag_to_action if flag.startswith(token)] if len(candidates_flags) == 1: flag_action = self._flag_to_action[candidates_flags[0]] @@ -338,9 +346,9 @@ class AutoCompleter(object): if pos_index < len(self._positional_actions): action = self._positional_actions[pos_index] pos_name = action.dest - if pos_name in self._positional_completers.keys(): + if pos_name in self._positional_completers: sub_completers = self._positional_completers[pos_name] - if token in sub_completers.keys(): + if token in sub_completers: return sub_completers[token].complete_command(tokens, text, line, begidx, endidx) pos_action = action @@ -372,7 +380,7 @@ class AutoCompleter(object): # current_items = [] if flag_action is not None: consumed = consumed_arg_values[flag_action.dest]\ - if flag_action.dest in consumed_arg_values.keys() else [] + if flag_action.dest in consumed_arg_values else [] # current_items.extend(self._resolve_choices_for_arg(flag_action, consumed)) completion_results = self._complete_for_arg(flag_action, text, line, begidx, endidx, consumed) if not completion_results: @@ -382,7 +390,7 @@ class AutoCompleter(object): else: if pos_action is not None: pos_name = pos_action.dest - consumed = consumed_arg_values[pos_name] if pos_name in consumed_arg_values.keys() else [] + 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) @@ -420,8 +428,8 @@ class AutoCompleter(object): line: str, begidx: int, endidx: int, - used_values=list()) -> List[str]: - if action.dest in self._arg_choices.keys(): + used_values=()) -> List[str]: + if action.dest in self._arg_choices: arg_choices = self._arg_choices[action.dest] if isinstance(arg_choices, tuple) and len(arg_choices) > 0 and callable(arg_choices[0]): @@ -447,8 +455,8 @@ class AutoCompleter(object): return [] - def _resolve_choices_for_arg(self, action: argparse.Action, used_values=list()) -> List[str]: - if action.dest in self._arg_choices.keys(): + def _resolve_choices_for_arg(self, action: argparse.Action, used_values=()) -> List[str]: + if action.dest in self._arg_choices: args = self._arg_choices[action.dest] if callable(args): args = args() @@ -508,6 +516,14 @@ class AutoCompleter(object): return [cur_match for cur_match in match_against if cur_match.startswith(text)] +############################################################################### +# Unless otherwise noted, everything below this point are copied from Python's +# argparse implementation with minor tweaks to adjust output. +# Changes are noted if it's buried in a block of copied code. Otherwise the +# function will check for a special case and fall back to the parent function +############################################################################### + + class ACHelpFormatter(argparse.HelpFormatter): """Custom help formatter to configure ordering of help text""" @@ -529,8 +545,9 @@ class ACHelpFormatter(argparse.HelpFormatter): # split optionals from positionals optionals = [] - required_options = [] positionals = [] + # Begin cmd2 customization (separates required and optional, applies to all changes in this function) + required_options = [] for action in actions: if action.option_strings: if action.required: @@ -539,6 +556,7 @@ class ACHelpFormatter(argparse.HelpFormatter): optionals.append(action) else: positionals.append(action) + # End cmd2 customization # build full usage string format = self._format_actions_usage @@ -549,6 +567,8 @@ class ACHelpFormatter(argparse.HelpFormatter): text_width = self._width - self._current_indent if len(prefix) + len(usage) > text_width: + # Begin cmd2 customization + # break usage into wrappable parts part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' opt_usage = format(optionals, groups) @@ -561,6 +581,8 @@ class ACHelpFormatter(argparse.HelpFormatter): assert ' '.join(pos_parts) == pos_usage assert ' '.join(req_parts) == req_usage + # End cmd2 customization + # helper for wrapping lines def get_lines(parts, indent, prefix=None): lines = [] @@ -585,6 +607,7 @@ class ACHelpFormatter(argparse.HelpFormatter): # if prog is short, follow it with optionals or positionals if len(prefix) + len(prog) <= 0.75 * text_width: indent = ' ' * (len(prefix) + len(prog) + 1) + # Begin cmd2 customization if opt_parts: lines = get_lines([prog] + pos_parts, indent, prefix) lines.extend(get_lines(req_parts, indent)) @@ -594,10 +617,12 @@ class ACHelpFormatter(argparse.HelpFormatter): lines.extend(get_lines(req_parts, indent)) else: lines = [prog] + # End cmd2 customization # if prog is long, put it on its own line else: indent = ' ' * len(prefix) + # Begin cmd2 customization parts = pos_parts + req_parts + opt_parts lines = get_lines(parts, indent) if len(lines) > 1: @@ -605,6 +630,7 @@ class ACHelpFormatter(argparse.HelpFormatter): lines.extend(get_lines(pos_parts, indent)) lines.extend(get_lines(req_parts, indent)) lines.extend(get_lines(opt_parts, indent)) + # End cmd2 customization lines = [prog] + lines # join lines into usage @@ -628,46 +654,24 @@ class ACHelpFormatter(argparse.HelpFormatter): parts.extend(action.option_strings) return ', '.join(parts) + # Begin cmd2 customization (less verbose) # if the Optional takes a value, format is: - # -s ARGS, --long ARGS + # -s, --long ARGS else: default = self._get_default_metavar_for_optional(action) args_string = self._format_args(action, default) - # for option_string in action.option_strings: - # parts.append('%s %s' % (option_string, args_string)) - return ', '.join(action.option_strings) + ' ' + args_string - - def _format_args(self, action, default_metavar): - get_metavar = self._metavar_formatter(action, default_metavar) - if isinstance(action, _RangeAction) and \ - action.nargs_min is not None and action.nargs_max is not None: - result = '{}{{{}..{}}}'.format('%s' % get_metavar(1), action.nargs_min, action.nargs_max) - - elif action.nargs is None: - result = '%s' % get_metavar(1) - elif action.nargs == OPTIONAL: - result = '[%s]' % get_metavar(1) - elif action.nargs == ZERO_OR_MORE: - result = '[%s [...]]' % get_metavar(1) - elif action.nargs == ONE_OR_MORE: - result = '%s [...]' % get_metavar(1) - elif action.nargs == REMAINDER: - result = '...' - elif action.nargs == PARSER: - result = '%s ...' % get_metavar(1) - else: - formats = ['%s' for _ in range(action.nargs)] - result = ' '.join(formats) % get_metavar(action.nargs) - return result + # End cmd2 customization def _metavar_formatter(self, action, default_metavar): if action.metavar is not None: result = action.metavar elif action.choices is not None: choice_strs = [str(choice) for choice in action.choices] + # Begin cmd2 customization (added space after comma) result = '{%s}' % ', '.join(choice_strs) + # End cmd2 customization else: result = default_metavar @@ -678,6 +682,21 @@ class ACHelpFormatter(argparse.HelpFormatter): return (result, ) * tuple_size return format + def _format_args(self, action, default_metavar): + get_metavar = self._metavar_formatter(action, default_metavar) + # Begin cmd2 customization (less verbose) + if isinstance(action, _RangeAction) and \ + action.nargs_min is not None and action.nargs_max is not None: + result = '{}{{{}..{}}}'.format('%s' % get_metavar(1), action.nargs_min, action.nargs_max) + elif action.nargs == ZERO_OR_MORE: + result = '[%s [...]]' % get_metavar(1) + elif action.nargs == ONE_OR_MORE: + result = '%s [...]' % get_metavar(1) + # End cmd2 customization + else: + result = super()._format_args(action, default_metavar) + return result + def _split_lines(self, text, width): return text.splitlines() @@ -694,15 +713,17 @@ class ACArgumentParser(argparse.ArgumentParser): self._custom_error_message = '' + # Begin cmd2 customization def set_custom_message(self, custom_message=''): """ Allows an error message override to the error() function, useful when forcing a re-parse of arguments with newly required parameters """ self._custom_error_message = custom_message + # End cmd2 customization def error(self, message): - """Custom error override.""" + """Custom error override. Allows application to control the error being displayed by argparse""" if len(self._custom_error_message) > 0: message = self._custom_error_message self._custom_error_message = '' @@ -733,6 +754,8 @@ class ACArgumentParser(argparse.ArgumentParser): # description formatter.add_text(self.description) + # Begin cmd2 customization (separate required and optional arguments) + # positionals, optionals and user-defined groups for action_group in self._action_groups: if action_group.title == 'optional arguments': @@ -762,6 +785,8 @@ class ACArgumentParser(argparse.ArgumentParser): formatter.add_arguments(action_group._group_actions) formatter.end_section() + # End cmd2 customization + # epilog formatter.add_text(self.epilog) diff --git a/cmd2/rl_utils.py b/cmd2/rl_utils.py index 1dc83d15..c00a5784 100644 --- a/cmd2/rl_utils.py +++ b/cmd2/rl_utils.py @@ -22,13 +22,15 @@ except ImportError: pass -# Check what implementation of readline we are using class RlType(Enum): + """Readline library types we recognize""" GNU = 1 PYREADLINE = 2 NONE = 3 +# Check what implementation of readline we are using + rl_type = RlType.NONE # The order of this check matters since importing pyreadline will also show readline in the modules list diff --git a/examples/tab_autocompletion.py b/examples/tab_autocompletion.py index 103706ac..448268d0 100755 --- a/examples/tab_autocompletion.py +++ b/examples/tab_autocompletion.py @@ -7,7 +7,7 @@ import itertools from typing import List import cmd2 -from cmd2 import with_argparser, with_category, AutoCompleter +from cmd2 import with_argparser, with_category, argparse_completer class TabCompleteExample(cmd2.Cmd): @@ -91,7 +91,7 @@ class TabCompleteExample(cmd2.Cmd): # - The help output for arguments with multiple flags or with append=True is more concise # - ACArgumentParser adds the ability to specify ranges of argument counts in 'nargs' - suggest_parser = AutoCompleter.ACArgumentParser() + suggest_parser = argparse_completer.ACArgumentParser() suggest_parser.add_argument('-t', '--type', choices=['movie', 'show'], required=True) suggest_parser.add_argument('-d', '--duration', nargs=(1, 2), action='append', @@ -113,7 +113,7 @@ class TabCompleteExample(cmd2.Cmd): def complete_suggest(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: """ Adds tab completion to media""" - completer = AutoCompleter.AutoCompleter(TabCompleteExample.suggest_parser, 1) + completer = argparse_completer.AutoCompleter(TabCompleteExample.suggest_parser, 1) tokens, _ = self.tokens_for_completion(line, begidx, endidx) results = completer.complete_command(tokens, text, line, begidx, endidx) @@ -125,7 +125,7 @@ class TabCompleteExample(cmd2.Cmd): suggest_parser_hybrid = argparse.ArgumentParser() # This registers the custom narg range handling - AutoCompleter.register_custom_actions(suggest_parser_hybrid) + argparse_completer.register_custom_actions(suggest_parser_hybrid) suggest_parser_hybrid.add_argument('-t', '--type', choices=['movie', 'show'], required=True) suggest_parser_hybrid.add_argument('-d', '--duration', nargs=(1, 2), action='append', @@ -141,7 +141,7 @@ class TabCompleteExample(cmd2.Cmd): def complete_hybrid_suggest(self, text, line, begidx, endidx): """ Adds tab completion to media""" - completer = AutoCompleter.AutoCompleter(TabCompleteExample.suggest_parser_hybrid) + completer = argparse_completer.AutoCompleter(TabCompleteExample.suggest_parser_hybrid) tokens, _ = self.tokens_for_completion(line, begidx, endidx) results = completer.complete_command(tokens, text, line, begidx, endidx) @@ -168,7 +168,7 @@ class TabCompleteExample(cmd2.Cmd): def complete_orig_suggest(self, text, line, begidx, endidx) -> List[str]: """ Adds tab completion to media""" - completer = AutoCompleter.AutoCompleter(TabCompleteExample.suggest_parser_orig) + completer = argparse_completer.AutoCompleter(TabCompleteExample.suggest_parser_orig) tokens, _ = self.tokens_for_completion(line, begidx, endidx) results = completer.complete_command(tokens, text, line, begidx, endidx) @@ -211,7 +211,7 @@ class TabCompleteExample(cmd2.Cmd): print() - media_parser = AutoCompleter.ACArgumentParser(prog='media') + media_parser = argparse_completer.ACArgumentParser(prog='media') media_types_subparsers = media_parser.add_subparsers(title='Media Types', dest='type') @@ -264,7 +264,7 @@ class TabCompleteExample(cmd2.Cmd): choices = {'actor': self.query_actors, # function 'director': TabCompleteExample.static_list_directors # static list } - completer = AutoCompleter.AutoCompleter(TabCompleteExample.media_parser, arg_choices=choices) + completer = argparse_completer.AutoCompleter(TabCompleteExample.media_parser, arg_choices=choices) tokens, _ = self.tokens_for_completion(line, begidx, endidx) results = completer.complete_command(tokens, text, line, begidx, endidx) @@ -293,11 +293,11 @@ class TabCompleteExample(cmd2.Cmd): def _query_movie_user_library(self): return TabCompleteExample.USER_MOVIE_LIBRARY - def _filter_library(self, text, line, begidx, endidx, full, exclude=[]): + def _filter_library(self, text, line, begidx, endidx, full, exclude=()): candidates = list(set(full).difference(set(exclude))) return [entry for entry in candidates if entry.startswith(text)] - library_parser = AutoCompleter.ACArgumentParser(prog='library') + library_parser = argparse_completer.ACArgumentParser(prog='library') library_subcommands = library_parser.add_subparsers(title='Media Types', dest='type') @@ -410,8 +410,8 @@ class TabCompleteExample(cmd2.Cmd): # under the type sub-parser group, there are 2 sub-parsers: 'movie', 'show' library_subcommand_groups = {'type': library_type_params} - completer = AutoCompleter.AutoCompleter(TabCompleteExample.library_parser, - subcmd_args_lookup=library_subcommand_groups) + completer = argparse_completer.AutoCompleter(TabCompleteExample.library_parser, + subcmd_args_lookup=library_subcommand_groups) tokens, _ = self.tokens_for_completion(line, begidx, endidx) results = completer.complete_command(tokens, text, line, begidx, endidx) diff --git a/tests/test_acargparse.py b/tests/test_acargparse.py index 6f40bd42..be3e8b97 100644 --- a/tests/test_acargparse.py +++ b/tests/test_acargparse.py @@ -1,14 +1,11 @@ """ -Unit/functional testing for readline tab-completion functions in the cmd2.py module. +Unit/functional testing for argparse customizations in cmd2 -These are primarily tests related to readline completer functions which handle tab-completion of cmd2/cmd commands, -file system paths, and shell commands. - -Copyright 2017 Todd Leonhardt <todd.leonhardt@gmail.com> +Copyright 2018 Eric Lin <anselor@gmail.com> Released under MIT license, see LICENSE file """ import pytest -from cmd2.AutoCompleter import ACArgumentParser +from cmd2.argparse_completer import ACArgumentParser def test_acarg_narg_empty_tuple(): @@ -54,5 +51,3 @@ def test_acarg_narg_tuple_zero_base(): def test_acarg_narg_tuple_zero_to_one(): parser = ACArgumentParser(prog='test') parser.add_argument('tuple', nargs=(0, 1)) - - diff --git a/tests/test_autocompletion.py b/tests/test_autocompletion.py index 5f28086a..e68bc104 100644 --- a/tests/test_autocompletion.py +++ b/tests/test_autocompletion.py @@ -1,10 +1,7 @@ """ -Unit/functional testing for readline tab-completion functions in the cmd2.py module. +Unit/functional testing for argparse completer in cmd2 -These are primarily tests related to readline completer functions which handle tab-completion of cmd2/cmd commands, -file system paths, and shell commands. - -Copyright 2017 Todd Leonhardt <todd.leonhardt@gmail.com> +Copyright 2018 Eric Lin <anselor@gmail.com> Released under MIT license, see LICENSE file """ import pytest @@ -257,9 +254,3 @@ def test_autcomp_custom_func_list_and_dict_arg(cmd2_app): first_match = complete_tester(text, line, begidx, endidx, cmd2_app) assert first_match is not None and \ cmd2_app.completion_matches == ['S01E02', 'S01E03', 'S02E01', 'S02E03'] - - - - - - diff --git a/tests/test_completion.py b/tests/test_completion.py index 2902f55e..a01d1166 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -863,6 +863,7 @@ def test_subcommand_tab_completion(sc_app): # It is at end of line, so extra space is present assert first_match is not None and sc_app.completion_matches == ['Football '] + def test_subcommand_tab_completion_with_no_completer(sc_app): # This tests what happens when a subcommand has no completer # In this case, the foo subcommand has no completer defined @@ -874,6 +875,7 @@ def test_subcommand_tab_completion_with_no_completer(sc_app): first_match = complete_tester(text, line, begidx, endidx, sc_app) assert first_match is None + def test_subcommand_tab_completion_space_in_text(sc_app): text = 'B' line = 'base sport "Space {}'.format(text) @@ -886,10 +888,6 @@ def test_subcommand_tab_completion_space_in_text(sc_app): sc_app.completion_matches == ['Ball" '] and \ sc_app.display_matches == ['Space Ball'] - - - - #################################################### @@ -960,6 +958,7 @@ class SubcommandsWithUnknownExample(cmd2.Cmd): @pytest.fixture def scu_app(): + """Declare test fixture for with_argparser_and_unknown_args""" app = SubcommandsWithUnknownExample() return app @@ -975,6 +974,7 @@ def test_cmd2_subcmd_with_unknown_completion_single_end(scu_app): # It is at end of line, so extra space is present assert first_match is not None and scu_app.completion_matches == ['foo '] + def test_cmd2_subcmd_with_unknown_completion_multiple(scu_app): text = '' line = 'base {}'.format(text) @@ -984,6 +984,7 @@ def test_cmd2_subcmd_with_unknown_completion_multiple(scu_app): first_match = complete_tester(text, line, begidx, endidx, scu_app) assert first_match is not None and scu_app.completion_matches == ['bar', 'foo', 'sport'] + def test_cmd2_subcmd_with_unknown_completion_nomatch(scu_app): text = 'z' line = 'base {}'.format(text) @@ -1001,6 +1002,7 @@ def test_cmd2_help_subcommand_completion_single(scu_app): begidx = endidx - len(text) assert scu_app.complete_help(text, line, begidx, endidx) == ['base'] + def test_cmd2_help_subcommand_completion_multiple(scu_app): text = '' line = 'help base {}'.format(text) @@ -1018,6 +1020,7 @@ def test_cmd2_help_subcommand_completion_nomatch(scu_app): begidx = endidx - len(text) assert scu_app.complete_help(text, line, begidx, endidx) == [] + def test_subcommand_tab_completion(scu_app): # This makes sure the correct completer for the sport subcommand is called text = 'Foot' @@ -1030,6 +1033,7 @@ def test_subcommand_tab_completion(scu_app): # It is at end of line, so extra space is present assert first_match is not None and scu_app.completion_matches == ['Football '] + def test_subcommand_tab_completion_with_no_completer(scu_app): # This tests what happens when a subcommand has no completer # In this case, the foo subcommand has no completer defined @@ -1041,6 +1045,7 @@ def test_subcommand_tab_completion_with_no_completer(scu_app): first_match = complete_tester(text, line, begidx, endidx, scu_app) assert first_match is None + def test_subcommand_tab_completion_space_in_text(scu_app): text = 'B' line = 'base sport "Space {}'.format(text) @@ -1053,19 +1058,9 @@ def test_subcommand_tab_completion_space_in_text(scu_app): scu_app.completion_matches == ['Ball" '] and \ scu_app.display_matches == ['Space Ball'] - - #################################################### - - - - - - - - class SecondLevel(cmd2.Cmd): """To be used as a second level command class. """ |