summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xexamples/subcommands.py34
-rw-r--r--tests/test_argparse.py84
2 files changed, 106 insertions, 12 deletions
diff --git a/examples/subcommands.py b/examples/subcommands.py
index 59ebe4cb..bc276dff 100755
--- a/examples/subcommands.py
+++ b/examples/subcommands.py
@@ -1,15 +1,16 @@
#!/usr/bin/env python
# coding=utf-8
-"""A simple example demonstrating how to use Argparse to support sub-commands.
+"""A simple example demonstrating how to use Argparse to support subcommands.
This example shows an easy way for a single command to have many subcommands, each of which takes different arguments
and provides separate contextual help.
"""
import argparse
+import functools
import cmd2
-from cmd2 import with_argparser
+from cmd2 import with_argparser, index_based_complete
class SubcommandsExample(cmd2.Cmd):
@@ -18,7 +19,7 @@ class SubcommandsExample(cmd2.Cmd):
def __init__(self):
cmd2.Cmd.__init__(self)
- # sub-command functions for the base command
+ # subcommand functions for the base command
def base_foo(self, args):
"""foo subcommand of base command"""
self.poutput(args.x * args.y)
@@ -27,31 +28,50 @@ class SubcommandsExample(cmd2.Cmd):
"""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 """
+ sports = ['Football', 'Hockey', 'Soccer', 'Baseball']
+ index_dict = {1: sports}
+ return 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" sub-command
+ # 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" sub-command
+ # 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')
+ parser_sport.set_defaults(func=base_sport)
+
@with_argparser(base_parser)
def do_base(self, args):
"""Base command help"""
try:
- # Call whatever sub-command function was selected
+ # Call whatever subcommand function was selected
args.func(self, args)
except AttributeError:
- # No sub-command was provided, so as called
+ # No subcommand was provided, so as called
self.do_help('base')
+ # This makes sure correct tab completion functions are called based on the selected subcommand
+ complete_base = functools.partialmethod(cmd2.Cmd.cmd_with_subs_completer, base='base')
+
if __name__ == '__main__':
app = SubcommandsExample()
diff --git a/tests/test_argparse.py b/tests/test_argparse.py
index 85b438e2..2c2c2a5a 100644
--- a/tests/test_argparse.py
+++ b/tests/test_argparse.py
@@ -3,9 +3,12 @@
Cmd2 testing for argument parsing
"""
import argparse
+import functools
import pytest
+import readline
import cmd2
+import mock
from conftest import run_cmd, StdOut
@@ -171,7 +174,7 @@ class SubcommandApp(cmd2.Cmd):
def __init__(self):
cmd2.Cmd.__init__(self)
- # sub-command functions for the base command
+ # subcommand functions for the base command
def base_foo(self, args):
"""foo subcommand of base command"""
self.poutput(args.x * args.y)
@@ -180,31 +183,50 @@ class SubcommandApp(cmd2.Cmd):
"""bar sucommand 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 """
+ sports = ['Football', 'Hockey', 'Soccer', 'Baseball']
+ index_dict = {1: sports}
+ return cmd2.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" sub-command
+ # 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" sub-command
+ # 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')
+ parser_sport.set_defaults(func=base_sport)
+
@cmd2.with_argparser_and_unknown_args(base_parser)
def do_base(self, args, arglist):
"""Base command help"""
try:
- # Call whatever sub-command function was selected
+ # Call whatever subcommand function was selected
args.func(self, args)
except AttributeError:
- # No sub-command was provided, so as called
+ # No subcommand was provided, so as called
self.do_help('base')
+ # This makes sure correct tab completion functions are called based on the selected subcommand
+ complete_base = functools.partialmethod(cmd2.Cmd.cmd_with_subs_completer, base='base')
+
@pytest.fixture
def subcommand_app():
app = SubcommandApp()
@@ -245,3 +267,55 @@ def test_subcommand_invalid_help(subcommand_app):
out = run_cmd(subcommand_app, 'help base baz')
assert out[0].startswith('usage: base')
assert out[1].startswith("base: error: invalid choice: 'baz'")
+
+def test_sumcommand_tab_completion(subcommand_app):
+ # This makes sure the correct completer for the sport subcommand is called
+ text = 'Foot'
+ line = 'base sport Foot'
+ endidx = len(line)
+ begidx = endidx - len(text)
+ state = 0
+
+ def get_line():
+ return line
+
+ def get_begidx():
+ return begidx
+
+ def get_endidx():
+ return endidx
+
+ with mock.patch.object(readline, 'get_line_buffer', get_line):
+ with mock.patch.object(readline, 'get_begidx', get_begidx):
+ with mock.patch.object(readline, 'get_endidx', get_endidx):
+ # Run the readline tab-completion function with readline mocks in place
+ first_match = subcommand_app.complete(text, state)
+
+ # It is at end of line, so extra space is present
+ assert first_match is not None and subcommand_app.completion_matches == ['Football ']
+
+def test_sumcommand_tab_completion_with_no_completer(subcommand_app):
+ # This tests what happens when a subcommand has no completer
+ # In this case, the foo subcommand has no completer defined
+ text = 'Foot'
+ line = 'base foo Foot'
+ endidx = len(line)
+ begidx = endidx - len(text)
+ state = 0
+
+ def get_line():
+ return line
+
+ def get_begidx():
+ return begidx
+
+ def get_endidx():
+ return endidx
+
+ with mock.patch.object(readline, 'get_line_buffer', get_line):
+ with mock.patch.object(readline, 'get_begidx', get_begidx):
+ with mock.patch.object(readline, 'get_endidx', get_endidx):
+ # Run the readline tab-completion function with readline mocks in place
+ first_match = subcommand_app.complete(text, state)
+
+ assert first_match is None