Skip to content

Commit 7b564b4

Browse files
authored
Merge pull request #255 from python-cmd2/argparse_help
Improved how new argparse-based decorators provide help
2 parents 91bc999 + 03f1569 commit 7b564b4

File tree

6 files changed

+46
-49
lines changed

6 files changed

+46
-49
lines changed

cmd2.py

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -288,13 +288,10 @@ def cmd_wrapper(instance, cmdline):
288288
argparser.prog = func.__name__[3:]
289289

290290
# put the help message in the method docstring
291-
funcdoc = func.__doc__
292-
if funcdoc:
293-
funcdoc += '\n'
294-
else:
295-
# if it's None, make it an empty string
296-
funcdoc = ''
297-
cmd_wrapper.__doc__ = '{}{}'.format(funcdoc, argparser.format_help())
291+
if func.__doc__:
292+
argparser.description = func.__doc__
293+
294+
cmd_wrapper.__doc__ = argparser.format_help()
298295
return cmd_wrapper
299296
return arg_decorator
300297

@@ -315,13 +312,10 @@ def cmd_wrapper(instance, cmdline):
315312
argparser.prog = func.__name__[3:]
316313

317314
# put the help message in the method docstring
318-
funcdoc = func.__doc__
319-
if funcdoc:
320-
funcdoc += '\n'
321-
else:
322-
# if it's None, make it an empty string
323-
funcdoc = ''
324-
cmd_wrapper.__doc__ = '{}{}'.format(funcdoc, argparser.format_help())
315+
if func.__doc__:
316+
argparser.description = func.__doc__
317+
318+
cmd_wrapper.__doc__ = argparser.format_help()
325319
return cmd_wrapper
326320
return arg_decorator
327321

@@ -1341,7 +1335,7 @@ def show(self, args, parameter):
13411335
else:
13421336
raise LookupError("Parameter '%s' not supported (type 'show' for list of parameters)." % param)
13431337

1344-
set_parser = argparse.ArgumentParser(description='show or set value of a parameter')
1338+
set_parser = argparse.ArgumentParser()
13451339
set_parser.add_argument('-a', '--all', action='store_true', help='display read-only settings as well')
13461340
set_parser.add_argument('-l', '--long', action='store_true', help='describe function of parameter')
13471341
set_parser.add_argument('settable', nargs='*', help='[param_name] [value]')
@@ -1693,13 +1687,11 @@ def do_ipy(self, arg):
16931687
exit_msg = 'Leaving IPython, back to {}'.format(sys.argv[0])
16941688
embed(banner1=banner, exit_msg=exit_msg)
16951689

