@@ -236,7 +236,6 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens)
236236)
237237from gettext import gettext
238238from typing import (
239- IO ,
240239 TYPE_CHECKING ,
241240 Any ,
242241 ClassVar ,
@@ -248,11 +247,23 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens)
248247 runtime_checkable ,
249248)
250249
251- from rich_argparse import RawTextRichHelpFormatter
250+ from rich .console import (
251+ Group ,
252+ RenderableType ,
253+ )
254+ from rich .table import Column , Table
255+ from rich .text import Text
256+ from rich_argparse import (
257+ ArgumentDefaultsRichHelpFormatter ,
258+ MetavarTypeRichHelpFormatter ,
259+ RawDescriptionRichHelpFormatter ,
260+ RawTextRichHelpFormatter ,
261+ RichHelpFormatter ,
262+ )
252263
253264from . import (
254- ansi ,
255265 constants ,
266+ rich_utils ,
256267)
257268
258269if TYPE_CHECKING : # pragma: no cover
@@ -759,19 +770,19 @@ def _add_argument_wrapper(
759770 # Validate nargs tuple
760771 if (
761772 len (nargs ) != 2
762- or not isinstance (nargs [0 ], int ) # type: ignore[unreachable]
763- or not (isinstance (nargs [1 ], int ) or nargs [1 ] == constants .INFINITY ) # type: ignore[misc]
773+ or not isinstance (nargs [0 ], int )
774+ or not (isinstance (nargs [1 ], int ) or nargs [1 ] == constants .INFINITY )
764775 ):
765776 raise ValueError ('Ranged values for nargs must be a tuple of 1 or 2 integers' )
766- if nargs [0 ] >= nargs [1 ]: # type: ignore[misc]
777+ if nargs [0 ] >= nargs [1 ]:
767778 raise ValueError ('Invalid nargs range. The first value must be less than the second' )
768779 if nargs [0 ] < 0 :
769780 raise ValueError ('Negative numbers are invalid for nargs range' )
770781
771782 # Save the nargs tuple as our range setting
772783 nargs_range = nargs
773784 range_min = nargs_range [0 ]
774- range_max = nargs_range [1 ] # type: ignore[misc]
785+ range_max = nargs_range [1 ]
775786
776787 # Convert nargs into a format argparse recognizes
777788 if range_min == 0 :
@@ -807,7 +818,7 @@ def _add_argument_wrapper(
807818 new_arg = orig_actions_container_add_argument (self , * args , ** kwargs )
808819
809820 # Set the custom attributes
810- new_arg .set_nargs_range (nargs_range ) # type: ignore[arg-type, attr-defined]
821+ new_arg .set_nargs_range (nargs_range ) # type: ignore[attr-defined]
811822
812823 if choices_provider :
813824 new_arg .set_choices_provider (choices_provider ) # type: ignore[attr-defined]
@@ -996,13 +1007,9 @@ def _SubParsersAction_remove_parser(self: argparse._SubParsersAction, name: str)
9961007############################################################################################################
9971008
9981009
999- class Cmd2HelpFormatter (RawTextRichHelpFormatter ):
1010+ class Cmd2HelpFormatter (RichHelpFormatter ):
10001011 """Custom help formatter to configure ordering of help text."""
10011012
1002- # rich-argparse formats all group names with str.title().
1003- # Override their formatter to do nothing.
1004- group_name_formatter : ClassVar [Callable [[str ], str ]] = str
1005-
10061013 # Disable automatic highlighting in the help text.
10071014 highlights : ClassVar [list [str ]] = []
10081015
@@ -1015,6 +1022,22 @@ class Cmd2HelpFormatter(RawTextRichHelpFormatter):
10151022 help_markup : ClassVar [bool ] = False
10161023 text_markup : ClassVar [bool ] = False
10171024
1025+ def __init__ (
1026+ self ,
1027+ prog : str ,
1028+ indent_increment : int = 2 ,
1029+ max_help_position : int = 24 ,
1030+ width : Optional [int ] = None ,
1031+ * ,
1032+ console : Optional [rich_utils .Cmd2Console ] = None ,
1033+ ** kwargs : Any ,
1034+ ) -> None :
1035+ """Initialize Cmd2HelpFormatter."""
1036+ if console is None :
1037+ console = rich_utils .Cmd2Console (sys .stdout )
1038+
1039+ super ().__init__ (prog , indent_increment , max_help_position , width , console = console , ** kwargs )
1040+
10181041 def _format_usage (
10191042 self ,
10201043 usage : Optional [str ],
@@ -1207,17 +1230,93 @@ def _format_args(self, action: argparse.Action, default_metavar: Union[str, tupl
12071230 return super ()._format_args (action , default_metavar ) # type: ignore[arg-type]
12081231
12091232
1233+ class RawDescriptionCmd2HelpFormatter (
1234+ RawDescriptionRichHelpFormatter ,
1235+ Cmd2HelpFormatter ,
1236+ ):
1237+ """Cmd2 help message formatter which retains any formatting in descriptions and epilogs."""
1238+
1239+
1240+ class RawTextCmd2HelpFormatter (
1241+ RawTextRichHelpFormatter ,
1242+ Cmd2HelpFormatter ,
1243+ ):
1244+ """Cmd2 help message formatter which retains formatting of all help text."""
1245+
1246+
1247+ class ArgumentDefaultsCmd2HelpFormatter (
1248+ ArgumentDefaultsRichHelpFormatter ,
1249+ Cmd2HelpFormatter ,
1250+ ):
1251+ """Cmd2 help message formatter which adds default values to argument help."""
1252+
1253+
1254+ class MetavarTypeCmd2HelpFormatter (
1255+ MetavarTypeRichHelpFormatter ,
1256+ Cmd2HelpFormatter ,
1257+ ):
1258+ """Cmd2 help message formatter which uses the argument 'type' as the default
1259+ metavar value (instead of the argument 'dest').
1260+ """ # noqa: D205
1261+
1262+
1263+ class TextGroup :
1264+ """A block of text which is formatted like an argparse argument group, including a title.
1265+
1266+ Title:
1267+ Here is the first row of text.
1268+ Here is yet another row of text.
1269+ """
1270+
1271+ def __init__ (
1272+ self ,
1273+ title : str ,
1274+ text : RenderableType ,
1275+ formatter_creator : Callable [[], Cmd2HelpFormatter ],
1276+ ) -> None :
1277+ """TextGroup initializer.
1278+
1279+ :param title: the group's title
1280+ :param text: the group's text (string or object that may be rendered by Rich)
1281+ :param formatter_creator: callable which returns a Cmd2HelpFormatter instance
1282+ """
1283+ self .title = title
1284+ self .text = text
1285+ self .formatter_creator = formatter_creator
1286+
1287+ def __rich__ (self ) -> Group :
1288+ """Perform custom rendering."""
1289+ formatter = self .formatter_creator ()
1290+
1291+ styled_title = Text (
1292+ type (formatter ).group_name_formatter (f"{ self .title } :" ),
1293+ style = formatter .styles ["argparse.groups" ],
1294+ )
1295+
1296+ # Left pad the text like an argparse argument group does
1297+ left_padding = formatter ._indent_increment
1298+ text_table = Table (
1299+ Column (overflow = "fold" ),
1300+ box = None ,
1301+ show_header = False ,
1302+ padding = (0 , 0 , 0 , left_padding ),
1303+ )
1304+ text_table .add_row (self .text )
1305+
1306+ return Group (styled_title , text_table )
1307+
1308+
12101309class Cmd2ArgumentParser (argparse .ArgumentParser ):
12111310 """Custom ArgumentParser class that improves error and help output."""
12121311
12131312 def __init__ (
12141313 self ,
12151314 prog : Optional [str ] = None ,
12161315 usage : Optional [str ] = None ,
1217- description : Optional [str ] = None ,
1218- epilog : Optional [str ] = None ,
1316+ description : Optional [RenderableType ] = None ,
1317+ epilog : Optional [RenderableType ] = None ,
12191318 parents : Sequence [argparse .ArgumentParser ] = (),
1220- formatter_class : type [argparse . HelpFormatter ] = Cmd2HelpFormatter ,
1319+ formatter_class : type [Cmd2HelpFormatter ] = Cmd2HelpFormatter ,
12211320 prefix_chars : str = '-' ,
12221321 fromfile_prefix_chars : Optional [str ] = None ,
12231322 argument_default : Optional [str ] = None ,
@@ -1247,8 +1346,8 @@ def __init__(
12471346 super ().__init__ (
12481347 prog = prog ,
12491348 usage = usage ,
1250- description = description ,
1251- epilog = epilog ,
1349+ description = description , # type: ignore[arg-type]
1350+ epilog = epilog , # type: ignore[arg-type]
12521351 parents = parents if parents else [],
12531352 formatter_class = formatter_class , # type: ignore[arg-type]
12541353 prefix_chars = prefix_chars ,
@@ -1261,6 +1360,10 @@ def __init__(
12611360 ** kwargs , # added in Python 3.14
12621361 )
12631362
1363+ # Recast to assist type checkers since these can be Rich renderables in a Cmd2HelpFormatter.
1364+ self .description : Optional [RenderableType ] = self .description # type: ignore[assignment]
1365+ self .epilog : Optional [RenderableType ] = self .epilog # type: ignore[assignment]
1366+
12641367 self .set_ap_completer_type (ap_completer_type ) # type: ignore[attr-defined]
12651368
12661369 def add_subparsers (self , ** kwargs : Any ) -> argparse ._SubParsersAction : # type: ignore[type-arg]
@@ -1290,8 +1393,18 @@ def error(self, message: str) -> NoReturn:
12901393 formatted_message += '\n ' + line
12911394
12921395 self .print_usage (sys .stderr )
1293- formatted_message = ansi .style_error (formatted_message )
1294- self .exit (2 , f'{ formatted_message } \n \n ' )
1396+
1397+ # Add error style to message
1398+ console = self ._get_formatter ().console
1399+ with console .capture () as capture :
1400+ console .print (formatted_message , style = "cmd2.error" , crop = False )
1401+ formatted_message = f"{ capture .get ()} "
1402+
1403+ self .exit (2 , f'{ formatted_message } \n ' )
1404+
1405+ def _get_formatter (self ) -> Cmd2HelpFormatter :
1406+ """Override _get_formatter with customizations for Cmd2HelpFormatter."""
1407+ return cast (Cmd2HelpFormatter , super ()._get_formatter ())
12951408
12961409 def format_help (self ) -> str :
12971410 """Return a string containing a help message, including the program usage and information about the arguments.
@@ -1350,12 +1463,9 @@ def format_help(self) -> str:
13501463 # determine help from format above
13511464 return formatter .format_help () + '\n '
13521465
1353- def _print_message (self , message : str , file : Optional [IO [str ]] = None ) -> None : # type: ignore[override]
1354- # Override _print_message to use style_aware_write() since we use ANSI escape characters to support color
1355- if message :
1356- if file is None :
1357- file = sys .stderr
1358- ansi .style_aware_write (file , message )
1466+ def create_text_group (self , title : str , text : RenderableType ) -> TextGroup :
1467+ """Create a TextGroup using this parser's formatter creator."""
1468+ return TextGroup (title , text , self ._get_formatter )
13591469
13601470
13611471class Cmd2AttributeWrapper :
0 commit comments