@@ -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 :
@@ -2620,15 +2618,22 @@ def _help_menu(self, verbose: bool=False) -> None:
26202618
26212619 for command in visible_commands :
26222620 func = self .cmd_func (command )
2623- if command in help_topics or func .__doc__ :
2624- if command in help_topics :
2625- help_topics .remove (command )
2626- if hasattr (func , HELP_CATEGORY ):
2627- category = getattr (func , HELP_CATEGORY )
2628- cmds_cats .setdefault (category , [])
2629- cmds_cats [category ].append (command )
2630- else :
2631- cmds_doc .append (command )
2621+ has_help_func = False
2622+
2623+ if command in help_topics :
2624+ # Prevent the command from showing as both a command and help topic in the output
2625+ help_topics .remove (command )
2626+
2627+ # Non-argparse commands can have help_functions for their documentation
2628+ if not hasattr (func , 'argparser' ):
2629+ has_help_func = True
2630+
2631+ if hasattr (func , HELP_CATEGORY ):
2632+ category = getattr (func , HELP_CATEGORY )
2633+ cmds_cats .setdefault (category , [])
2634+ cmds_cats [category ].append (command )
2635+ elif func .__doc__ or has_help_func :
2636+ cmds_doc .append (command )
26322637 else :
26332638 cmds_undoc .append (command )
26342639
@@ -2670,51 +2675,51 @@ def _print_topics(self, header: str, cmds: List[str], verbose: bool) -> None:
26702675 if self .ruler :
26712676 self .stdout .write ('{:{ruler}<{width}}\n ' .format ('' , ruler = self .ruler , width = 80 ))
26722677
2678+ # Try to get the documentation string for each command
2679+ topics = self .get_help_topics ()
2680+
26732681 for command in cmds :
2674- # Try to get the documentation string
2675- try :
2676- # first see if there's a help function implemented
2677- func = getattr (self , 'help_' + command )
2678- except AttributeError :
2679- # Couldn't find a help function
2680- func = self .cmd_func (command )
2681- try :
2682- # Now see if help_summary has been set
2683- doc = func .help_summary
2684- except AttributeError :
2685- # Last, try to directly access the function's doc-string
2686- doc = func .__doc__
2687- else :
2688- # we found the help function
2682+ cmd_func = self .cmd_func (command )
2683+
2684+ # Non-argparse commands can have help_functions for their documentation
2685+ if not hasattr (cmd_func , 'argparser' ) and command in topics :
2686+ help_func = getattr (self , HELP_FUNC_PREFIX + command )
26892687 result = io .StringIO ()
2688+
26902689 # try to redirect system stdout
26912690 with redirect_stdout (result ):
26922691 # save our internal stdout
26932692 stdout_orig = self .stdout
26942693 try :
26952694 # redirect our internal stdout
26962695 self .stdout = result
2697- func ()
2696+ help_func ()
26982697 finally :
26992698 # restore internal stdout
27002699 self .stdout = stdout_orig
27012700 doc = result .getvalue ()
27022701
2702+ else :
2703+ doc = cmd_func .__doc__
2704+
27032705 # Attempt to locate the first documentation block
2704- doc_block = []
2705- found_first = False
2706- for doc_line in doc .splitlines ():
2707- stripped_line = doc_line .strip ()
2708-
2709- # Don't include :param type lines
2710- if stripped_line .startswith (':' ):
2711- if found_first :
2706+ if not doc :
2707+ doc_block = ['' ]
2708+ else :
2709+ doc_block = []
2710+ found_first = False
2711+ for doc_line in doc .splitlines ():
2712+ stripped_line = doc_line .strip ()
2713+
2714+ # Don't include :param type lines
2715+ if stripped_line .startswith (':' ):
2716+ if found_first :
2717+ break
2718+ elif stripped_line :
2719+ doc_block .append (stripped_line )
2720+ found_first = True
2721+ elif found_first :
27122722 break
2713- elif stripped_line :
2714- doc_block .append (stripped_line )
2715- found_first = True
2716- elif found_first :
2717- break
27182723
27192724 for doc_line in doc_block :
27202725 self .stdout .write ('{: <{col_width}}{doc}\n ' .format (command ,
@@ -3404,7 +3409,7 @@ def do_load(self, args: argparse.Namespace) -> None:
34043409
34053410 @with_argparser (relative_load_parser )
34063411 def do__relative_load (self , args : argparse .Namespace ) -> None :
3407- """"""
3412+ """Run commands in script file that is encoded as either ASCII or UTF-8 text """
34083413 file_path = args .file_path
34093414 # NOTE: Relative path is an absolute path, it is just relative to the current script directory
34103415 relative_path = os .path .join (self ._current_script_dir or '' , file_path )
0 commit comments