Skip to content

Commit 52bf16c

Browse files
committed
Fixed issue where categorization is skipped when there's a help_<command> function provided.
In verbose help, added check for argparse usage block (starting with 'usage: '), to skip that block and move to the next comment block Added unit tests for new categorization code Updated example to demonstrate skipping of argparse usage statement
1 parent 2ffd342 commit 52bf16c

File tree

4 files changed

+138
-9
lines changed

4 files changed

+138
-9
lines changed

cmd2.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2934,10 +2934,9 @@ def _help_menu(self, verbose=False):
29342934
cmds_cats = {}
29352935

29362936
for command in visible_commands:
2937-
if command in help_topics:
2938-
cmds_doc.append(command)
2939-
help_topics.remove(command)
2940-
elif getattr(self, self._func_named(command)).__doc__:
2937+
if command in help_topics or getattr(self, self._func_named(command)).__doc__:
2938+
if command in help_topics:
2939+
help_topics.remove(command)
29412940
if hasattr(getattr(self, self._func_named(command)), HELP_CATEGORY):
29422941
category = getattr(getattr(self, self._func_named(command)), HELP_CATEGORY)
29432942
cmds_cats.setdefault(category, [])
@@ -2972,7 +2971,7 @@ def _print_topics(self, header, cmds, verbose):
29722971
widest = 0
29732972
# measure the commands
29742973
for command in cmds:
2975-
width = wcswidth(command)
2974+
width = len(command)
29762975
if width > widest:
29772976
widest = width
29782977
# add a 4-space pad
@@ -2983,17 +2982,23 @@ def _print_topics(self, header, cmds, verbose):
29832982
if self.ruler:
29842983
self.stdout.write('{:{ruler}<{width}}\n'.format('', ruler=self.ruler, width=80))
29852984

2985+
help_topics = self.get_help_topics()
29862986
for command in cmds:
29872987
# Attempt to locate the first documentation block
29882988
doc = getattr(self, self._func_named(command)).__doc__
29892989
doc_block = []
29902990
found_first = False
2991+
in_usage = False
29912992
for doc_line in doc.splitlines():
29922993
str(doc_line).strip()
29932994
if len(doc_line.strip()) > 0:
2995+
if in_usage or doc_line.startswith('usage: '):
2996+
in_usage = True
2997+
continue
29942998
doc_block.append(doc_line.strip())
29952999
found_first = True
29963000
else:
3001+
in_usage = False
29973002
if found_first:
29983003
break
29993004

@@ -3101,7 +3106,7 @@ def show(self, args, parameter):
31013106
else:
31023107
raise LookupError("Parameter '%s' not supported (type 'show' for list of parameters)." % param)
31033108

3104-
set_parser = argparse.ArgumentParser()
3109+
set_parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
31053110
set_parser.add_argument('-a', '--all', action='store_true', help='display read-only settings as well')
31063111
set_parser.add_argument('-l', '--long', action='store_true', help='describe function of parameter')
31073112
set_parser.add_argument('settable', nargs='*', help='[param_name] [value]')

examples/help_categories.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
A sample application for tagging categories on commands.
55
"""
66

7-
from cmd2 import Cmd, categorize, __version__
7+
from cmd2 import Cmd, categorize, __version__, with_argparser
8+
import argparse
89

910

1011
class HelpCategories(Cmd):
@@ -51,6 +52,12 @@ def do_redeploy(self, _):
5152
"""Redeploy command"""
5253
self.poutput('Redeploy')
5354

55+
restart_parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
56+
restart_parser.add_argument('when', default='now',
57+
choices=['now', 'later', 'sometime', 'whenever'],
58+
help='Specify when to restart')
59+
60+
@with_argparser(restart_parser)
5461
def do_restart(self, _):
5562
"""Restart command"""
5663
self.poutput('Restart')

tests/conftest.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,24 @@
1919
edit history py quit shell unalias
2020
"""
2121