1696-
history_parser = argparse.ArgumentParser(
1697-
description='run, edit, and save previously entered commands',
1698-
formatter_class=argparse.RawTextHelpFormatter,
1699-
)
1690+
history_parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
17001691
history_parser_group = history_parser.add_mutually_exclusive_group()
17011692
history_parser_group.add_argument('-r', '--run', action='store_true', help='run selected history items')
1702-
history_parser_group.add_argument('-e', '--edit', action='store_true', help='edit and then run selected history items')
1693+
history_parser_group.add_argument('-e', '--edit', action='store_true',
1694+
help='edit and then run selected history items')
17031695
history_parser_group.add_argument('-o', '--output-file', metavar='FILE', help='output to file')
17041696
history_parser.add_argument('-s', '--script', action='store_true', help='script format; no separation lines')
17051697
_history_arg_help = """empty all history items
@@ -1711,8 +1703,8 @@ def do_ipy(self, arg):
17111703

17121704
@with_argument_parser(history_parser)
17131705
def do_history(self, args):
1714-
# If an argument was supplied, then retrieve partial contents of the
1715-
# history
1706+
"""View, run, edit, and save previously entered commands."""
1707+
# If an argument was supplied, then retrieve partial contents of the history
17161708
cowardly_refuse_to_run = False
17171709
if args.arg:
17181710
# If a character indicating a slice is present, retrieve
@@ -1735,7 +1727,8 @@ def do_history(self, args):
17351727
if args.run:
17361728
if cowardly_refuse_to_run:
17371729
self.perror("Cowardly refusing to run all previously entered commands.", traceback_war=False)
1738-
self.perror("If this is what you want to do, specify '1:' as the range of history.", traceback_war=False)
1730+
self.perror("If this is what you want to do, specify '1:' as the range of history.",
1731+
traceback_war=False)
17391732
else:
17401733
for runme in history:
17411734
self.pfeedback(runme)
@@ -1744,20 +1737,20 @@ def do_history(self, args):
17441737
elif args.edit:
17451738
fd, fname = tempfile.mkstemp(suffix='.txt', text=True)
17461739
with os.fdopen(fd, 'w') as fobj:
1747-
for cmd in history:
1748-
fobj.write('{}\n'.format(cmd))
1740+
for command in history:
1741+
fobj.write('{}\n'.format(command))
17491742
try:
17501743
os.system('"{}" "{}"'.format(self.editor, fname))
17511744
self.do_load(fname)
1752-
except:
1745+
except Exception:
17531746
raise
17541747
finally:
17551748
os.remove(fname)
17561749
elif args.output_file:
17571750
try:
17581751
with open(os.path.expanduser(args.output_file), 'w') as fobj:
1759-
for cmd in history:
1760-
fobj.write('{}\n'.format(cmd))
1752+
for command in history:
1753+
fobj.write('{}\n'.format(command))
17611754
plural = 's' if len(history) > 1 else ''
17621755
self.pfeedback('{} command{} saved to {}'.format(len(history), plural, args.output_file))
17631756
except Exception as e:
@@ -1770,7 +1763,6 @@ def do_history(self, args):
17701763
else:
17711764
self.poutput(hi.pr())
17721765

1773-
17741766
@with_argument_list
17751767
def do_edit(self, arglist):
17761768
"""Edit a file or command in a text editor.
@@ -1876,7 +1868,6 @@ def do_load(self, arglist):
18761868

18771869
self._script_dir.append(os.path.dirname(expanded_path))
18781870

