@@ -475,15 +475,22 @@ def __init__(
475
475
# The multiline command currently being typed which is used to tab complete multiline commands.
476
476
self ._multiline_in_progress = ''
477
477
478
- # Set the header used for the help function's listing of documented functions
479
- self .doc_header = "Documented commands (use 'help -v' for verbose/'help <topic>' for details)"
478
+ # Set text which prints right before all of the help topics are listed.
479
+ self .doc_leader = ""
480
+
481
+ # Set header for table listing documented commands.
482
+ self .doc_header = "Documented Commands"
480
483
481
484
# Set header for table listing help topics not related to a command.
482
485
self .misc_header = "Miscellaneous Help Topics"
483
486
484
487
# Set header for table listing commands that have no help info.
485
488
self .undoc_header = "Undocumented Commands"
486
489
490
+ # If any command has been categorized, then all other commands that haven't been categorized
491
+ # will display under this section in the help output.
492
+ self .default_category = "Uncategorized Commands"
493
+
487
494
# The error that prints when no help information can be found
488
495
self .help_error = "No help on {}"
489
496
@@ -551,10 +558,6 @@ def __init__(
551
558
# values are DisabledCommand objects.
552
559
self .disabled_commands : dict [str , DisabledCommand ] = {}
553
560
554
- # If any command has been categorized, then all other commands that haven't been categorized
555
- # will display under this section in the help output.
556
- self .default_category = 'Uncategorized'
557
-
558
561
# The default key for sorting string results. Its default value performs a case-insensitive alphabetical sort.
559
562
# If natural sorting is preferred, then set this to NATURAL_SORT_KEY.
560
563
# cmd2 uses this key for sorting:
@@ -4039,6 +4042,37 @@ def complete_help_subcommands(
4039
4042
completer = argparse_completer .DEFAULT_AP_COMPLETER (argparser , self )
4040
4043
return completer .complete_subcommand_help (text , line , begidx , endidx , arg_tokens ['subcommands' ])
4041
4044
4045
+ def _build_command_info (self ) -> tuple [dict [str , list [str ]], list [str ], list [str ], list [str ]]:
4046
+ # Get a sorted list of help topics
4047
+ help_topics = sorted (self .get_help_topics (), key = self .default_sort_key )
4048
+
4049
+ # Get a sorted list of visible command names
4050
+ visible_commands = sorted (self .get_visible_commands (), key = self .default_sort_key )
4051
+ cmds_doc : list [str ] = []
4052
+ cmds_undoc : list [str ] = []
4053
+ cmds_cats : dict [str , list [str ]] = {}
4054
+ for command in visible_commands :
4055
+ func = cast (CommandFunc , self .cmd_func (command ))
4056
+ has_help_func = False
4057
+ has_parser = func in self ._command_parsers
4058
+
4059
+ if command in help_topics :
4060
+ # Prevent the command from showing as both a command and help topic in the output
4061
+ help_topics .remove (command )
4062
+
4063
+ # Non-argparse commands can have help_functions for their documentation
4064
+ has_help_func = not has_parser
4065
+
4066
+ if hasattr (func , constants .CMD_ATTR_HELP_CATEGORY ):
4067
+ category : str = getattr (func , constants .CMD_ATTR_HELP_CATEGORY )
4068
+ cmds_cats .setdefault (category , [])
4069
+ cmds_cats [category ].append (command )
4070
+ elif func .__doc__ or has_help_func or has_parser :
4071
+ cmds_doc .append (command )
4072
+ else :
4073
+ cmds_undoc .append (command )
4074
+ return cmds_cats , cmds_doc , cmds_undoc , help_topics
4075
+
4042
4076
@classmethod
4043
4077
def _build_help_parser (cls ) -> Cmd2ArgumentParser :
4044
4078
help_parser = argparse_custom .DEFAULT_ARGUMENT_PARSER (
@@ -4074,7 +4108,24 @@ def do_help(self, args: argparse.Namespace) -> None:
4074
4108
self .last_result = True
4075
4109
4076
4110
if not args .command or args .verbose :
4077
- self ._help_menu (args .verbose )
4111
+ cmds_cats , cmds_doc , cmds_undoc , help_topics = self ._build_command_info ()
4112
+
4113
+ if self .doc_leader :
4114
+ self .poutput ()
4115
+ self .poutput (self .doc_leader , style = Cmd2Style .HELP_LEADER , soft_wrap = False )
4116
+ self .poutput ()
4117
+
4118
+ if not cmds_cats :
4119
+ # No categories found, fall back to standard behavior
4120
+ self ._print_documented_command_topics (self .doc_header , cmds_doc , args .verbose )
4121
+ else :
4122
+ # Categories found, Organize all commands by category
4123
+ for category in sorted (cmds_cats .keys (), key = self .default_sort_key ):
4124
+ self ._print_documented_command_topics (category , cmds_cats [category ], args .verbose )
4125
+ self ._print_documented_command_topics (self .default_category , cmds_doc , args .verbose )
4126
+
4127
+ self .print_topics (self .misc_header , help_topics , 15 , 80 )
4128
+ self .print_topics (self .undoc_header , cmds_undoc , 15 , 80 )
4078
4129
4079
4130
else :
4080
4131
# Getting help for a specific command
@@ -4113,128 +4164,23 @@ def print_topics(self, header: str, cmds: list[str] | None, cmdlen: int, maxcol:
4113
4164
"""
4114
4165
if cmds :
4115
4166
header_grid = Table .grid ()
4116
- header_grid .add_row (header , style = Cmd2Style .HELP_TITLE )
4167
+ header_grid .add_row (header , style = Cmd2Style .HELP_HEADER )
4117
4168
if self .ruler :
4118
4169
header_grid .add_row (Rule (characters = self .ruler ))
4119
4170
self .poutput (header_grid )
4120
4171
self .columnize (cmds , maxcol - 1 )
4121
4172
self .poutput ()
4122
4173
4123
- def columnize (self , str_list : list [str ] | None , display_width : int = 80 ) -> None :
4124
- """Display a list of single-line strings as a compact set of columns.
4125
-
4126
- Override of cmd's columnize() to handle strings with ANSI style sequences and wide characters.
4127
-
4128
- Each column is only as wide as necessary.
4129
- Columns are separated by two spaces (one was not legible enough).
4130
- """
4131
- if not str_list :
4132
- self .poutput ("<empty>" )
4133
- return
4134
-
4135
- nonstrings = [i for i in range (len (str_list )) if not isinstance (str_list [i ], str )]
4136
- if nonstrings :
4137
- raise TypeError (f"str_list[i] not a string for i in { nonstrings } " )
4138
- size = len (str_list )
4139
- if size == 1 :
4140
- self .poutput (str_list [0 ])
4141
- return
4142
- # Try every row count from 1 upwards
4143
- for nrows in range (1 , len (str_list )):
4144
- ncols = (size + nrows - 1 ) // nrows
4145
- colwidths = []
4146
- totwidth = - 2
4147
- for col in range (ncols ):
4148
- colwidth = 0
4149
- for row in range (nrows ):
4150
- i = row + nrows * col
4151
- if i >= size :
4152
- break
4153
- x = str_list [i ]
4154
- colwidth = max (colwidth , su .str_width (x ))
4155
- colwidths .append (colwidth )
4156
- totwidth += colwidth + 2
4157
- if totwidth > display_width :
4158
- break
4159
- if totwidth <= display_width :
4160
- break
4161
- else :
4162
- # The output is wider than display_width. Print 1 column with each string on its own row.
4163
- nrows = len (str_list )
4164
- ncols = 1
4165
- colwidths = [1 ]
4166
- for row in range (nrows ):
4167
- texts = []
4168
- for col in range (ncols ):
4169
- i = row + nrows * col
4170
- x = "" if i >= size else str_list [i ]
4171
- texts .append (x )
4172
- while texts and not texts [- 1 ]:
4173
- del texts [- 1 ]
4174
- for col in range (len (texts )):
4175
- texts [col ] = su .align_left (texts [col ], width = colwidths [col ])
4176
- self .poutput (" " .join (texts ))
4177
-
4178
- def _help_menu (self , verbose : bool = False ) -> None :
4179
- """Show a list of commands which help can be displayed for."""
4180
- cmds_cats , cmds_doc , cmds_undoc , help_topics = self ._build_command_info ()
4181
-
4182
- if not cmds_cats :
4183
- # No categories found, fall back to standard behavior
4184
- self .poutput (self .doc_leader , soft_wrap = False )
4185
- self ._print_topics (self .doc_header , cmds_doc , verbose )
4186
- else :
4187
- # Categories found, Organize all commands by category
4188
- self .poutput (self .doc_leader , style = Cmd2Style .HELP_HEADER , soft_wrap = False )
4189
- self .poutput (self .doc_header , style = Cmd2Style .HELP_HEADER , end = "\n \n " , soft_wrap = False )
4190
- for category in sorted (cmds_cats .keys (), key = self .default_sort_key ):
4191
- self ._print_topics (category , cmds_cats [category ], verbose )
4192
- self ._print_topics (self .default_category , cmds_doc , verbose )
4193
-
4194
- self .print_topics (self .misc_header , help_topics , 15 , 80 )
4195
- self .print_topics (self .undoc_header , cmds_undoc , 15 , 80 )
4196
-
4197
- def _build_command_info (self ) -> tuple [dict [str , list [str ]], list [str ], list [str ], list [str ]]:
4198
- # Get a sorted list of help topics
4199
- help_topics = sorted (self .get_help_topics (), key = self .default_sort_key )
4200
-
4201
- # Get a sorted list of visible command names
4202
- visible_commands = sorted (self .get_visible_commands (), key = self .default_sort_key )
4203
- cmds_doc : list [str ] = []
4204
- cmds_undoc : list [str ] = []
4205
- cmds_cats : dict [str , list [str ]] = {}
4206
- for command in visible_commands :
4207
- func = cast (CommandFunc , self .cmd_func (command ))
4208
- has_help_func = False
4209
- has_parser = func in self ._command_parsers
4210
-
4211
- if command in help_topics :
4212
- # Prevent the command from showing as both a command and help topic in the output
4213
- help_topics .remove (command )
4214
-
4215
- # Non-argparse commands can have help_functions for their documentation
4216
- has_help_func = not has_parser
4217
-
4218
- if hasattr (func , constants .CMD_ATTR_HELP_CATEGORY ):
4219
- category : str = getattr (func , constants .CMD_ATTR_HELP_CATEGORY )
4220
- cmds_cats .setdefault (category , [])
4221
- cmds_cats [category ].append (command )
4222
- elif func .__doc__ or has_help_func or has_parser :
4223
- cmds_doc .append (command )
4224
- else :
4225
- cmds_undoc .append (command )
4226
- return cmds_cats , cmds_doc , cmds_undoc , help_topics
4227
-
4228
- def _print_topics (self , header : str , cmds : list [str ], verbose : bool ) -> None :
4229
- """Print topics, switching between verbose or traditional output."""
4174
+ def _print_documented_command_topics (self , header : str , cmds : list [str ], verbose : bool ) -> None :
4175
+ """Print topics which are documented commands, switching between verbose or traditional output."""
4230
4176
import io
4231
4177
4232
4178
if cmds :
4233
4179
if not verbose :
4234
4180
self .print_topics (header , cmds , 15 , 80 )
4235
4181
else :
4236
4182
category_grid = Table .grid ()
4237
- category_grid .add_row (header , style = Cmd2Style .HELP_TITLE )
4183
+ category_grid .add_row (header , style = Cmd2Style .HELP_HEADER )
4238
4184
category_grid .add_row (Rule (characters = self .ruler ))
4239
4185
topics_table = Table (
4240
4186
Column ("Name" , no_wrap = True ),
@@ -4283,6 +4229,58 @@ def _print_topics(self, header: str, cmds: list[str], verbose: bool) -> None:
4283
4229
category_grid .add_row (topics_table )
4284
4230
self .poutput (category_grid , "" )
4285
4231
4232
+ def columnize (self , str_list : list [str ] | None , display_width : int = 80 ) -> None :
4233
+ """Display a list of single-line strings as a compact set of columns.
4234
+
4235
+ Override of cmd's columnize() to handle strings with ANSI style sequences and wide characters.
4236
+
4237
+ Each column is only as wide as necessary.
4238
+ Columns are separated by two spaces (one was not legible enough).
4239
+ """
4240
+ if not str_list :
4241
+ self .poutput ("<empty>" )
4242
+ return
4243
+
4244
+ size = len (str_list )
4245
+ if size == 1 :
4246
+ self .poutput (str_list [0 ])
4247
+ return
4248
+ # Try every row count from 1 upwards
4249
+ for nrows in range (1 , len (str_list )):
4250
+ ncols = (size + nrows - 1 ) // nrows
4251
+ colwidths = []
4252
+ totwidth = - 2
4253
+ for col in range (ncols ):
4254
+ colwidth = 0
4255
+ for row in range (nrows ):
4256
+ i = row + nrows * col
4257
+ if i >= size :
4258
+ break
4259
+ x = str_list [i ]
4260
+ colwidth = max (colwidth , su .str_width (x ))
4261
+ colwidths .append (colwidth )
4262
+ totwidth += colwidth + 2
4263
+ if totwidth > display_width :
4264
+ break
4265
+ if totwidth <= display_width :
4266
+ break
4267
+ else :
4268
+ # The output is wider than display_width. Print 1 column with each string on its own row.
4269
+ nrows = len (str_list )
4270
+ ncols = 1
4271
+ colwidths = [1 ]
4272
+ for row in range (nrows ):
4273
+ texts = []
4274
+ for col in range (ncols ):
4275
+ i = row + nrows * col
4276
+ x = "" if i >= size else str_list [i ]
4277
+ texts .append (x )
4278
+ while texts and not texts [- 1 ]:
4279
+ del texts [- 1 ]
4280
+ for col in range (len (texts )):
4281
+ texts [col ] = su .align_left (texts [col ], width = colwidths [col ])
4282
+ self .poutput (" " .join (texts ))
4283
+
4286
4284
@staticmethod
4287
4285
def _build_shortcuts_parser () -> Cmd2ArgumentParser :
4288
4286
return argparse_custom .DEFAULT_ARGUMENT_PARSER (description = "List available shortcuts." )
0 commit comments