@@ -120,14 +120,16 @@ def __subclasshook__(cls, C):
120120
121121# optional attribute, when tagged on a function, allows cmd2 to categorize commands
122122HELP_CATEGORY = 'help_category'
123- HELP_SUMMARY = 'help_summary'
124123
125124INTERNAL_COMMAND_EPILOG = ("Notes:\n "
126125 " This command is for internal use and is not intended to be called from the\n "
127126 " command line." )
128127
129128# All command functions start with this
130- COMMAND_PREFIX = 'do_'
129+ COMMAND_FUNC_PREFIX = 'do_'
130+
131+ # All help functions start with this
132+ HELP_FUNC_PREFIX = 'help_'
131133
132134
133135def categorize (func : Union [Callable , Iterable ], category : str ) -> None :
@@ -211,16 +213,14 @@ def cmd_wrapper(instance, cmdline):
211213
212214 # argparser defaults the program name to sys.argv[0]
213215 # we want it to be the name of our command
214- argparser .prog = func .__name__ [3 :]
216+ argparser .prog = func .__name__ [len ( COMMAND_FUNC_PREFIX ) :]
215217
216218 # If the description has not been set, then use the method docstring if one exists
217219 if argparser .description is None and func .__doc__ :
218220 argparser .description = func .__doc__
219221
220- if func .__doc__ :
221- setattr (cmd_wrapper , HELP_SUMMARY , func .__doc__ )
222-
223- cmd_wrapper .__doc__ = argparser .format_help ()
222+ # Set the command's help text as argparser.description (which can be None)
223+ cmd_wrapper .__doc__ = argparser .description
224224
225225 # Mark this function as having an argparse ArgumentParser
226226 setattr (cmd_wrapper , 'argparser' , argparser )
@@ -254,16 +254,14 @@ def cmd_wrapper(instance, cmdline):
254254
255255 # argparser defaults the program name to sys.argv[0]
256256 # we want it to be the name of our command
257- argparser .prog = func .__name__ [3 :]
257+ argparser .prog = func .__name__ [len ( COMMAND_FUNC_PREFIX ) :]
258258
259259 # If the description has not been set, then use the method docstring if one exists
260260 if argparser .description is None and func .__doc__ :
261261 argparser .description = func .__doc__
262262
263- if func .__doc__ :
264- setattr (cmd_wrapper , HELP_SUMMARY , func .__doc__ )
265-
266- cmd_wrapper .__doc__ = argparser .format_help ()
263+ # Set the command's help text as argparser.description (which can be None)
264+ cmd_wrapper .__doc__ = argparser .description
267265
268266 # Mark this function as having an argparse ArgumentParser
269267 setattr (cmd_wrapper , 'argparser' , argparser )
@@ -1606,8 +1604,8 @@ def _autocomplete_default(self, text: str, line: str, begidx: int, endidx: int,
16061604
16071605 def get_all_commands (self ) -> List [str ]:
16081606 """Returns a list of all commands."""
1609- return [name [len (COMMAND_PREFIX ):] for name in self .get_names ()
1610- if name .startswith (COMMAND_PREFIX ) and callable (getattr (self , name ))]
1607+ return [name [len (COMMAND_FUNC_PREFIX ):] for name in self .get_names ()
1608+ if name .startswith (COMMAND_FUNC_PREFIX ) and callable (getattr (self , name ))]
16111609
16121610 def get_visible_commands (self ) -> List [str ]:
16131611 """Returns a list of commands that have not been hidden."""
@@ -1637,8 +1635,8 @@ def get_commands_aliases_and_macros_for_completion(self) -> List[str]:
16371635
16381636 def get_help_topics (self ) -> List [str ]:
16391637 """ Returns a list of help topics """
1640- return [name [5 :] for name in self .get_names ()
1641- if name .startswith ('help_' ) and callable (getattr (self , name ))]
1638+ return [name [len ( HELP_FUNC_PREFIX ) :] for name in self .get_names ()
1639+ if name .startswith (HELP_FUNC_PREFIX ) and callable (getattr (self , name ))]
16421640
16431641 # noinspection PyUnusedLocal
16441642 def sigint_handler (self , signum : int , frame ) -> None :
@@ -1985,7 +1983,7 @@ def cmd_func_name(self, command: str) -> str:
19851983 :param command: command to look up method name which implements it
19861984 :return: method name which implements the given command
19871985 """
1988- target = COMMAND_PREFIX + command
1986+ target = COMMAND_FUNC_PREFIX + command
19891987 return target if callable (getattr (self , target , None )) else ''
19901988
19911989 def onecmd (self , statement : Union [Statement , str ]) -> bool :
@@ -2635,15 +2633,22 @@ def _help_menu(self, verbose: bool=False) -> None:
26352633
26362634 for command in visible_commands :
26372635 func = self .cmd_func (command )
2638- if command in help_topics or func .__doc__ :
2639- if command in help_topics :
2640- help_topics .remove (command )
2641- if hasattr (func , HELP_CATEGORY ):
2642- category = getattr (func , HELP_CATEGORY )
2643- cmds_cats .setdefault (category , [])
2644- cmds_cats [category ].append (command )
2645- else :
2646- cmds_doc .append (command )
2636+ has_help_func = False
2637+
2638+ if command in help_topics :
2639+ # Prevent the command from showing as both a command and help topic in the output
2640+ help_topics .remove (command )
2641+
2642+ # Non-argparse commands can have help_functions for their documentation
2643+ if not hasattr (func , 'argparser' ):
2644+ has_help_func = True
2645+
2646+ if hasattr (func , HELP_CATEGORY ):
2647+ category = getattr (func , HELP_CATEGORY )
2648+ cmds_cats .setdefault (category , [])
2649+ cmds_cats [category ].append (command )
2650+ elif func .__doc__ or has_help_func :
2651+ cmds_doc .append (command )
26472652 else :
26482653 cmds_undoc .append (command )
26492654
@@ -2685,51 +2690,51 @@ def _print_topics(self, header: str, cmds: List[str], verbose: bool) -> None:
26852690 if self .ruler :
26862691 self .stdout .write ('{:{ruler}<{width}}\n ' .format ('' , ruler = self .ruler , width = 80 ))
26872692
2693+ # Try to get the documentation string for each command
2694+ topics = self .get_help_topics ()
2695+
26882696 for command in cmds :
2689- # Try to get the documentation string
2690- try :
2691- # first see if there's a help function implemented
2692- func = getattr (self , 'help_' + command )
2693- except AttributeError :
2694- # Couldn't find a help function
2695- func = self .cmd_func (command )
2696- try :
2697- # Now see if help_summary has been set
2698- doc = func .help_summary
2699- except AttributeError :
2700- # Last, try to directly access the function's doc-string
2701- doc = func .__doc__
2702- else :
2703- # we found the help function
2697+ cmd_func = self .cmd_func (command )
2698+
2699+ # Non-argparse commands can have help_functions for their documentation
2700+ if not hasattr (cmd_func , 'argparser' ) and command in topics :
2701+ help_func = getattr (self , HELP_FUNC_PREFIX + command )
27042702 result = io .StringIO ()
2703+
27052704 # try to redirect system stdout
27062705 with redirect_stdout (result ):
27072706 # save our internal stdout
27082707 stdout_orig = self .stdout
27092708 try :
27102709 # redirect our internal stdout
27112710 self .stdout = result
2712- func ()
2711+ help_func ()
27132712 finally :
27142713 # restore internal stdout
27152714 self .stdout = stdout_orig
27162715 doc = result .getvalue ()
27172716
2717+ else :
2718+ doc = cmd_func .__doc__
2719+
27182720 # Attempt to locate the first documentation block
2719- doc_block = []
2720- found_first = False
2721- for doc_line in doc .splitlines ():
2722- stripped_line = doc_line .strip ()
2723-
2724- # Don't include :param type lines
2725- if stripped_line .startswith (':' ):
2726- if found_first :
2721+ if not doc :
2722+ doc_block = ['' ]
2723+ else :
2724+ doc_block = []
2725+ found_first = False
2726+ for doc_line in doc .splitlines ():
2727+ stripped_line = doc_line .strip ()
2728+
2729+ # Don't include :param type lines
2730+ if stripped_line .startswith (':' ):
2731+ if found_first :
2732+ break
2733+ elif stripped_line :
2734+ doc_block .append (stripped_line )
2735+ found_first = True
2736+ elif found_first :
27272737 break
2728- elif stripped_line :
2729- doc_block .append (stripped_line )
2730- found_first = True
2731- elif found_first :
2732- break
27332738
27342739 for doc_line in doc_block :
27352740 self .stdout .write ('{: <{col_width}}{doc}\n ' .format (command ,
@@ -3408,7 +3413,7 @@ def do_load(self, args: argparse.Namespace) -> None:
34083413
34093414 @with_argparser (relative_load_parser )
34103415 def do__relative_load (self , args : argparse .Namespace ) -> None :
3411- """"""
3416+ """Run commands in script file that is encoded as either ASCII or UTF-8 text """
34123417 file_path = args .file_path
34133418 # NOTE: Relative path is an absolute path, it is just relative to the current script directory
34143419 relative_path = os .path .join (self ._current_script_dir or '' , file_path )
0 commit comments