22+
BASE_HELP_VERBOSE = """
23+
Documented commands (type help <topic>):
24+
================================================================================
25+
alias Define or display aliases
26+
edit Edit a file in a text editor.
27+
help List available commands with "help" or detailed help with "help cmd".
28+
history View, run, edit, and save previously entered commands.
29+
load Runs commands in script file that is encoded as either ASCII or UTF-8 text.
30+
py Invoke python command, shell, or script
31+
pyscript Runs a python script file inside the console
32+
quit Exits this application.
33+
set Sets a settable parameter or shows current settings of parameters.
34+
shell Execute a command as if at the OS prompt.
35+
shortcuts Lists shortcuts (aliases) available.
36+
unalias Unsets aliases
37+
38+
"""
39+
2240
# Help text for the history command
2341
HELP_HISTORY = """usage: history [-h] [-r | -e | -s | -o FILE | -t TRANSCRIPT] [arg]
2442

tests/test_cmd2.py

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
import six.moves as sm
2222

2323
import cmd2
24-
from conftest import run_cmd, normalize, BASE_HELP, HELP_HISTORY, SHORTCUTS_TXT, SHOW_TXT, SHOW_LONG, StdOut
24+
from conftest import run_cmd, normalize, BASE_HELP, BASE_HELP_VERBOSE, \
25+
HELP_HISTORY, SHORTCUTS_TXT, SHOW_TXT, SHOW_LONG, StdOut
2526

2627

2728
def test_ver():
@@ -38,6 +39,13 @@ def test_base_help(base_app):
3839
expected = normalize(BASE_HELP)
3940
assert out == expected
4041

42+
def test_base_help_verbose(base_app):
43+
out = run_cmd(base_app, 'help -v')
44+
expected = normalize(BASE_HELP_VERBOSE)
45+
assert out == expected
46+
47+
out = run_cmd(base_app, 'help --verbose')
48+
assert out == expected
4149

4250
def test_base_help_history(base_app):
4351
out = run_cmd(base_app, 'help history')
@@ -47,7 +55,7 @@ def test_base_argparse_help(base_app, capsys):
4755
# Verify that "set -h" gives the same output as "help set" and that it starts in a way that makes sense
4856
run_cmd(base_app, 'set -h')
4957
out, err = capsys.readouterr()
50-
out1 = out.splitlines()
58+
out1 = normalize(out)
5159

5260
out2 = run_cmd(base_app, 'help set')
5361

@@ -1066,6 +1074,97 @@ def test_help_overridden_method(help_app):
10661074
assert out == expected
10671075

10681076

1077+
class HelpCategoriesApp(cmd2.Cmd):
1078+
"""Class for testing custom help_* methods which override docstring help."""
1079+
def __init__(self, *args, **kwargs):
1080+
# Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x
1081+
cmd2.Cmd.__init__(self, *args, **kwargs)
1082+
1083+
def do_diddly(self, arg):
1084+
"""This command does diddly"""
1085+
pass
1086+
1087+
cmd2.categorize(do_diddly, "Some Category")
1088+
1089+
def do_squat(self, arg):
1090+
"""This docstring help will never be shown because the help_squat method overrides it."""
1091+
pass
1092+
1093+
def help_squat(self):
1094+
self.stdout.write('This command does diddly squat...\n')
1095+
1096+
def do_edit(self, arg):
1097+
"""This overrides the edit command and does nothing."""
1098+
pass
1099+
1100+
cmd2.categorize((do_squat, do_edit), 'Custom Category')
1101+
1102+
# This command will be in the "undocumented" section of the help menu
1103+
def do_undoc(self, arg):
1104+
pass
1105+
1106+
@pytest.fixture
1107+
def helpcat_app():
1108+
app = HelpCategoriesApp()
1109+
app.stdout = StdOut()
1110+
return app
1111+
1112+
def test_help_cat_base(helpcat_app):
1113+
out = run_cmd(helpcat_app, 'help')
1114+
expected = normalize("""Documented commands (type help <topic>):
1115+
1116+
Custom Category
1117+
===============
1118+
edit squat
1119+
1120+
Some Category
1121+
=============
1122+
diddly
1123+
1124+
Other
1125+
=====
1126+
alias help history load py pyscript quit set shell shortcuts unalias
1127+
1128+
Undocumented commands:
1129+
======================
1130+
undoc
1131+
""")
1132+
assert out == expected
1133+
1134+
def test_help_cat_verbose(helpcat_app):
1135+
out = run_cmd(helpcat_app, 'help --verbose')
1136+
expected = normalize("""Documented commands (type help <topic>):
1137+
1138+
Custom Category
1139+
================================================================================
1140+
edit This overrides the edit command and does nothing.
1141+
squat This docstring help will never be shown because the help_squat method overrides it.
1142+
1143+
Some Category
1144+
================================================================================
1145+
diddly This command does diddly
1146+
1147+
Other
1148+
================================================================================
1149+
alias Define or display aliases
1150+
help List available commands with "help" or detailed help with "help cmd".
1151+
history View, run, edit, and save previously entered commands.
1152+
load Runs commands in script file that is encoded as either ASCII or UTF-8 text.
1153+
py Invoke python command, shell, or script
1154+
pyscript Runs a python script file inside the console
1155+
quit Exits this application.
1156+
set Sets a settable parameter or shows current settings of parameters.
1157+
shell Execute a command as if at the OS prompt.
1158+
shortcuts Lists shortcuts (aliases) available.
1159+
unalias Unsets aliases
1160+
1161+
Undocumented commands:
1162+
======================
1163+
undoc
1164+
""")
1165+
assert out == expected
1166+
1167+
10691168
class SelectApp(cmd2.Cmd):
10701169
def do_eat(self, arg):
10711170
"""Eat something, with a selection of sauces to choose from."""

0 commit comments

Comments
 (0)