summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcmd2/cmd2.py167
-rwxr-xr-xexamples/subcommands.py11
-rw-r--r--tests/test_completion.py89
3 files changed, 13 insertions, 254 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 23e56f19..f9940b44 100755
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -271,21 +271,6 @@ def with_argparser_and_unknown_args(argparser: argparse.ArgumentParser) -> Calla
cmd_wrapper.__dict__['has_parser'] = True
setattr(cmd_wrapper, 'argparser', argparser)
- # If there are subcommands, store their names in a list to support tab-completion of subcommand names
- if argparser._subparsers is not None:
- # Key is subcommand name and value is completer function
- subcommands = collections.OrderedDict()
-
- # Get all subcommands and check if they have completer functions
- for name, parser in argparser._subparsers._group_actions[0]._name_parser_map.items():
- if 'completer' in parser._defaults:
- completer = parser._defaults['completer']
- else:
- completer = None
- subcommands[name] = completer
-
- cmd_wrapper.__dict__['subcommands'] = subcommands
-
return cmd_wrapper
return arg_decorator
@@ -324,22 +309,6 @@ def with_argparser(argparser: argparse.ArgumentParser) -> Callable:
cmd_wrapper.__dict__['has_parser'] = True
setattr(cmd_wrapper, 'argparser', argparser)
- # If there are subcommands, store their names in a list to support tab-completion of subcommand names
- if argparser._subparsers is not None:
-
- # Key is subcommand name and value is completer function
- subcommands = collections.OrderedDict()
-
- # Get all subcommands and check if they have completer functions
- for name, parser in argparser._subparsers._group_actions[0]._name_parser_map.items():
- if 'completer' in parser._defaults:
- completer = parser._defaults['completer']
- else:
- completer = None
- subcommands[name] = completer
-
- cmd_wrapper.__dict__['subcommands'] = subcommands
-
return cmd_wrapper
return arg_decorator
@@ -1023,49 +992,6 @@ class Cmd(cmd.Cmd):
return self._colorcodes[color][True] + val + self._colorcodes[color][False]
return val
- def get_subcommands(self, command):
- """
- Returns a list of a command's subcommand names if they exist
- :param command: the command we are querying
- :return: A subcommand list or None
- """
-
- subcommand_names = None
-
- # Check if is a valid command
- funcname = self._func_named(command)
-
- if funcname:
- # Check to see if this function was decorated with an argparse ArgumentParser
- func = getattr(self, funcname)
- subcommands = func.__dict__.get('subcommands', None)
- if subcommands is not None:
- subcommand_names = subcommands.keys()
-
- return subcommand_names
-
- def get_subcommand_completer(self, command, subcommand):
- """
- Returns a subcommand's tab completion function if one exists
- :param command: command which owns the subcommand
- :param subcommand: the subcommand we are querying
- :return: A completer or None
- """
-
- completer = None
-
- # Check if is a valid command
- funcname = self._func_named(command)
-
- if funcname:
- # Check to see if this function was decorated with an argparse ArgumentParser
- func = getattr(self, funcname)
- subcommands = func.__dict__.get('subcommands', None)
- if subcommands is not None:
- completer = subcommands[subcommand]
-
- return completer
-
# ----- Methods related to tab completion -----
def set_completion_defaults(self):
@@ -1797,10 +1723,11 @@ class Cmd(cmd.Cmd):
try:
compfunc = getattr(self, 'complete_' + command)
except AttributeError:
-
+ # There's no completer function, next see if the command uses argparser
cmd_func = getattr(self, 'do_' + command)
if hasattr(cmd_func, 'has_parser') and hasattr(cmd_func, 'argparser') and \
getattr(cmd_func, 'has_parser'):
+ # Command uses argparser, switch to the default argparse completer
argparser = getattr(cmd_func, 'argparser')
compfunc = functools.partial(self._autocomplete_default,
argparser=argparser)
@@ -1975,6 +1902,7 @@ class Cmd(cmd.Cmd):
strs_to_match = list(topics | visible_commands)
matches = self.basic_complete(text, line, begidx, endidx, strs_to_match)
+ # check if the command uses argparser
elif index >= subcmd_index and hasattr(self, 'do_' + tokens[cmd_index]) and\
hasattr(getattr(self, 'do_' + tokens[cmd_index]), 'has_parser'):
command = tokens[cmd_index]
@@ -1983,14 +1911,6 @@ class Cmd(cmd.Cmd):
completer = AutoCompleter(parser)
matches = completer.complete_command_help(tokens[1:], text, line, begidx, endidx)
-
- # Check if we are completing a subcommand
- elif index == subcmd_index:
-
- # Match subcommands if any exist
- command = tokens[cmd_index]
- matches = self.basic_complete(text, line, begidx, endidx, self.get_subcommands(command))
-
return matches
# noinspection PyUnusedLocal
@@ -2947,87 +2867,6 @@ Usage: Usage: unalias [-a] name [name ...]
index_dict = {1: self.shell_cmd_complete}
return self.index_based_complete(text, line, begidx, endidx, index_dict, self.path_complete)
- def cmd_with_subs_completer(self, text, line, begidx, endidx):
- """
- This is a function provided for convenience to those who want an easy way to add
- tab completion to functions that implement subcommands. By setting this as the
- completer of the base command function, the correct completer for the chosen subcommand
- will be called.
-
- The use of this function requires assigning a completer function to the subcommand's parser
- Example:
- A command called print has a subcommands called 'names' that needs a tab completer
- When you create the parser for names, include the completer function in the parser's defaults.
-
- names_parser.set_defaults(func=print_names, completer=complete_print_names)
-
- To make sure the names completer gets called, set the completer for the print function
- in a similar fashion to what follows.
-
- complete_print = cmd2.Cmd.cmd_with_subs_completer
-
- When the subcommand's completer is called, this function will have stripped off all content from the
- beginning of the command line before the subcommand, meaning the line parameter always starts with the
- subcommand name and the index parameters reflect this change.
-
- For instance, the command "print names -d 2" becomes "names -d 2"
- begidx and endidx are incremented accordingly
-
- :param text: str - the string prefix we are attempting to match (all returned matches must begin with it)
- :param line: str - the current input line with leading whitespace removed
- :param begidx: int - the beginning index of the prefix text
- :param endidx: int - the ending index of the prefix text
- :return: List[str] - a list of possible tab completions
- """
- # The command is the token at index 0 in the command line
- cmd_index = 0
-
- # The subcommand is the token at index 1 in the command line
- subcmd_index = 1
-
- # Get all tokens through the one being completed
- tokens, _ = self.tokens_for_completion(line, begidx, endidx)
- if tokens is None:
- return []
-
- matches = []
-
- # Get the index of the token being completed
- index = len(tokens) - 1
-
- # If the token being completed is past the subcommand name, then do subcommand specific tab-completion
- if index > subcmd_index:
-
- # Get the command name
- command = tokens[cmd_index]
-
- # Get the subcommand name
- subcommand = tokens[subcmd_index]
-
- # Find the offset into line where the subcommand name begins
- subcmd_start = 0
- for cur_index in range(0, subcmd_index + 1):
- cur_token = tokens[cur_index]
- subcmd_start = line.find(cur_token, subcmd_start)
-
- if cur_index != subcmd_index:
- subcmd_start += len(cur_token)
-
- # Strip off everything before subcommand name
- orig_line = line
- line = line[subcmd_start:]
-
- # Update the indexes
- diff = len(orig_line) - len(line)
- begidx -= diff
- endidx -= diff
-
- # Call the subcommand specific completer if it exists
- compfunc = self.get_subcommand_completer(command, subcommand)
- if compfunc is not None:
- matches = compfunc(self, text, line, begidx, endidx)
-
- return matches
# noinspection PyBroadException
def do_py(self, arg):
diff --git a/examples/subcommands.py b/examples/subcommands.py
index 03088c93..8cdfb368 100755
--- a/examples/subcommands.py
+++ b/examples/subcommands.py
@@ -9,7 +9,7 @@ and provides separate contextual help.
import argparse
import cmd2
-from cmd2 import with_argparser
+from cmd2 import with_argparser, with_argparser_and_unknown_args
sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball']
@@ -35,12 +35,6 @@ class SubcommandsExample(cmd2.Cmd):
"""sport subcommand of base command"""
self.poutput('Sport is {}'.format(args.sport))
- # noinspection PyUnusedLocal
- def complete_base_sport(self, text, line, begidx, endidx):
- """ Adds tab completion to base sport subcommand """
- index_dict = {1: sport_item_strs}
- return self.index_based_complete(text, line, begidx, endidx, index_dict)
-
# create the top-level parser for the base command
base_parser = argparse.ArgumentParser(prog='base')
base_subparsers = base_parser.add_subparsers(title='subcommands', help='subcommand help')
@@ -81,9 +75,6 @@ class SubcommandsExample(cmd2.Cmd):
# No subcommand was provided, so call help
self.do_help('base')
- # Enable tab completion of base to make sure the subcommands' completers get called.
- # complete_base = cmd2.Cmd.cmd_with_subs_completer
-
if __name__ == '__main__':
app = SubcommandsExample()
diff --git a/tests/test_completion.py b/tests/test_completion.py
index a01d1166..cf45f281 100644
--- a/tests/test_completion.py
+++ b/tests/test_completion.py
@@ -14,7 +14,8 @@ import sys
import cmd2
import pytest
-from .conftest import complete_tester
+from .conftest import complete_tester, StdOut
+from examples.subcommands import SubcommandsExample
# List of strings used with completion functions
food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato']
@@ -726,76 +727,13 @@ 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
-class SubcommandsExample(cmd2.Cmd):
- """
- Example cmd2 application where we a base command which has a couple subcommands
- and the "sport" subcommand has tab completion enabled.
- """
-
- def __init__(self):
- cmd2.Cmd.__init__(self)
-
- # subcommand functions for the base command
- def base_foo(self, args):
- """foo subcommand of base command"""
- self.poutput(args.x * args.y)
-
- def base_bar(self, args):
- """bar subcommand of base command"""
- self.poutput('((%s))' % args.z)
-
- def base_sport(self, args):
- """sport subcommand of base command"""
- self.poutput('Sport is {}'.format(args.sport))
-
- # noinspection PyUnusedLocal
- def complete_base_sport(self, text, line, begidx, endidx):
- """ Adds tab completion to base sport subcommand """
- index_dict = {1: sport_item_strs}
- return self.index_based_complete(text, line, begidx, endidx, index_dict)
-
- # create the top-level parser for the base command
- base_parser = argparse.ArgumentParser(prog='base')
- base_subparsers = base_parser.add_subparsers(title='subcommands', help='subcommand help')
-
- # create the parser for the "foo" subcommand
- parser_foo = base_subparsers.add_parser('foo', help='foo help')
- parser_foo.add_argument('-x', type=int, default=1, help='integer')
- parser_foo.add_argument('y', type=float, help='float')
- parser_foo.set_defaults(func=base_foo)
-
- # create the parser for the "bar" subcommand
- parser_bar = base_subparsers.add_parser('bar', help='bar help')
- parser_bar.add_argument('z', help='string')
- parser_bar.set_defaults(func=base_bar)
-
- # create the parser for the "sport" subcommand
- parser_sport = base_subparsers.add_parser('sport', help='sport help')
- parser_sport.add_argument('sport', help='Enter name of a sport')
-
- # Set both a function and tab completer for the "sport" subcommand
- parser_sport.set_defaults(func=base_sport, completer=complete_base_sport)
-
- @cmd2.with_argparser(base_parser)
- def do_base(self, args):
- """Base command help"""
- func = getattr(args, 'func', None)
- if func is not None:
- # Call whatever subcommand function was selected
- func(self, args)
- else:
- # No subcommand was provided, so call help
- self.do_help('base')
-
- # Enable tab completion of base to make sure the subcommands' completers get called.
- complete_base = cmd2.Cmd.cmd_with_subs_completer
-
@pytest.fixture
def sc_app():
- app = SubcommandsExample()
- return app
+ c = SubcommandsExample()
+ c.stdout = StdOut()
+ return c
def test_cmd2_subcommand_completion_single_end(sc_app):
text = 'f'
@@ -913,12 +851,6 @@ class SubcommandsWithUnknownExample(cmd2.Cmd):
"""sport subcommand of base command"""
self.poutput('Sport is {}'.format(args.sport))
- # noinspection PyUnusedLocal
- def complete_base_sport(self, text, line, begidx, endidx):
- """ Adds tab completion to base sport subcommand """
- index_dict = {1: sport_item_strs}
- return self.index_based_complete(text, line, begidx, endidx, index_dict)
-
# create the top-level parser for the base command
base_parser = argparse.ArgumentParser(prog='base')
base_subparsers = base_parser.add_subparsers(title='subcommands', help='subcommand help')
@@ -936,10 +868,8 @@ class SubcommandsWithUnknownExample(cmd2.Cmd):
# create the parser for the "sport" subcommand
parser_sport = base_subparsers.add_parser('sport', help='sport help')
- parser_sport.add_argument('sport', help='Enter name of a sport')
-
- # Set both a function and tab completer for the "sport" subcommand
- parser_sport.set_defaults(func=base_sport, completer=complete_base_sport)
+ sport_arg = parser_sport.add_argument('sport', help='Enter name of a sport')
+ setattr(sport_arg, 'arg_choices', sport_item_strs)
@cmd2.with_argparser_and_unknown_args(base_parser)
def do_base(self, args):
@@ -952,9 +882,6 @@ class SubcommandsWithUnknownExample(cmd2.Cmd):
# No subcommand was provided, so call help
self.do_help('base')
- # Enable tab completion of base to make sure the subcommands' completers get called.
- complete_base = cmd2.Cmd.cmd_with_subs_completer
-
@pytest.fixture
def scu_app():
@@ -971,6 +898,8 @@ def test_cmd2_subcmd_with_unknown_completion_single_end(scu_app):
first_match = complete_tester(text, line, begidx, endidx, scu_app)
+ print('first_match: {}'.format(first_match))
+
# It is at end of line, so extra space is present
assert first_match is not None and scu_app.completion_matches == ['foo ']