diff options
-rwxr-xr-x | cmd2.py | 58 | ||||
-rw-r--r-- | docs/argument_processing.rst | 132 | ||||
-rwxr-xr-x | examples/help_categories.py | 12 | ||||
-rw-r--r-- | tests/conftest.py | 1 | ||||
-rw-r--r-- | tests/test_cmd2.py | 7 |
5 files changed, 192 insertions, 18 deletions
@@ -79,7 +79,6 @@ except ImportError: return True return NotImplemented - # Newer versions of pyperclip are released as a single file, but older versions had a more complicated structure try: from pyperclip.exceptions import PyperclipException @@ -113,6 +112,12 @@ if sys.version_info < (3, 5): else: from contextlib import redirect_stdout, redirect_stderr +if sys.version_info > (3, 0): + from io import StringIO # Python3 +else: + from io import BytesIO as StringIO # Python2 + + # Detect whether IPython is installed to determine if the built-in "ipy" command should be included ipython_available = True try: @@ -183,6 +188,7 @@ if six.PY2 and sys.platform.startswith('lin'): except ImportError: pass + __version__ = '0.8.4' # Pyparsing enablePackrat() can greatly speed up parsing, but problems have been seen in Python 3 in the past @@ -212,6 +218,7 @@ REDIRECTION_CHARS = ['|', '<', '>'] # optional attribute, when tagged on a function, allows cmd2 to categorize commands HELP_CATEGORY = 'help_category' +HELP_SUMMARY = 'help_summary' def categorize(func, category): @@ -358,6 +365,14 @@ def parse_quoted_string(cmdline): return lexed_arglist +def with_category(category): + """A decorator to apply a category to a command function""" + def cat_decorator(func): + categorize(func, category) + return func + return cat_decorator + + def with_argument_list(func): """A decorator to alter the arguments passed to a do_* cmd2 method. Default passes a string of whatever the user typed. @@ -396,6 +411,9 @@ def with_argparser_and_unknown_args(argparser): if argparser.description is None and func.__doc__: argparser.description = func.__doc__ + if func.__doc__: + setattr(cmd_wrapper, HELP_SUMMARY, func.__doc__) + cmd_wrapper.__doc__ = argparser.format_help() # Mark this function as having an argparse ArgumentParser (used by do_help) @@ -435,6 +453,9 @@ def with_argparser(argparser): if argparser.description is None and func.__doc__: argparser.description = func.__doc__ + if func.__doc__: + setattr(cmd_wrapper, HELP_SUMMARY, func.__doc__) + cmd_wrapper.__doc__ = argparser.format_help() # Mark this function as having an argparse ArgumentParser (used by do_help) @@ -2984,21 +3005,44 @@ Usage: Usage: unalias [-a] name [name ...] help_topics = self.get_help_topics() for command in cmds: + doc = '' + # Try to get the documentation string + try: + # first see if there's a help function implemented + func = getattr(self, 'help_' + command) + except AttributeError: + # Couldn't find a help function + try: + # Now see if help_summary has been set + doc = getattr(self, self._func_named(command)).help_summary + except AttributeError: + # Last, try to directly ac cess the function's doc-string + doc = getattr(self, self._func_named(command)).__doc__ + else: + # we found the help function + result = StringIO() + # try to redirect system stdout + with redirect_stdout(result): + # save our internal stdout + stdout_orig = self.stdout + try: + # redirect our internal stdout + self.stdout = result + func() + finally: + # restore internal stdout + self.stdout = stdout_orig + doc = result.getvalue() + # Attempt to locate the first documentation block - doc = getattr(self, self._func_named(command)).__doc__ doc_block = [] found_first = False - in_usage = False for doc_line in doc.splitlines(): str(doc_line).strip() if len(doc_line.strip()) > 0: - if in_usage or doc_line.startswith('usage: '): - in_usage = True - continue doc_block.append(doc_line.strip()) found_first = True else: - in_usage = False if found_first: break diff --git a/docs/argument_processing.rst b/docs/argument_processing.rst index 15c947fb..08f866b2 100644 --- a/docs/argument_processing.rst +++ b/docs/argument_processing.rst @@ -160,6 +160,138 @@ Which yields: This command can not generate tags with no content, like <br/> +Grouping Commands +================= + +By default, the ``help`` command displays:: + + Documented commands (type help <topic>): + ======================================== + alias findleakers pyscript sessions status vminfo + config help quit set stop which + connect history redeploy shell thread_dump + deploy list resources shortcuts unalias + edit load restart sslconnectorciphers undeploy + expire py serverinfo start version + +If you have a large number of commands, you can optionally group your commands into categories. +Here's the output from the example ``help_categories.py``:: + + Documented commands (type help <topic>): + + Application Management + ====================== + deploy findleakers redeploy sessions stop + expire list restart start undeploy + + Connecting + ========== + connect which + + Server Information + ================== + resources serverinfo sslconnectorciphers status thread_dump vminfo + + Other + ===== + alias edit history py quit shell unalias + config help load pyscript set shortcuts version + + +There are 2 methods of specifying command categories, using the ``@with_category`` decorator or with the +``categorize()`` function. Once a single command category is detected, the help output switches to a categorized +mode of display. All commands with an explicit category defined default to the category `Other`. + +Using the ``@with_category`` decorator:: + + @with_category(CMD_CAT_CONNECTING) + def do_which(self, _): + """Which command""" + self.poutput('Which') + +Using the ``categorize()`` function: + + You can call with a single function:: + + def do_connect(self, _): + """Connect command""" + self.poutput('Connect') + + # Tag the above command functions under the category Connecting + categorize(do_connect, CMD_CAT_CONNECTING) + + Or with an Iterable container of functions:: + + def do_undeploy(self, _): + """Undeploy command""" + self.poutput('Undeploy') + + def do_stop(self, _): + """Stop command""" + self.poutput('Stop') + + def do_findleakers(self, _): + """Find Leakers command""" + self.poutput('Find Leakers') + + # Tag the above command functions under the category Application Management + categorize((do_undeploy, + do_stop, + do_findleakers), CMD_CAT_APP_MGMT) + +The ``help`` command also has a verbose option (``help -v`` or ``help --verbose``) that combines +the help categories with per-command Help Messages:: + + Documented commands (type help <topic>): + + Application Management + ================================================================================ + deploy Deploy command + expire Expire command + findleakers Find Leakers command + list List command + redeploy Redeploy command + restart usage: restart [-h] {now,later,sometime,whenever} + sessions Sessions command + start Start command + stop Stop command + undeploy Undeploy command + + Connecting + ================================================================================ + connect Connect command + which Which command + + Server Information + ================================================================================ + resources Resources command + serverinfo Server Info command + sslconnectorciphers SSL Connector Ciphers command is an example of a command that contains + multiple lines of help information for the user. Each line of help in a + contiguous set of lines will be printed and aligned in the verbose output + provided with 'help --verbose' + status Status command + thread_dump Thread Dump command + vminfo VM Info command + + Other + ================================================================================ + alias Define or display aliases + config Config command + edit Edit a file in a text editor. + help List available commands with "help" or detailed help with "help cmd". + history usage: history [-h] [-r | -e | -s | -o FILE | -t TRANSCRIPT] [arg] + load Runs commands in script file that is encoded as either ASCII or UTF-8 text. + py Invoke python command, shell, or script + pyscript Runs a python script file inside the console + quit Exits this application. + set usage: set [-h] [-a] [-l] [settable [settable ...]] + shell Execute a command as if at the OS prompt. + shortcuts Lists shortcuts (aliases) available. + unalias Unsets aliases + version Version command + + Receiving an argument list ========================== diff --git a/examples/help_categories.py b/examples/help_categories.py index 8a33e62c..e7e3373d 100755 --- a/examples/help_categories.py +++ b/examples/help_categories.py @@ -4,7 +4,7 @@ A sample application for tagging categories on commands. """ -from cmd2 import Cmd, categorize, __version__, with_argparser +from cmd2 import Cmd, categorize, __version__, with_argparser, with_category import argparse @@ -24,14 +24,14 @@ class HelpCategories(Cmd): """Connect command""" self.poutput('Connect') + # Tag the above command functions under the category Connecting + categorize(do_connect, CMD_CAT_CONNECTING) + + @with_category(CMD_CAT_CONNECTING) def do_which(self, _): """Which command""" self.poutput('Which') - # Tag the above command functions under the category Connecting - categorize(do_connect, CMD_CAT_CONNECTING) - categorize(do_which, CMD_CAT_CONNECTING) - def do_list(self, _): """List command""" self.poutput('List') @@ -58,6 +58,7 @@ class HelpCategories(Cmd): help='Specify when to restart') @with_argparser(restart_parser) + @with_category(CMD_CAT_APP_MGMT) def do_restart(self, _): """Restart command""" self.poutput('Restart') @@ -84,7 +85,6 @@ class HelpCategories(Cmd): do_start, do_sessions, do_redeploy, - do_restart, do_expire, do_undeploy, do_stop, diff --git a/tests/conftest.py b/tests/conftest.py index 4170a5e1..837e7504 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,7 +34,6 @@ set Sets a settable parameter or shows current settings of param shell Execute a command as if at the OS prompt. shortcuts Lists shortcuts (aliases) available. unalias Unsets aliases - """ # Help text for the history command diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 0861c073..75d27869 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -55,7 +55,7 @@ def test_base_argparse_help(base_app, capsys): # Verify that "set -h" gives the same output as "help set" and that it starts in a way that makes sense run_cmd(base_app, 'set -h') out, err = capsys.readouterr() - out1 = normalize(out) + out1 = normalize(str(out)) out2 = run_cmd(base_app, 'help set') @@ -1080,12 +1080,11 @@ class HelpCategoriesApp(cmd2.Cmd): # Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x cmd2.Cmd.__init__(self, *args, **kwargs) + @cmd2.with_category('Some Category') def do_diddly(self, arg): """This command does diddly""" pass - cmd2.categorize(do_diddly, "Some Category") - def do_squat(self, arg): """This docstring help will never be shown because the help_squat method overrides it.""" pass @@ -1138,7 +1137,7 @@ def test_help_cat_verbose(helpcat_app): Custom Category ================================================================================ edit This overrides the edit command and does nothing. -squat This docstring help will never be shown because the help_squat method overrides it. +squat This command does diddly squat... Some Category ================================================================================ |