77parser that inherits from it. This will give a consistent look-and-feel between
88the help/error output of built-in cmd2 commands and the app-specific commands.
99If you wish to override the parser used by cmd2's built-in commands, see
10- override_parser .py example.
10+ custom_parser .py example.
1111
1212Since the new capabilities are added by patching at the argparse API level,
1313they are available whether or not Cmd2ArgumentParser is used. However, the help
@@ -265,6 +265,18 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens)
265265 runtime_checkable ,
266266)
267267
268+ from rich .console import (
269+ Group ,
270+ RenderableType ,
271+ )
272+ from rich_argparse import (
273+ ArgumentDefaultsRichHelpFormatter ,
274+ MetavarTypeRichHelpFormatter ,
275+ RawDescriptionRichHelpFormatter ,
276+ RawTextRichHelpFormatter ,
277+ RichHelpFormatter ,
278+ )
279+
268280from . import (
269281 ansi ,
270282 constants ,
@@ -1042,9 +1054,14 @@ def _SubParsersAction_remove_parser(self: argparse._SubParsersAction, name: str)
10421054############################################################################################################
10431055
10441056
1045- class Cmd2HelpFormatter (argparse . RawTextHelpFormatter ):
1057+ class Cmd2HelpFormatter (RichHelpFormatter ):
10461058 """Custom help formatter to configure ordering of help text"""
10471059
1060+ # Render usage, argument help, description, and epilog as markup.
1061+ RichHelpFormatter .usage_markup = True
1062+ RichHelpFormatter .help_markup = True
1063+ RichHelpFormatter .text_markup = True
1064+
10481065 def _format_usage (
10491066 self ,
10501067 usage : Optional [str ],
@@ -1249,17 +1266,95 @@ def _format_args(self, action: argparse.Action, default_metavar: Union[str, Tupl
12491266 return super ()._format_args (action , default_metavar ) # type: ignore[arg-type]
12501267
12511268
1269+ class RawDescriptionCmd2HelpFormatter (
1270+ RawDescriptionRichHelpFormatter ,
1271+ Cmd2HelpFormatter ,
1272+ ):
1273+ """Cmd2 help message formatter which retains any formatting in descriptions and epilogs."""
1274+
1275+
1276+ class RawTextCmd2HelpFormatter (
1277+ RawTextRichHelpFormatter ,
1278+ Cmd2HelpFormatter ,
1279+ ):
1280+ """Cmd2 help message formatter which retains formatting of all help text."""
1281+
1282+
1283+ class ArgumentDefaultsCmd2HelpFormatter (
1284+ ArgumentDefaultsRichHelpFormatter ,
1285+ Cmd2HelpFormatter ,
1286+ ):
1287+ """Cmd2 help message formatter which adds default values to argument help."""
1288+
1289+
1290+ class MetavarTypeCmd2HelpFormatter (
1291+ MetavarTypeRichHelpFormatter ,
1292+ Cmd2HelpFormatter ,
1293+ ):
1294+ """
1295+ Cmd2 help message formatter which uses the argument 'type' as the default
1296+ metavar value (instead of the argument 'dest').
1297+ """
1298+
1299+
1300+ class TextGroup :
1301+ """
1302+ A block of text which is formatted like an argparse argument group, including a title.
1303+
1304+ Title:
1305+ Here is the first row of text.
1306+ Here is yet another row of text.
1307+ """
1308+
1309+ def __init__ (
1310+ self ,
1311+ title : str ,
1312+ text : RenderableType ,
1313+ formatter_creator : Callable [[], Cmd2HelpFormatter ],
1314+ ) -> None :
1315+ """
1316+ :param title: the group's title
1317+ :param text: the group's text (string or object that may be rendered by Rich)
1318+ :param formatter_creator: callable which returns a Cmd2HelpFormatter instance
1319+ """
1320+ self .title = title
1321+ self .text = text
1322+ self .formatter_creator = formatter_creator
1323+
1324+ def __rich__ (self ) -> Group :
1325+ """Custom rendering logic."""
1326+ import rich
1327+
1328+ formatter = self .formatter_creator ()
1329+
1330+ styled_title = rich .text .Text (
1331+ type (formatter ).group_name_formatter (f"{ self .title } :" ),
1332+ style = formatter .styles ["argparse.groups" ],
1333+ )
1334+
1335+ # Left pad the text like an argparse argument group does
1336+ left_padding = formatter ._indent_increment
1337+
1338+ text_table = rich .table .Table (
1339+ box = None ,
1340+ show_header = False ,
1341+ padding = (0 , 0 , 0 , left_padding ),
1342+ )
1343+ text_table .add_row (self .text )
1344+ return Group (styled_title , text_table )
1345+
1346+
12521347class Cmd2ArgumentParser (argparse .ArgumentParser ):
12531348 """Custom ArgumentParser class that improves error and help output"""
12541349
12551350 def __init__ (
12561351 self ,
12571352 prog : Optional [str ] = None ,
12581353 usage : Optional [str ] = None ,
1259- description : Optional [str ] = None ,
1260- epilog : Optional [str ] = None ,
1354+ description : Optional [RenderableType ] = None ,
1355+ epilog : Optional [RenderableType ] = None ,
12611356 parents : Sequence [argparse .ArgumentParser ] = (),
1262- formatter_class : Type [argparse . HelpFormatter ] = Cmd2HelpFormatter ,
1357+ formatter_class : Type [Cmd2HelpFormatter ] = Cmd2HelpFormatter ,
12631358 prefix_chars : str = '-' ,
12641359 fromfile_prefix_chars : Optional [str ] = None ,
12651360 argument_default : Optional [str ] = None ,
@@ -1279,10 +1374,10 @@ def __init__(
12791374 super (Cmd2ArgumentParser , self ).__init__ (
12801375 prog = prog ,
12811376 usage = usage ,
1282- description = description ,
1283- epilog = epilog ,
1377+ description = description , # type: ignore[arg-type]
1378+ epilog = epilog , # type: ignore[arg-type]
12841379 parents = parents if parents else [],
1285- formatter_class = formatter_class , # type: ignore[arg-type]
1380+ formatter_class = formatter_class ,
12861381 prefix_chars = prefix_chars ,
12871382 fromfile_prefix_chars = fromfile_prefix_chars ,
12881383 argument_default = argument_default ,
@@ -1291,6 +1386,10 @@ def __init__(
12911386 allow_abbrev = allow_abbrev ,
12921387 )
12931388
1389+ # Recast to assist type checkers since in a Cmd2HelpFormatter, these can be Rich renderables.
1390+ self .description : Optional [RenderableType ] = self .description # type: ignore[assignment]
1391+ self .epilog : Optional [RenderableType ] = self .epilog # type: ignore[assignment]
1392+
12941393 self .set_ap_completer_type (ap_completer_type ) # type: ignore[attr-defined]
12951394
12961395 def add_subparsers (self , ** kwargs : Any ) -> argparse ._SubParsersAction : # type: ignore
@@ -1321,6 +1420,10 @@ def error(self, message: str) -> NoReturn:
13211420 formatted_message = ansi .style_error (formatted_message )
13221421 self .exit (2 , f'{ formatted_message } \n \n ' )
13231422
1423+ def _get_formatter (self ) -> Cmd2HelpFormatter :
1424+ """Copy of _get_formatter() with a different return type to assist type checkers."""
1425+ return cast (Cmd2HelpFormatter , super ()._get_formatter ())
1426+
13241427 def format_help (self ) -> str :
13251428 """Copy of format_help() from argparse.ArgumentParser with tweaks to separately display required parameters"""
13261429 formatter = self ._get_formatter ()
@@ -1329,7 +1432,7 @@ def format_help(self) -> str:
13291432 formatter .add_usage (self .usage , self ._actions , self ._mutually_exclusive_groups ) # type: ignore[arg-type]
13301433
13311434 # description
1332- formatter .add_text (self .description )
1435+ formatter .add_text (self .description ) # type: ignore[arg-type]
13331436
13341437 # Begin cmd2 customization (separate required and optional arguments)
13351438
@@ -1370,7 +1473,7 @@ def format_help(self) -> str:
13701473 # End cmd2 customization
13711474
13721475 # epilog
1373- formatter .add_text (self .epilog )
1476+ formatter .add_text (self .epilog ) # type: ignore[arg-type]
13741477
13751478 # determine help from format above
13761479 return formatter .format_help () + '\n '
@@ -1382,6 +1485,10 @@ def _print_message(self, message: str, file: Optional[IO[str]] = None) -> None:
13821485 file = sys .stderr
13831486 ansi .style_aware_write (file , message )
13841487
1488+ def create_text_group (self , title : str , text : RenderableType ) -> TextGroup :
1489+ """Create a TextGroup using this parser's formatter creator."""
1490+ return TextGroup (title , text , self ._get_formatter )
1491+
13851492
13861493class Cmd2AttributeWrapper :
13871494 """
0 commit comments