1879-
18801871
@staticmethod
18811872
def is_text_file(file_path):
18821873
"""

docs/argument_processing.rst

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,25 +58,26 @@ Help Messages
5858

5959
By default, cmd2 uses the docstring of the command method when a user asks
6060
for help on the command. When you use the ``@with_argument_parser``
61-
decorator, the formatted help from the ``argparse.ArgumentParser`` is
62-
appended to the docstring for the method of that command. With this code::
61+
decorator, the docstring for the ``do_*`` method is used to set the description for the ``argparse.ArgumentParser`` is
62+
With this code::
6363

6464
argparser = argparse.ArgumentParser()
65-
argparser.add_argument('tag', nargs=1, help='tag')
65+
argparser.add_argument('tag', help='tag')
6666
argparser.add_argument('content', nargs='+', help='content to surround with tag')
6767
@with_argument_parser(argparser)
6868
def do_tag(self, args):
6969
"""create a html tag"""
70-
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag[0], ' '.join(args.content)))
70+
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag, ' '.join(args.content)))
7171
self.stdout.write('\n')
7272

7373
The ``help tag`` command displays:
7474

7575
.. code-block:: none
7676
77-
create a html tag
7877
usage: tag [-h] tag content [content ...]
7978
79+
create a html tag
80+
8081
positional arguments:
8182
tag tag
8283
content content to surround with tag
@@ -85,14 +86,15 @@ The ``help tag`` command displays:
8586
-h, --help show this help message and exit
8687
8788
88-
If you would prefer the short description of your command to come after the usage message, leave the docstring on your method empty, but supply a ``description`` variable to the argument parser::
89+
If you would prefer you can set the ``description`` while instantiating the ``argparse.ArgumentParser`` and leave the
90+
docstring on your method empty::
8991

9092
argparser = argparse.ArgumentParser(description='create an html tag')
91-
argparser.add_argument('tag', nargs=1, help='tag')
93+
argparser.add_argument('tag', help='tag')
9294
argparser.add_argument('content', nargs='+', help='content to surround with tag')
9395
@with_argument_parser(argparser)
9496
def do_tag(self, args):
95-
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag[0], ' '.join(args.content)))
97+
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag, ' '.join(args.content)))
9698
self.stdout.write('\n')
9799

98100
Now when the user enters ``help tag`` they see:
@@ -117,11 +119,11 @@ To add additional text to the end of the generated help message, use the ``epilo
117119
description='create an html tag',
118120
epilog='This command can not generate tags with no content, like <br/>.'
119121
)
120-
argparser.add_argument('tag', nargs=1, help='tag')
122+
argparser.add_argument('tag', help='tag')
121123
argparser.add_argument('content', nargs='+', help='content to surround with tag')
122124
@with_argument_parser(argparser)
123125
def do_tag(self, args):
124-
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag[0], ' '.join(args.content)))
126+
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag, ' '.join(args.content)))
125127
self.stdout.write('\n')
126128

127129
Which yields:

examples/argparse_example.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,14 @@ def do_speak(self, args):
6464
do_say = do_speak # now "say" is a synonym for "speak"
6565
do_orate = do_speak # another synonym, but this one takes multi-line input
6666

67-
tag_parser = argparse.ArgumentParser(description='create a html tag')
68-
tag_parser.add_argument('tag', nargs=1, help='tag')
67+
tag_parser = argparse.ArgumentParser()
68+
tag_parser.add_argument('tag', help='tag')
6969
tag_parser.add_argument('content', nargs='+', help='content to surround with tag')
7070

7171
@with_argument_parser(tag_parser)
7272
def do_tag(self, args):
7373
"""create a html tag"""
74-
self.poutput('<{0}>{1}</{0}>'.format(args.tag[0], ' '.join(args.content)))
74+
self.poutput('<{0}>{1}</{0}>'.format(args.tag, ' '.join(args.content)))
7575

7676

7777
@with_argument_list

examples/pirate.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,15 @@ def do_sing(self, arg):
7676
yo_parser = argparse.ArgumentParser()
7777
yo_parser.add_argument('--ho', type=int, default=2, help="How often to chant 'ho'")
7878
yo_parser.add_argument('-c', '--commas', action='store_true', help='Intersperse commas')
79-
yo_parser.add_argument('beverage', nargs=1, help='beverage to drink with the chant')
79+
yo_parser.add_argument('beverage', help='beverage to drink with the chant')
8080

8181
@with_argument_parser(yo_parser)
8282
def do_yo(self, args):
8383
"""Compose a yo-ho-ho type chant with flexible options."""
8484
chant = ['yo'] + ['ho'] * args.ho
8585
separator = ', ' if args.commas else ' '
8686
chant = separator.join(chant)
87-
print('{0} and a bottle of {1}'.format(chant, args.beverage[0]))
87+
print('{0} and a bottle of {1}'.format(chant, args.beverage))
8888

8989

9090
if __name__ == '__main__':

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
# Help text for the history command
2222
HELP_HISTORY = """usage: history [-h] [-r | -e | -o FILE] [-s] [arg]
2323
24-
run, edit, and save previously entered commands
24+
View, run, edit, and save previously entered commands.
2525
2626
positional arguments:
2727
arg empty all history items

tests/test_argparse.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ def do_say(self, args):
3737
self.stdout.write('\n')
3838

3939
tag_parser = argparse.ArgumentParser(description='create a html tag')
40-
tag_parser.add_argument('tag', nargs=1, help='tag')
40+
tag_parser.add_argument('tag', help='tag')
4141
tag_parser.add_argument('content', nargs='+', help='content to surround with tag')
4242

4343
@cmd2.with_argument_parser(tag_parser)
4444
def do_tag(self, args):
45-
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag[0], ' '.join(args.content)))
45+
self.stdout.write('<{0}>{1}</{0}>'.format(args.tag, ' '.join(args.content)))
4646
self.stdout.write('\n')
4747

4848
@cmd2.with_argument_list
@@ -140,10 +140,14 @@ def test_argparse_quoted_arguments_posix_multiple(argparse_app):
140140

141141
def test_argparse_help_docstring(argparse_app):
142142
out = run_cmd(argparse_app, 'help say')
143-
assert out[0] == 'Repeat what you tell me to.'
143+
assert out[0].startswith('usage: say')
144+
assert out[1] == ''
145+
assert out[2] == 'Repeat what you tell me to.'
144146

145147
def test_argparse_help_description(argparse_app):
146148
out = run_cmd(argparse_app, 'help tag')
149+
assert out[0].startswith('usage: tag')
150+
assert out[1] == ''
147151
assert out[2] == 'create a html tag'
148152

149153
def test_argparse_prog(argparse_app):

0 commit comments

Comments
 (0)