@@ -236,7 +236,6 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens)
236
236
)
237
237
from gettext import gettext
238
238
from typing import (
239
- IO ,
240
239
TYPE_CHECKING ,
241
240
Any ,
242
241
ClassVar ,
@@ -248,11 +247,23 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens)
248
247
runtime_checkable ,
249
248
)
250
249
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
+ )
252
263
253
264
from . import (
254
- ansi ,
255
265
constants ,
266
+ rich_utils ,
256
267
)
257
268
258
269
if TYPE_CHECKING : # pragma: no cover
@@ -759,19 +770,19 @@ def _add_argument_wrapper(
759
770
# Validate nargs tuple
760
771
if (
761
772
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 )
764
775
):
765
776
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 ]:
767
778
raise ValueError ('Invalid nargs range. The first value must be less than the second' )
768
779
if nargs [0 ] < 0 :
769
780
raise ValueError ('Negative numbers are invalid for nargs range' )
770
781
771
782
# Save the nargs tuple as our range setting
772
783
nargs_range = nargs
773
784
range_min = nargs_range [0 ]
774
- range_max = nargs_range [1 ] # type: ignore[misc]
785
+ range_max = nargs_range [1 ]
775
786
776
787
# Convert nargs into a format argparse recognizes
777
788
if range_min == 0 :
@@ -807,7 +818,7 @@ def _add_argument_wrapper(
807
818
new_arg = orig_actions_container_add_argument (self , * args , ** kwargs )
808
819
809
820
# 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]
811
822
812
823
if choices_provider :
813
824
new_arg .set_choices_provider (choices_provider ) # type: ignore[attr-defined]
@@ -996,13 +1007,9 @@ def _SubParsersAction_remove_parser(self: argparse._SubParsersAction, name: str)
996
1007
############################################################################################################
997
1008
998
1009
999
- class Cmd2HelpFormatter (RawTextRichHelpFormatter ):
1010
+ class Cmd2HelpFormatter (RichHelpFormatter ):
1000
1011
"""Custom help formatter to configure ordering of help text."""
1001
1012
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
-
1006
1013
# Disable automatic highlighting in the help text.
1007
1014
highlights : ClassVar [list [str ]] = []
1008
1015
@@ -1015,6 +1022,22 @@ class Cmd2HelpFormatter(RawTextRichHelpFormatter):
1015
1022
help_markup : ClassVar [bool ] = False
1016
1023
text_markup : ClassVar [bool ] = False
1017
1024
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
+
1018
1041
def _format_usage (
1019
1042
self ,
1020
1043
usage : Optional [str ],
@@ -1207,17 +1230,93 @@ def _format_args(self, action: argparse.Action, default_metavar: Union[str, tupl
1207
1230
return super ()._format_args (action , default_metavar ) # type: ignore[arg-type]
1208
1231
1209
1232
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
+
1210
1309
class Cmd2ArgumentParser (argparse .ArgumentParser ):
1211
1310
"""Custom ArgumentParser class that improves error and help output."""
1212
1311
1213
1312
def __init__ (
1214
1313
self ,
1215
1314
prog : Optional [str ] = None ,
1216
1315
usage : Optional [str ] = None ,
1217
- description : Optional [str ] = None ,
1218
- epilog : Optional [str ] = None ,
1316
+ description : Optional [RenderableType ] = None ,
1317
+ epilog : Optional [RenderableType ] = None ,
1219
1318
parents : Sequence [argparse .ArgumentParser ] = (),
1220
- formatter_class : type [argparse . HelpFormatter ] = Cmd2HelpFormatter ,
1319
+ formatter_class : type [Cmd2HelpFormatter ] = Cmd2HelpFormatter ,
1221
1320
prefix_chars : str = '-' ,
1222
1321
fromfile_prefix_chars : Optional [str ] = None ,
1223
1322
argument_default : Optional [str ] = None ,
@@ -1247,8 +1346,8 @@ def __init__(
1247
1346
super ().__init__ (
1248
1347
prog = prog ,
1249
1348
usage = usage ,
1250
- description = description ,
1251
- epilog = epilog ,
1349
+ description = description , # type: ignore[arg-type]
1350
+ epilog = epilog , # type: ignore[arg-type]
1252
1351
parents = parents if parents else [],
1253
1352
formatter_class = formatter_class , # type: ignore[arg-type]
1254
1353
prefix_chars = prefix_chars ,
@@ -1261,6 +1360,10 @@ def __init__(
1261
1360
** kwargs , # added in Python 3.14
1262
1361
)
1263
1362
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
+
1264
1367
self .set_ap_completer_type (ap_completer_type ) # type: ignore[attr-defined]
1265
1368
1266
1369
def add_subparsers (self , ** kwargs : Any ) -> argparse ._SubParsersAction : # type: ignore[type-arg]
@@ -1290,8 +1393,18 @@ def error(self, message: str) -> NoReturn:
1290
1393
formatted_message += '\n ' + line
1291
1394
1292
1395
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 ())
1295
1408
1296
1409
def format_help (self ) -> str :
1297
1410
"""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:
1350
1463
# determine help from format above
1351
1464
return formatter .format_help () + '\n '
1352
1465
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 )
1359
1469
1360
1470
1361
1471
class Cmd2AttributeWrapper :
0 commit comments