Skip to content

Commit 136de7e

Browse files
committed
Added support for argparse sub-commands when using cmd2 decorators
Modified the do_help() method to behave differently for methods which have been decorated with an argparse ArgumentParser. This is so that help will properly deal with sub-command help. Suppose you have a base command "base" which has two sub-commands, "foo" and "bar". Then "help base" will provide very different help text than "help base foo". Slightly tweaked the two argparse decorators to set an attribute in the decorated function's dictionary so that the do_help method can know which functions have an ArgumentParser and which do not. Added a "subcommands.py" example for demonstrating how to create and use subcommands based on argparse and the cmd2 @with_argument_parser decorator.
1 parent 7b564b4 commit 136de7e

File tree

2 files changed

+78
-2
lines changed

2 files changed

+78
-2
lines changed

cmd2.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,12 @@ def cmd_wrapper(instance, cmdline):
292292
argparser.description = func.__doc__
293293

294294
cmd_wrapper.__doc__ = argparser.format_help()
295+
296+
# Mark this function as having an argparse ArgumentParser (used by do_help)
297+
cmd_wrapper.__dict__['has_parser'] = True
298+
295299
return cmd_wrapper
300+
296301
return arg_decorator
297302

298303

@@ -316,7 +321,12 @@ def cmd_wrapper(instance, cmdline):
316321
argparser.description = func.__doc__
317322

318323
cmd_wrapper.__doc__ = argparser.format_help()
324+
325+
# Mark this function as having an argparse ArgumentParser (used by do_help)
326+
cmd_wrapper.__dict__['has_parser'] = True
327+
319328
return cmd_wrapper
329+
320330
return arg_decorator
321331

322332

@@ -1198,8 +1208,16 @@ def do_help(self, arglist):
11981208
# Getting help for a specific command
11991209
funcname = self._func_named(arglist[0])
12001210
if funcname:
1201-
# No special behavior needed, delegate to cmd base class do_help()
1202-
cmd.Cmd.do_help(self, funcname[3:])
1211+
# Check to see if this function was decorated with an argparse ArgumentParser
1212+
func = getattr(self, funcname)
1213+
if func.__dict__.get('has_parser', False):
1214+
# Function has an argparser, so get help based on all the arguments in case there are sub-commands
1215+
new_arglist = arglist[1:]
1216+
new_arglist.append('-h')
1217+
func(new_arglist)
1218+
else:
1219+
# No special behavior needed, delegate to cmd base class do_help()
1220+
cmd.Cmd.do_help(self, funcname[3:])
12031221
else:
12041222
# Show a menu of what commands help can be gotten for
12051223
self._help_menu()

examples/subcommands.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env python
2+
# coding=utf-8
3+
"""A simple example demonstrating how to use Argparse to support sub-commands.
4+
5+
6+
This example shows an easy way for a single command to have many subcommands, each of which takes different arguments
7+
and provides separate contextual help.
8+
"""
9+
import argparse
10+
11+
import cmd2
12+
from cmd2 import with_argument_parser
13+
14+
15+
class SubcommandsExample(cmd2.Cmd):
16+
""" Example cmd2 application where we a base command which has a couple subcommands."""
17+
18+
def __init__(self):
19+
cmd2.Cmd.__init__(self)
20+
21+
# sub-command functions for the base command
22+
def foo(self, args):
23+
"""foo subcommand of base command"""
24+
print(args.x * args.y)
25+
26+
def bar(self, args):
27+
"""bar sucommand of base command"""
28+
print('((%s))' % args.z)
29+
30+
# create the top-level parser
31+
base_parser = argparse.ArgumentParser(prog='base')
32+
base_subparsers = base_parser.add_subparsers(title='subcommands', help='subcommand help')
33+
34+
# create the parser for the "foo" command
35+
parser_foo = base_subparsers.add_parser('foo', help='foo help')
36+
parser_foo.add_argument('-x', type=int, default=1, help='integer')
37+
parser_foo.add_argument('y', type=float, help='float')
38+
parser_foo.set_defaults(func=foo)
39+
40+
# create the parser for the "bar" command
41+
parser_bar = base_subparsers.add_parser('bar', help='bar help')
42+
parser_bar.add_argument('z', help='string')
43+
parser_bar.set_defaults(func=bar)
44+
45+
@with_argument_parser(base_parser)
46+
def do_base(self, args):
47+
"""Base command help"""
48+
try:
49+
# Call whatever sub-command function was selected
50+
args.func(self, args)
51+
except AttributeError:
52+
# No sub-command was provided, so as called
53+
self.do_help('base')
54+
55+
56+
if __name__ == '__main__':
57+
app = SubcommandsExample()
58+
app.cmdloop()

0 commit comments

Comments
 (0)