6060 IO ,
6161 TYPE_CHECKING ,
6262 Any ,
63+ ClassVar ,
6364 TextIO ,
6465 TypeVar ,
6566 Union ,
113114 get_paste_buffer ,
114115 write_to_paste_buffer ,
115116)
116- from .command_definition import (
117+ from .command_set import (
117118 CommandFunc ,
118119 CommandSet ,
119120)
123124 Completions ,
124125)
125126from .constants import (
126- CMDSET_ATTR_DEFAULT_HELP_CATEGORY ,
127127 COMMAND_FUNC_PREFIX ,
128128 COMPLETER_FUNC_PREFIX ,
129129 HELP_FUNC_PREFIX ,
@@ -328,13 +328,24 @@ class Cmd:
328328 Line-oriented command interpreters are often useful for test harnesses, internal tools, and rapid prototypes.
329329 """
330330
331- DEFAULT_COMPLETEKEY = 'tab'
332- DEFAULT_EDITOR = utils .find_editor ()
333- DEFAULT_PROMPT = '(Cmd) '
331+ DEFAULT_COMPLETEKEY : ClassVar [str ] = "tab"
332+ DEFAULT_EDITOR : ClassVar [str | None ] = utils .find_editor ()
333+ DEFAULT_PROMPT : ClassVar [str ] = "(Cmd) "
334+
335+ # Default category for commands defined in this class which have
336+ # not been explicitly categorized with the @with_category decorator.
337+ # This value is inherited by subclasses but they can set their own
338+ # DEFAULT_CATEGORY to place their commands into a custom category.
339+ # Subclasses can also reassign cmd2.Cmd.DEFAULT_CATEGORY to rename
340+ # the category used for the framework's built-in commands.
341+ DEFAULT_CATEGORY : ClassVar [str ] = "Cmd2 Commands"
342+
343+ # Header for table listing help topics not related to a command.
344+ MISC_HEADER : ClassVar [str ] = "Miscellaneous Help Topics"
334345
335346 def __init__ (
336347 self ,
337- completekey : str = DEFAULT_COMPLETEKEY ,
348+ completekey : str | None = None ,
338349 stdin : TextIO | None = None ,
339350 stdout : TextIO | None = None ,
340351 * ,
@@ -359,7 +370,7 @@ def __init__(
359370 ) -> None :
360371 """Easy but powerful framework for writing line-oriented command interpreters, extends Python's cmd package.
361372
362- :param completekey: name of a completion key, default to Tab
373+ :param completekey: name of a completion key, default to 'tab'. (If None or an empty string, 'tab' is used)
363374 :param stdin: alternate input file object, if not specified, sys.stdin is used
364375 :param stdout: alternate output file object, if not specified, sys.stdout is used
365376 :param allow_cli_args: if ``True``, then [cmd2.Cmd.__init__][] will process command
@@ -416,9 +427,12 @@ def __init__(
416427 self ._initialize_plugin_system ()
417428
418429 # Configure a few defaults
419- self .prompt : str = Cmd .DEFAULT_PROMPT
430+ self .prompt : str = self .DEFAULT_PROMPT
420431 self .intro = intro
421432
433+ if not completekey :
434+ completekey = self .DEFAULT_COMPLETEKEY
435+
422436 # What to use for standard input
423437 if stdin is not None :
424438 self .stdin = stdin
@@ -446,7 +460,7 @@ def __init__(
446460 self .always_show_hint = False
447461 self .debug = False
448462 self .echo = False
449- self .editor = Cmd .DEFAULT_EDITOR
463+ self .editor = self .DEFAULT_EDITOR
450464 self .feedback_to_output = False # Do not include nonessentials in >, | output by default (things like timing)
451465 self .quiet = False # Do not suppress nonessential output
452466 self .scripts_add_to_history = True # Scripts and pyscripts add commands to history
@@ -537,19 +551,6 @@ def __init__(
537551 # Set text which prints right before all of the help tables are listed.
538552 self .doc_leader = ""
539553
540- # Set header for table listing documented commands.
541- self .doc_header = "Documented Commands"
542-
543- # Set header for table listing help topics not related to a command.
544- self .misc_header = "Miscellaneous Help Topics"
545-
546- # Set header for table listing commands that have no help info.
547- self .undoc_header = "Undocumented Commands"
548-
549- # If any command has been categorized, then all other documented commands that
550- # haven't been categorized will display under this section in the help output.
551- self .default_category = "Uncategorized Commands"
552-
553554 # The error that prints when no help information can be found
554555 self .help_error = "No help on {}"
555556
@@ -840,8 +841,6 @@ def register_command_set(self, cmdset: CommandSet) -> None:
840841 ),
841842 )
842843
843- default_category = getattr (cmdset , CMDSET_ATTR_DEFAULT_HELP_CATEGORY , None )
844-
845844 installed_attributes = []
846845 try :
847846 for cmd_func_name , command_method in methods :
@@ -864,11 +863,8 @@ def register_command_set(self, cmdset: CommandSet) -> None:
864863
865864 self ._cmd_to_command_sets [command ] = cmdset
866865
867- if default_category and not hasattr (command_method , constants .CMD_ATTR_HELP_CATEGORY ):
868- utils .categorize (command_method , default_category )
869-
870866 # If this command is in a disabled category, then disable it
871- command_category = getattr (command_method , constants . CMD_ATTR_HELP_CATEGORY , None )
867+ command_category = self . _get_command_category (command_method )
872868 if command_category in self .disabled_categories :
873869 message_to_print = self .disabled_categories [command_category ]
874870 self .disable_command (command , message_to_print )
@@ -3338,6 +3334,23 @@ def cmd_func(self, command: str) -> CommandFunc | None:
33383334 func = getattr (self , func_name , None )
33393335 return cast (CommandFunc , func ) if callable (func ) else None
33403336
3337+ def _get_command_category (self , func : CommandFunc ) -> str :
3338+ """Determine the category for a command.
3339+
3340+ :param func: the do_* function implementing the command
3341+ :return: category name
3342+ """
3343+ # Check if the command function has a category.
3344+ if hasattr (func , constants .CMD_ATTR_HELP_CATEGORY ):
3345+ category : str = getattr (func , constants .CMD_ATTR_HELP_CATEGORY )
3346+
3347+ # Otherwise get the category from its defining class.
3348+ else :
3349+ defining_cls = get_defining_class (func )
3350+ category = getattr (defining_cls , 'DEFAULT_CATEGORY' , self .DEFAULT_CATEGORY )
3351+
3352+ return category
3353+
33413354 def onecmd (self , statement : Statement | str , * , add_to_history : bool = True ) -> bool :
33423355 """Execute the actual do_* method for a command.
33433356
@@ -4214,44 +4227,31 @@ def complete_help_subcommands(
42144227 completer = argparse_completer .DEFAULT_AP_COMPLETER (argparser , self )
42154228 return completer .complete_subcommand_help (text , line , begidx , endidx , arg_tokens ['subcommands' ])
42164229
4217- def _build_command_info (self ) -> tuple [dict [str , list [str ]], list [str ], list [ str ], list [ str ] ]:
4230+ def _build_command_info (self ) -> tuple [dict [str , list [str ]], list [str ]]:
42184231 """Categorizes and sorts visible commands and help topics for display.
42194232
42204233 :return: tuple containing:
42214234 - dictionary mapping category names to lists of command names
4222- - list of documented command names
4223- - list of undocumented command names
42244235 - list of help topic names that are not also commands
42254236 """
42264237 # Get a sorted list of help topics
42274238 help_topics = sorted (self .get_help_topics (), key = utils .DEFAULT_STR_SORT_KEY )
42284239
42294240 # Get a sorted list of visible command names
42304241 visible_commands = sorted (self .get_visible_commands (), key = utils .DEFAULT_STR_SORT_KEY )
4231- cmds_doc : list [str ] = []
4232- cmds_undoc : list [str ] = []
42334242 cmds_cats : dict [str , list [str ]] = {}
4234- for command in visible_commands :
4235- func = cast (CommandFunc , self .cmd_func (command ))
4236- has_help_func = False
4237- has_parser = func in self ._command_parsers
42384243
4244+ for command in visible_commands :
4245+ # Prevent the command from showing as both a command and help topic in the output
42394246 if command in help_topics :
4240- # Prevent the command from showing as both a command and help topic in the output
42414247 help_topics .remove (command )
42424248
4243- # Non-argparse commands can have help_functions for their documentation
4244- has_help_func = not has_parser
4249+ # Store the command within its category
4250+ func = cast (CommandFunc , self .cmd_func (command ))
4251+ category = self ._get_command_category (func )
4252+ cmds_cats .setdefault (category , []).append (command )
42454253
4246- if hasattr (func , constants .CMD_ATTR_HELP_CATEGORY ):
4247- category : str = getattr (func , constants .CMD_ATTR_HELP_CATEGORY )
4248- cmds_cats .setdefault (category , [])
4249- cmds_cats [category ].append (command )
4250- elif func .__doc__ or has_help_func or has_parser :
4251- cmds_doc .append (command )
4252- else :
4253- cmds_undoc .append (command )
4254- return cmds_cats , cmds_doc , cmds_undoc , help_topics
4254+ return cmds_cats , help_topics
42554255
42564256 @classmethod
42574257 def _build_help_parser (cls ) -> Cmd2ArgumentParser :
@@ -4284,36 +4284,32 @@ def do_help(self, args: argparse.Namespace) -> None:
42844284 self .last_result = True
42854285
42864286 if not args .command or args .verbose :
4287- cmds_cats , cmds_doc , cmds_undoc , help_topics = self ._build_command_info ()
4287+ cmds_cats , help_topics = self ._build_command_info ()
42884288
42894289 if self .doc_leader :
42904290 self .poutput ()
42914291 self .poutput (Text (self .doc_leader , style = Cmd2Style .HELP_LEADER ))
42924292 self .poutput ()
42934293
4294- # Print any categories first and then the remaining documented commands.
4295- sorted_categories = sorted (cmds_cats .keys (), key = utils .DEFAULT_STR_SORT_KEY )
4296- all_cmds = {category : cmds_cats [category ] for category in sorted_categories }
4297- if all_cmds :
4298- all_cmds [self .default_category ] = cmds_doc
4299- else :
4300- all_cmds [self .doc_header ] = cmds_doc
4301-
43024294 # Used to provide verbose table separation for better readability.
43034295 previous_table_printed = False
43044296
4297+ # Print commands grouped by category
4298+ sorted_categories = sorted (cmds_cats .keys (), key = utils .DEFAULT_STR_SORT_KEY )
4299+ all_cmds = {category : cmds_cats [category ] for category in sorted_categories }
4300+
43054301 for category , commands in all_cmds .items ():
43064302 if previous_table_printed :
43074303 self .poutput ()
43084304
43094305 self ._print_documented_command_topics (category , commands , args .verbose )
43104306 previous_table_printed = bool (commands ) and args .verbose
43114307
4312- if previous_table_printed and ( help_topics or cmds_undoc ) :
4308+ if previous_table_printed and help_topics :
43134309 self .poutput ()
43144310
4315- self . print_topics ( self . misc_header , help_topics , 15 , 80 )
4316- self .print_topics (self .undoc_header , cmds_undoc , 15 , 80 )
4311+ # Print help topics table
4312+ self .print_topics (self .MISC_HEADER , help_topics , 15 , 80 )
43174313
43184314 else :
43194315 # Getting help for a specific command
@@ -5620,7 +5616,7 @@ def enable_category(self, category: str) -> None:
56205616
56215617 for cmd_name in list (self .disabled_commands ):
56225618 func = self .disabled_commands [cmd_name ].command_function
5623- if getattr (func , constants . CMD_ATTR_HELP_CATEGORY , None ) == category :
5619+ if self . _get_command_category (func ) == category :
56245620 self .enable_command (cmd_name )
56255621
56265622 del self .disabled_categories [category ]
@@ -5681,8 +5677,8 @@ def disable_category(self, category: str, message_to_print: str) -> None:
56815677 all_commands = self .get_all_commands ()
56825678
56835679 for cmd_name in all_commands :
5684- func = self .cmd_func (cmd_name )
5685- if getattr (func , constants . CMD_ATTR_HELP_CATEGORY , None ) == category :
5680+ func = cast ( CommandFunc , self .cmd_func (cmd_name ) )
5681+ if self . _get_command_category (func ) == category :
56865682 self .disable_command (cmd_name , message_to_print )
56875683
56885684 self .disabled_categories [category ] = message_to_print
0 commit comments