diff options
author | Todd Leonhardt <todd.leonhardt@gmail.com> | 2018-10-03 08:40:31 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-10-03 08:40:31 -0400 |
commit | fadb8d3d005f36d9945a4680f799bbdf730da67b (patch) | |
tree | b5d6010010ec67a6f98d3ee96a3250c0c92d9772 /cmd2 | |
parent | 924f8a57a73de7da462e654f3937b134ca92dbc7 (diff) | |
parent | 38f9da63fd11c8c32d256e9181ecdf5464754f17 (diff) | |
download | cmd2-git-fadb8d3d005f36d9945a4680f799bbdf730da67b.tar.gz |
Merge pull request #563 from python-cmd2/help_summary
Fixed argparse help summary when no docstring was provided
Diffstat (limited to 'cmd2')
-rw-r--r-- | cmd2/cmd2.py | 117 |
1 files changed, 61 insertions, 56 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index e04138fa..cc4c4bbc 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -120,14 +120,16 @@ except ImportError: # pragma: no cover # optional attribute, when tagged on a function, allows cmd2 to categorize commands HELP_CATEGORY = 'help_category' -HELP_SUMMARY = 'help_summary' INTERNAL_COMMAND_EPILOG = ("Notes:\n" " This command is for internal use and is not intended to be called from the\n" " command line.") # All command functions start with this -COMMAND_PREFIX = 'do_' +COMMAND_FUNC_PREFIX = 'do_' + +# All help functions start with this +HELP_FUNC_PREFIX = 'help_' def categorize(func: Union[Callable, Iterable], category: str) -> None: @@ -211,16 +213,14 @@ def with_argparser_and_unknown_args(argparser: argparse.ArgumentParser, preserve # argparser defaults the program name to sys.argv[0] # we want it to be the name of our command - argparser.prog = func.__name__[3:] + argparser.prog = func.__name__[len(COMMAND_FUNC_PREFIX):] # If the description has not been set, then use the method docstring if one exists 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() + # Set the command's help text as argparser.description (which can be None) + cmd_wrapper.__doc__ = argparser.description # Mark this function as having an argparse ArgumentParser setattr(cmd_wrapper, 'argparser', argparser) @@ -254,16 +254,14 @@ def with_argparser(argparser: argparse.ArgumentParser, preserve_quotes: bool=Fal # argparser defaults the program name to sys.argv[0] # we want it to be the name of our command - argparser.prog = func.__name__[3:] + argparser.prog = func.__name__[len(COMMAND_FUNC_PREFIX):] # If the description has not been set, then use the method docstring if one exists 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() + # Set the command's help text as argparser.description (which can be None) + cmd_wrapper.__doc__ = argparser.description # Mark this function as having an argparse ArgumentParser setattr(cmd_wrapper, 'argparser', argparser) @@ -1606,8 +1604,8 @@ class Cmd(cmd.Cmd): def get_all_commands(self) -> List[str]: """Returns a list of all commands.""" - return [name[len(COMMAND_PREFIX):] for name in self.get_names() - if name.startswith(COMMAND_PREFIX) and callable(getattr(self, name))] + return [name[len(COMMAND_FUNC_PREFIX):] for name in self.get_names() + if name.startswith(COMMAND_FUNC_PREFIX) and callable(getattr(self, name))] def get_visible_commands(self) -> List[str]: """Returns a list of commands that have not been hidden.""" @@ -1637,8 +1635,8 @@ class Cmd(cmd.Cmd): def get_help_topics(self) -> List[str]: """ Returns a list of help topics """ - return [name[5:] for name in self.get_names() - if name.startswith('help_') and callable(getattr(self, name))] + return [name[len(HELP_FUNC_PREFIX):] for name in self.get_names() + if name.startswith(HELP_FUNC_PREFIX) and callable(getattr(self, name))] # noinspection PyUnusedLocal def sigint_handler(self, signum: int, frame) -> None: @@ -1985,7 +1983,7 @@ class Cmd(cmd.Cmd): :param command: command to look up method name which implements it :return: method name which implements the given command """ - target = COMMAND_PREFIX + command + target = COMMAND_FUNC_PREFIX + command return target if callable(getattr(self, target, None)) else '' def onecmd(self, statement: Union[Statement, str]) -> bool: @@ -2635,15 +2633,22 @@ class Cmd(cmd.Cmd): for command in visible_commands: func = self.cmd_func(command) - if command in help_topics or func.__doc__: - if command in help_topics: - help_topics.remove(command) - if hasattr(func, HELP_CATEGORY): - category = getattr(func, HELP_CATEGORY) - cmds_cats.setdefault(category, []) - cmds_cats[category].append(command) - else: - cmds_doc.append(command) + has_help_func = False + + if command in help_topics: + # Prevent the command from showing as both a command and help topic in the output + help_topics.remove(command) + + # Non-argparse commands can have help_functions for their documentation + if not hasattr(func, 'argparser'): + has_help_func = True + + if hasattr(func, HELP_CATEGORY): + category = getattr(func, HELP_CATEGORY) + cmds_cats.setdefault(category, []) + cmds_cats[category].append(command) + elif func.__doc__ or has_help_func: + cmds_doc.append(command) else: cmds_undoc.append(command) @@ -2685,23 +2690,17 @@ class Cmd(cmd.Cmd): if self.ruler: self.stdout.write('{:{ruler}<{width}}\n'.format('', ruler=self.ruler, width=80)) + # Try to get the documentation string for each command + topics = self.get_help_topics() + for command in cmds: - # 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 - func = self.cmd_func(command) - try: - # Now see if help_summary has been set - doc = func.help_summary - except AttributeError: - # Last, try to directly access the function's doc-string - doc = func.__doc__ - else: - # we found the help function + cmd_func = self.cmd_func(command) + + # Non-argparse commands can have help_functions for their documentation + if not hasattr(cmd_func, 'argparser') and command in topics: + help_func = getattr(self, HELP_FUNC_PREFIX + command) result = io.StringIO() + # try to redirect system stdout with redirect_stdout(result): # save our internal stdout @@ -2709,27 +2708,33 @@ class Cmd(cmd.Cmd): try: # redirect our internal stdout self.stdout = result - func() + help_func() finally: # restore internal stdout self.stdout = stdout_orig doc = result.getvalue() + else: + doc = cmd_func.__doc__ + # Attempt to locate the first documentation block - doc_block = [] - found_first = False - for doc_line in doc.splitlines(): - stripped_line = doc_line.strip() - - # Don't include :param type lines - if stripped_line.startswith(':'): - if found_first: + if not doc: + doc_block = [''] + else: + doc_block = [] + found_first = False + for doc_line in doc.splitlines(): + stripped_line = doc_line.strip() + + # Don't include :param type lines + if stripped_line.startswith(':'): + if found_first: + break + elif stripped_line: + doc_block.append(stripped_line) + found_first = True + elif found_first: break - elif stripped_line: - doc_block.append(stripped_line) - found_first = True - elif found_first: - break for doc_line in doc_block: self.stdout.write('{: <{col_width}}{doc}\n'.format(command, @@ -3408,7 +3413,7 @@ a..b, a:b, a:, ..b items by indices (inclusive) @with_argparser(relative_load_parser) def do__relative_load(self, args: argparse.Namespace) -> None: - """""" + """Run commands in script file that is encoded as either ASCII or UTF-8 text""" file_path = args.file_path # NOTE: Relative path is an absolute path, it is just relative to the current script directory relative_path = os.path.join(self._current_script_dir or '', file_path) |