cmd2 2.7.0__py3-none-any.whl → 3.0.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cmd2/__init__.py +41 -38
- cmd2/argparse_completer.py +80 -81
- cmd2/argparse_custom.py +322 -123
- cmd2/clipboard.py +1 -1
- cmd2/cmd2.py +1264 -837
- cmd2/colors.py +270 -0
- cmd2/command_definition.py +13 -5
- cmd2/constants.py +0 -6
- cmd2/decorators.py +41 -104
- cmd2/exceptions.py +1 -1
- cmd2/history.py +7 -11
- cmd2/parsing.py +12 -17
- cmd2/plugin.py +1 -2
- cmd2/py_bridge.py +15 -10
- cmd2/rich_utils.py +451 -0
- cmd2/rl_utils.py +12 -8
- cmd2/string_utils.py +166 -0
- cmd2/styles.py +72 -0
- cmd2/terminal_utils.py +144 -0
- cmd2/transcript.py +7 -9
- cmd2/utils.py +88 -508
- {cmd2-2.7.0.dist-info → cmd2-3.0.0b1.dist-info}/METADATA +22 -44
- cmd2-3.0.0b1.dist-info/RECORD +27 -0
- cmd2/ansi.py +0 -1093
- cmd2/table_creator.py +0 -1122
- cmd2-2.7.0.dist-info/RECORD +0 -24
- {cmd2-2.7.0.dist-info → cmd2-3.0.0b1.dist-info}/WHEEL +0 -0
- {cmd2-2.7.0.dist-info → cmd2-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
- {cmd2-2.7.0.dist-info → cmd2-3.0.0b1.dist-info}/top_level.txt +0 -0
cmd2/argparse_custom.py
CHANGED
@@ -6,7 +6,7 @@ recommended that developers of cmd2-based apps either use it or write their own
|
|
6
6
|
parser that inherits from it. This will give a consistent look-and-feel between
|
7
7
|
the help/error output of built-in cmd2 commands and the app-specific commands.
|
8
8
|
If you wish to override the parser used by cmd2's built-in commands, see
|
9
|
-
|
9
|
+
custom_parser.py example.
|
10
10
|
|
11
11
|
Since the new capabilities are added by patching at the argparse API level,
|
12
12
|
they are available whether or not Cmd2ArgumentParser is used. However, the help
|
@@ -122,38 +122,25 @@ uninformative data is being tab completed. For instance, tab completing ID
|
|
122
122
|
numbers isn't very helpful to a user without context. Returning a list of
|
123
123
|
CompletionItems instead of a regular string for completion results will signal
|
124
124
|
the ArgparseCompleter to output the completion results in a table of completion
|
125
|
-
tokens with
|
125
|
+
tokens with descriptive data instead of just a table of tokens::
|
126
126
|
|
127
127
|
Instead of this:
|
128
128
|
1 2 3
|
129
129
|
|
130
130
|
The user sees this:
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
131
|
+
ITEM_ID Description
|
132
|
+
────────────────────────────
|
133
|
+
1 My item
|
134
|
+
2 Another item
|
135
|
+
3 Yet another item
|
136
136
|
|
137
137
|
|
138
138
|
The left-most column is the actual value being tab completed and its header is
|
139
139
|
that value's name. The right column header is defined using the
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
token = 1
|
146
|
-
token_description = "My Item"
|
147
|
-
completion_item = CompletionItem(token, token_description)
|
148
|
-
|
149
|
-
Since descriptive_header and CompletionItem.description are just strings, you
|
150
|
-
can format them in such a way to have multiple columns::
|
151
|
-
|
152
|
-
ITEM_ID Item Name Checked Out Due Date
|
153
|
-
==========================================================
|
154
|
-
1 My item True 02/02/2022
|
155
|
-
2 Another item False
|
156
|
-
3 Yet another item False
|
140
|
+
``descriptive_headers`` parameter of add_argument(), which is a list of header
|
141
|
+
names that defaults to ["Description"]. The right column values come from the
|
142
|
+
``CompletionItem.descriptive_data`` member, which is a list with the same number
|
143
|
+
of items as columns defined in descriptive_headers.
|
157
144
|
|
158
145
|
To use CompletionItems, just return them from your choices_provider or
|
159
146
|
completer functions. They can also be used as argparse choices. When a
|
@@ -162,12 +149,59 @@ makes it accessible through a property called orig_value. cmd2 has patched
|
|
162
149
|
argparse so that when evaluating choices, input is compared to
|
163
150
|
CompletionItem.orig_value instead of the CompletionItem instance.
|
164
151
|
|
165
|
-
|
152
|
+
Example::
|
153
|
+
|
154
|
+
Add an argument and define its descriptive_headers.
|
155
|
+
|
156
|
+
parser.add_argument(
|
157
|
+
add_argument(
|
158
|
+
"item_id",
|
159
|
+
type=int,
|
160
|
+
choices_provider=get_items,
|
161
|
+
descriptive_headers=["Item Name", "Checked Out", "Due Date"],
|
162
|
+
)
|
163
|
+
|
164
|
+
Implement the choices_provider to return CompletionItems.
|
165
|
+
|
166
|
+
def get_items(self) -> list[CompletionItems]:
|
167
|
+
\"\"\"choices_provider which returns CompletionItems\"\"\"
|
168
|
+
|
169
|
+
# CompletionItem's second argument is descriptive_data.
|
170
|
+
# Its item count should match that of descriptive_headers.
|
171
|
+
return [
|
172
|
+
CompletionItem(1, ["My item", True, "02/02/2022"]),
|
173
|
+
CompletionItem(2, ["Another item", False, ""]),
|
174
|
+
CompletionItem(3, ["Yet another item", False, ""]),
|
175
|
+
]
|
176
|
+
|
177
|
+
This is what the user will see during tab completion.
|
178
|
+
|
179
|
+
ITEM_ID Item Name Checked Out Due Date
|
180
|
+
───────────────────────────────────────────────────────
|
181
|
+
1 My item True 02/02/2022
|
182
|
+
2 Another item False
|
183
|
+
3 Yet another item False
|
184
|
+
|
185
|
+
``descriptive_headers`` can be strings or ``Rich.table.Columns`` for more
|
186
|
+
control over things like alignment.
|
187
|
+
|
188
|
+
- If a header is a string, it will render as a left-aligned column with its
|
189
|
+
overflow behavior set to "fold". This means a long string will wrap within its
|
190
|
+
cell, creating as many new lines as required to fit.
|
191
|
+
|
192
|
+
- If a header is a ``Column``, it defaults to "ellipsis" overflow behavior.
|
193
|
+
This means a long string which exceeds the width of its column will be
|
194
|
+
truncated with an ellipsis at the end. You can override this and other settings
|
195
|
+
when you create the ``Column``.
|
196
|
+
|
197
|
+
``descriptive_data`` items can include Rich objects, including styled Text and Tables.
|
198
|
+
|
199
|
+
To avoid printing a excessive information to the screen at once when a user
|
166
200
|
presses tab, there is a maximum threshold for the number of CompletionItems
|
167
|
-
that will be shown. Its value is defined in cmd2.Cmd.max_completion_items
|
168
|
-
defaults to 50, but can be changed. If the number of completion suggestions
|
201
|
+
that will be shown. Its value is defined in ``cmd2.Cmd.max_completion_items``.
|
202
|
+
It defaults to 50, but can be changed. If the number of completion suggestions
|
169
203
|
exceeds this number, they will be displayed in the typical columnized format
|
170
|
-
and will not include the
|
204
|
+
and will not include the descriptive_data of the CompletionItems.
|
171
205
|
|
172
206
|
|
173
207
|
**Patched argparse functions**
|
@@ -200,8 +234,8 @@ for cases in which you need to manually access the cmd2-specific attributes.
|
|
200
234
|
- ``argparse.Action.get_choices_callable()`` - See `action_get_choices_callable` for more details.
|
201
235
|
- ``argparse.Action.set_choices_provider()`` - See `_action_set_choices_provider` for more details.
|
202
236
|
- ``argparse.Action.set_completer()`` - See `_action_set_completer` for more details.
|
203
|
-
- ``argparse.Action.
|
204
|
-
- ``argparse.Action.
|
237
|
+
- ``argparse.Action.get_descriptive_headers()`` - See `_action_get_descriptive_headers` for more details.
|
238
|
+
- ``argparse.Action.set_descriptive_headers()`` - See `_action_set_descriptive_headers` for more details.
|
205
239
|
- ``argparse.Action.get_nargs_range()`` - See `_action_get_nargs_range` for more details.
|
206
240
|
- ``argparse.Action.set_nargs_range()`` - See `_action_set_nargs_range` for more details.
|
207
241
|
- ``argparse.Action.get_suppress_tab_hint()`` - See `_action_get_suppress_tab_hint` for more details.
|
@@ -236,25 +270,35 @@ from collections.abc import (
|
|
236
270
|
)
|
237
271
|
from gettext import gettext
|
238
272
|
from typing import (
|
239
|
-
IO,
|
240
273
|
TYPE_CHECKING,
|
241
274
|
Any,
|
242
275
|
ClassVar,
|
243
276
|
NoReturn,
|
244
|
-
Optional,
|
245
277
|
Protocol,
|
246
|
-
Union,
|
247
278
|
cast,
|
248
279
|
runtime_checkable,
|
249
280
|
)
|
250
281
|
|
251
|
-
from
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
282
|
+
from rich.console import (
|
283
|
+
Group,
|
284
|
+
RenderableType,
|
285
|
+
)
|
286
|
+
from rich.protocol import is_renderable
|
287
|
+
from rich.table import Column
|
288
|
+
from rich.text import Text
|
289
|
+
from rich_argparse import (
|
290
|
+
ArgumentDefaultsRichHelpFormatter,
|
291
|
+
MetavarTypeRichHelpFormatter,
|
292
|
+
RawDescriptionRichHelpFormatter,
|
293
|
+
RawTextRichHelpFormatter,
|
294
|
+
RichHelpFormatter,
|
256
295
|
)
|
257
296
|
|
297
|
+
from . import constants
|
298
|
+
from . import rich_utils as ru
|
299
|
+
from .rich_utils import Cmd2RichArgparseConsole
|
300
|
+
from .styles import Cmd2Style
|
301
|
+
|
258
302
|
if TYPE_CHECKING: # pragma: no cover
|
259
303
|
from .argparse_completer import (
|
260
304
|
ArgparseCompleter,
|
@@ -280,6 +324,56 @@ def generate_range_error(range_min: int, range_max: float) -> str:
|
|
280
324
|
return err_str
|
281
325
|
|
282
326
|
|
327
|
+
def set_parser_prog(parser: argparse.ArgumentParser, prog: str) -> None:
|
328
|
+
"""Recursively set prog attribute of a parser and all of its subparsers.
|
329
|
+
|
330
|
+
Does so that the root command is a command name and not sys.argv[0].
|
331
|
+
|
332
|
+
:param parser: the parser being edited
|
333
|
+
:param prog: new value for the parser's prog attribute
|
334
|
+
"""
|
335
|
+
# Set the prog value for this parser
|
336
|
+
parser.prog = prog
|
337
|
+
req_args: list[str] = []
|
338
|
+
|
339
|
+
# Set the prog value for the parser's subcommands
|
340
|
+
for action in parser._actions:
|
341
|
+
if isinstance(action, argparse._SubParsersAction):
|
342
|
+
# Set the _SubParsersAction's _prog_prefix value. That way if its add_parser() method is called later,
|
343
|
+
# the correct prog value will be set on the parser being added.
|
344
|
+
action._prog_prefix = parser.prog
|
345
|
+
|
346
|
+
# The keys of action.choices are subcommand names as well as subcommand aliases. The aliases point to the
|
347
|
+
# same parser as the actual subcommand. We want to avoid placing an alias into a parser's prog value.
|
348
|
+
# Unfortunately there is nothing about an action.choices entry which tells us it's an alias. In most cases
|
349
|
+
# we can filter out the aliases by checking the contents of action._choices_actions. This list only contains
|
350
|
+
# help information and names for the subcommands and not aliases. However, subcommands without help text
|
351
|
+
# won't show up in that list. Since dictionaries are ordered in Python 3.6 and above and argparse inserts the
|
352
|
+
# subcommand name into choices dictionary before aliases, we should be OK assuming the first time we see a
|
353
|
+
# parser, the dictionary key is a subcommand and not alias.
|
354
|
+
processed_parsers = []
|
355
|
+
|
356
|
+
# Set the prog value for each subcommand's parser
|
357
|
+
for subcmd_name, subcmd_parser in action.choices.items():
|
358
|
+
# Check if we've already edited this parser
|
359
|
+
if subcmd_parser in processed_parsers:
|
360
|
+
continue
|
361
|
+
|
362
|
+
subcmd_prog = parser.prog
|
363
|
+
if req_args:
|
364
|
+
subcmd_prog += " " + " ".join(req_args)
|
365
|
+
subcmd_prog += " " + subcmd_name
|
366
|
+
set_parser_prog(subcmd_parser, subcmd_prog)
|
367
|
+
processed_parsers.append(subcmd_parser)
|
368
|
+
|
369
|
+
# We can break since argparse only allows 1 group of subcommands per level
|
370
|
+
break
|
371
|
+
|
372
|
+
# Need to save required args so they can be prepended to the subcommand usage
|
373
|
+
if action.required:
|
374
|
+
req_args.append(action.dest)
|
375
|
+
|
376
|
+
|
283
377
|
class CompletionItem(str): # noqa: SLOT000
|
284
378
|
"""Completion item with descriptive text attached.
|
285
379
|
|
@@ -290,15 +384,22 @@ class CompletionItem(str): # noqa: SLOT000
|
|
290
384
|
"""Responsible for creating and returning a new instance, called before __init__ when an object is instantiated."""
|
291
385
|
return super().__new__(cls, value)
|
292
386
|
|
293
|
-
def __init__(self, value: object,
|
387
|
+
def __init__(self, value: object, descriptive_data: Sequence[Any], *args: Any) -> None:
|
294
388
|
"""CompletionItem Initializer.
|
295
389
|
|
296
390
|
:param value: the value being tab completed
|
297
|
-
:param
|
391
|
+
:param descriptive_data: a list of descriptive data to display in the columns that follow
|
392
|
+
the completion value. The number of items in this list must equal
|
393
|
+
the number of descriptive headers defined for the argument.
|
298
394
|
:param args: args for str __init__
|
299
395
|
"""
|
300
396
|
super().__init__(*args)
|
301
|
-
|
397
|
+
|
398
|
+
# Make sure all objects are renderable by a Rich table.
|
399
|
+
renderable_data = [obj if is_renderable(obj) else str(obj) for obj in descriptive_data]
|
400
|
+
|
401
|
+
# Convert strings containing ANSI style sequences to Rich Text objects for correct display width.
|
402
|
+
self.descriptive_data = ru.prepare_objects_for_rendering(*renderable_data)
|
302
403
|
|
303
404
|
# Save the original value to support CompletionItems as argparse choices.
|
304
405
|
# cmd2 has patched argparse so input is compared to this value instead of the CompletionItem instance.
|
@@ -331,7 +432,7 @@ class ChoicesProviderFuncWithTokens(Protocol):
|
|
331
432
|
"""Enable instances to be called like functions."""
|
332
433
|
|
333
434
|
|
334
|
-
ChoicesProviderFunc =
|
435
|
+
ChoicesProviderFunc = ChoicesProviderFuncBase | ChoicesProviderFuncWithTokens
|
335
436
|
|
336
437
|
|
337
438
|
@runtime_checkable
|
@@ -364,7 +465,7 @@ class CompleterFuncWithTokens(Protocol):
|
|
364
465
|
"""Enable instances to be called like functions."""
|
365
466
|
|
366
467
|
|
367
|
-
CompleterFunc =
|
468
|
+
CompleterFunc = CompleterFuncBase | CompleterFuncWithTokens
|
368
469
|
|
369
470
|
|
370
471
|
class ChoicesCallable:
|
@@ -376,7 +477,7 @@ class ChoicesCallable:
|
|
376
477
|
def __init__(
|
377
478
|
self,
|
378
479
|
is_completer: bool,
|
379
|
-
to_call:
|
480
|
+
to_call: CompleterFunc | ChoicesProviderFunc,
|
380
481
|
) -> None:
|
381
482
|
"""Initialize the ChoiceCallable instance.
|
382
483
|
|
@@ -424,7 +525,7 @@ class ChoicesCallable:
|
|
424
525
|
ATTR_CHOICES_CALLABLE = 'choices_callable'
|
425
526
|
|
426
527
|
# Descriptive header that prints when using CompletionItems
|
427
|
-
|
528
|
+
ATTR_DESCRIPTIVE_HEADERS = 'descriptive_headers'
|
428
529
|
|
429
530
|
# A tuple specifying nargs as a range (min, max)
|
430
531
|
ATTR_NARGS_RANGE = 'nargs_range'
|
@@ -437,7 +538,7 @@ ATTR_SUPPRESS_TAB_HINT = 'suppress_tab_hint'
|
|
437
538
|
############################################################################################################
|
438
539
|
# Patch argparse.Action with accessors for choice_callable attribute
|
439
540
|
############################################################################################################
|
440
|
-
def _action_get_choices_callable(self: argparse.Action) ->
|
541
|
+
def _action_get_choices_callable(self: argparse.Action) -> ChoicesCallable | None:
|
441
542
|
"""Get the choices_callable attribute of an argparse Action.
|
442
543
|
|
443
544
|
This function is added by cmd2 as a method called ``get_choices_callable()`` to ``argparse.Action`` class.
|
@@ -447,7 +548,7 @@ def _action_get_choices_callable(self: argparse.Action) -> Optional[ChoicesCalla
|
|
447
548
|
:param self: argparse Action being queried
|
448
549
|
:return: A ChoicesCallable instance or None if attribute does not exist
|
449
550
|
"""
|
450
|
-
return cast(
|
551
|
+
return cast(ChoicesCallable | None, getattr(self, ATTR_CHOICES_CALLABLE, None))
|
451
552
|
|
452
553
|
|
453
554
|
setattr(argparse.Action, 'get_choices_callable', _action_get_choices_callable)
|
@@ -521,44 +622,44 @@ setattr(argparse.Action, 'set_completer', _action_set_completer)
|
|
521
622
|
|
522
623
|
|
523
624
|
############################################################################################################
|
524
|
-
# Patch argparse.Action with accessors for
|
625
|
+
# Patch argparse.Action with accessors for descriptive_headers attribute
|
525
626
|
############################################################################################################
|
526
|
-
def
|
527
|
-
"""Get the
|
627
|
+
def _action_get_descriptive_headers(self: argparse.Action) -> Sequence[str | Column] | None:
|
628
|
+
"""Get the descriptive_headers attribute of an argparse Action.
|
528
629
|
|
529
|
-
This function is added by cmd2 as a method called ``
|
630
|
+
This function is added by cmd2 as a method called ``get_descriptive_headers()`` to ``argparse.Action`` class.
|
530
631
|
|
531
|
-
To call: ``action.
|
632
|
+
To call: ``action.get_descriptive_headers()``
|
532
633
|
|
533
634
|
:param self: argparse Action being queried
|
534
|
-
:return: The value of
|
635
|
+
:return: The value of descriptive_headers or None if attribute does not exist
|
535
636
|
"""
|
536
|
-
return cast(
|
637
|
+
return cast(Sequence[str | Column] | None, getattr(self, ATTR_DESCRIPTIVE_HEADERS, None))
|
537
638
|
|
538
639
|
|
539
|
-
setattr(argparse.Action, '
|
640
|
+
setattr(argparse.Action, 'get_descriptive_headers', _action_get_descriptive_headers)
|
540
641
|
|
541
642
|
|
542
|
-
def
|
543
|
-
"""Set the
|
643
|
+
def _action_set_descriptive_headers(self: argparse.Action, descriptive_headers: Sequence[str | Column] | None) -> None:
|
644
|
+
"""Set the descriptive_headers attribute of an argparse Action.
|
544
645
|
|
545
|
-
This function is added by cmd2 as a method called ``
|
646
|
+
This function is added by cmd2 as a method called ``set_descriptive_headers()`` to ``argparse.Action`` class.
|
546
647
|
|
547
|
-
To call: ``action.
|
648
|
+
To call: ``action.set_descriptive_headers(descriptive_headers)``
|
548
649
|
|
549
650
|
:param self: argparse Action being updated
|
550
|
-
:param
|
651
|
+
:param descriptive_headers: value being assigned
|
551
652
|
"""
|
552
|
-
setattr(self,
|
653
|
+
setattr(self, ATTR_DESCRIPTIVE_HEADERS, descriptive_headers)
|
553
654
|
|
554
655
|
|
555
|
-
setattr(argparse.Action, '
|
656
|
+
setattr(argparse.Action, 'set_descriptive_headers', _action_set_descriptive_headers)
|
556
657
|
|
557
658
|
|
558
659
|
############################################################################################################
|
559
660
|
# Patch argparse.Action with accessors for nargs_range attribute
|
560
661
|
############################################################################################################
|
561
|
-
def _action_get_nargs_range(self: argparse.Action) ->
|
662
|
+
def _action_get_nargs_range(self: argparse.Action) -> tuple[int, int | float] | None:
|
562
663
|
"""Get the nargs_range attribute of an argparse Action.
|
563
664
|
|
564
665
|
This function is added by cmd2 as a method called ``get_nargs_range()`` to ``argparse.Action`` class.
|
@@ -568,13 +669,13 @@ def _action_get_nargs_range(self: argparse.Action) -> Optional[tuple[int, Union[
|
|
568
669
|
:param self: argparse Action being queried
|
569
670
|
:return: The value of nargs_range or None if attribute does not exist
|
570
671
|
"""
|
571
|
-
return cast(
|
672
|
+
return cast(tuple[int, int | float] | None, getattr(self, ATTR_NARGS_RANGE, None))
|
572
673
|
|
573
674
|
|
574
675
|
setattr(argparse.Action, 'get_nargs_range', _action_get_nargs_range)
|
575
676
|
|
576
677
|
|
577
|
-
def _action_set_nargs_range(self: argparse.Action, nargs_range:
|
678
|
+
def _action_set_nargs_range(self: argparse.Action, nargs_range: tuple[int, int | float] | None) -> None:
|
578
679
|
"""Set the nargs_range attribute of an argparse Action.
|
579
680
|
|
580
681
|
This function is added by cmd2 as a method called ``set_nargs_range()`` to ``argparse.Action`` class.
|
@@ -633,7 +734,7 @@ CUSTOM_ACTION_ATTRIBS: set[str] = set()
|
|
633
734
|
_CUSTOM_ATTRIB_PFX = '_attr_'
|
634
735
|
|
635
736
|
|
636
|
-
def register_argparse_argument_parameter(param_name: str, param_type:
|
737
|
+
def register_argparse_argument_parameter(param_name: str, param_type: type[Any] | None) -> None:
|
637
738
|
"""Register a custom argparse argument parameter.
|
638
739
|
|
639
740
|
The registered name will then be a recognized keyword parameter to the parser's `add_argument()` function.
|
@@ -699,11 +800,11 @@ orig_actions_container_add_argument = argparse._ActionsContainer.add_argument
|
|
699
800
|
def _add_argument_wrapper(
|
700
801
|
self: argparse._ActionsContainer,
|
701
802
|
*args: Any,
|
702
|
-
nargs:
|
703
|
-
choices_provider:
|
704
|
-
completer:
|
803
|
+
nargs: int | str | tuple[int] | tuple[int, int] | tuple[int, float] | None = None,
|
804
|
+
choices_provider: ChoicesProviderFunc | None = None,
|
805
|
+
completer: CompleterFunc | None = None,
|
705
806
|
suppress_tab_hint: bool = False,
|
706
|
-
|
807
|
+
descriptive_headers: list[Column | str] | None = None,
|
707
808
|
**kwargs: Any,
|
708
809
|
) -> argparse.Action:
|
709
810
|
"""Wrap ActionsContainer.add_argument() which supports more settings used by cmd2.
|
@@ -723,8 +824,8 @@ def _add_argument_wrapper(
|
|
723
824
|
current argument's help text as a hint. Set this to True to suppress the hint. If this
|
724
825
|
argument's help text is set to argparse.SUPPRESS, then tab hints will not display
|
725
826
|
regardless of the value passed for suppress_tab_hint. Defaults to False.
|
726
|
-
:param
|
727
|
-
|
827
|
+
:param descriptive_headers: if the provided choices are CompletionItems, then these are the headers
|
828
|
+
of the descriptive data. Defaults to None.
|
728
829
|
|
729
830
|
# Args from original function
|
730
831
|
:param kwargs: keyword-arguments recognized by argparse._ActionsContainer.add_argument
|
@@ -749,7 +850,7 @@ def _add_argument_wrapper(
|
|
749
850
|
nargs_range = None
|
750
851
|
|
751
852
|
if nargs is not None:
|
752
|
-
nargs_adjusted:
|
853
|
+
nargs_adjusted: int | str | tuple[int] | tuple[int, int] | tuple[int, float] | None
|
753
854
|
# Check if nargs was given as a range
|
754
855
|
if isinstance(nargs, tuple):
|
755
856
|
# Handle 1-item tuple by setting max to INFINITY
|
@@ -759,11 +860,11 @@ def _add_argument_wrapper(
|
|
759
860
|
# Validate nargs tuple
|
760
861
|
if (
|
761
862
|
len(nargs) != 2
|
762
|
-
or not isinstance(nargs[0], int)
|
763
|
-
or not (isinstance(nargs[1], int) or nargs[1] == constants.INFINITY)
|
863
|
+
or not isinstance(nargs[0], int)
|
864
|
+
or not (isinstance(nargs[1], int) or nargs[1] == constants.INFINITY)
|
764
865
|
):
|
765
866
|
raise ValueError('Ranged values for nargs must be a tuple of 1 or 2 integers')
|
766
|
-
if nargs[0] >= nargs[1]:
|
867
|
+
if nargs[0] >= nargs[1]:
|
767
868
|
raise ValueError('Invalid nargs range. The first value must be less than the second')
|
768
869
|
if nargs[0] < 0:
|
769
870
|
raise ValueError('Negative numbers are invalid for nargs range')
|
@@ -771,7 +872,7 @@ def _add_argument_wrapper(
|
|
771
872
|
# Save the nargs tuple as our range setting
|
772
873
|
nargs_range = nargs
|
773
874
|
range_min = nargs_range[0]
|
774
|
-
range_max = nargs_range[1]
|
875
|
+
range_max = nargs_range[1]
|
775
876
|
|
776
877
|
# Convert nargs into a format argparse recognizes
|
777
878
|
if range_min == 0:
|
@@ -807,7 +908,7 @@ def _add_argument_wrapper(
|
|
807
908
|
new_arg = orig_actions_container_add_argument(self, *args, **kwargs)
|
808
909
|
|
809
910
|
# Set the custom attributes
|
810
|
-
new_arg.set_nargs_range(nargs_range) # type: ignore[
|
911
|
+
new_arg.set_nargs_range(nargs_range) # type: ignore[attr-defined]
|
811
912
|
|
812
913
|
if choices_provider:
|
813
914
|
new_arg.set_choices_provider(choices_provider) # type: ignore[attr-defined]
|
@@ -815,7 +916,7 @@ def _add_argument_wrapper(
|
|
815
916
|
new_arg.set_completer(completer) # type: ignore[attr-defined]
|
816
917
|
|
817
918
|
new_arg.set_suppress_tab_hint(suppress_tab_hint) # type: ignore[attr-defined]
|
818
|
-
new_arg.
|
919
|
+
new_arg.set_descriptive_headers(descriptive_headers) # type: ignore[attr-defined]
|
819
920
|
|
820
921
|
for keyword, value in custom_attribs.items():
|
821
922
|
attr_setter = getattr(new_arg, f'set_{keyword}', None)
|
@@ -890,7 +991,7 @@ setattr(argparse.ArgumentParser, '_match_argument', _match_argument_wrapper)
|
|
890
991
|
ATTR_AP_COMPLETER_TYPE = 'ap_completer_type'
|
891
992
|
|
892
993
|
|
893
|
-
def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) ->
|
994
|
+
def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> type['ArgparseCompleter'] | None: # noqa: N802
|
894
995
|
"""Get the ap_completer_type attribute of an argparse ArgumentParser.
|
895
996
|
|
896
997
|
This function is added by cmd2 as a method called ``get_ap_completer_type()`` to ``argparse.ArgumentParser`` class.
|
@@ -900,7 +1001,7 @@ def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Opti
|
|
900
1001
|
:param self: ArgumentParser being queried
|
901
1002
|
:return: An ArgparseCompleter-based class or None if attribute does not exist
|
902
1003
|
"""
|
903
|
-
return cast(
|
1004
|
+
return cast(type['ArgparseCompleter'] | None, getattr(self, ATTR_AP_COMPLETER_TYPE, None))
|
904
1005
|
|
905
1006
|
|
906
1007
|
setattr(argparse.ArgumentParser, 'get_ap_completer_type', _ArgumentParser_get_ap_completer_type)
|
@@ -996,13 +1097,9 @@ setattr(argparse._SubParsersAction, 'remove_parser', _SubParsersAction_remove_pa
|
|
996
1097
|
############################################################################################################
|
997
1098
|
|
998
1099
|
|
999
|
-
class Cmd2HelpFormatter(
|
1100
|
+
class Cmd2HelpFormatter(RichHelpFormatter):
|
1000
1101
|
"""Custom help formatter to configure ordering of help text."""
|
1001
1102
|
|
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
1103
|
# Disable automatic highlighting in the help text.
|
1007
1104
|
highlights: ClassVar[list[str]] = []
|
1008
1105
|
|
@@ -1015,12 +1112,28 @@ class Cmd2HelpFormatter(RawTextRichHelpFormatter):
|
|
1015
1112
|
help_markup: ClassVar[bool] = False
|
1016
1113
|
text_markup: ClassVar[bool] = False
|
1017
1114
|
|
1115
|
+
def __init__(
|
1116
|
+
self,
|
1117
|
+
prog: str,
|
1118
|
+
indent_increment: int = 2,
|
1119
|
+
max_help_position: int = 24,
|
1120
|
+
width: int | None = None,
|
1121
|
+
*,
|
1122
|
+
console: Cmd2RichArgparseConsole | None = None,
|
1123
|
+
**kwargs: Any,
|
1124
|
+
) -> None:
|
1125
|
+
"""Initialize Cmd2HelpFormatter."""
|
1126
|
+
if console is None:
|
1127
|
+
console = Cmd2RichArgparseConsole()
|
1128
|
+
|
1129
|
+
super().__init__(prog, indent_increment, max_help_position, width, console=console, **kwargs)
|
1130
|
+
|
1018
1131
|
def _format_usage(
|
1019
1132
|
self,
|
1020
|
-
usage:
|
1133
|
+
usage: str | None,
|
1021
1134
|
actions: Iterable[argparse.Action],
|
1022
1135
|
groups: Iterable[argparse._ArgumentGroup],
|
1023
|
-
prefix:
|
1136
|
+
prefix: str | None = None,
|
1024
1137
|
) -> str:
|
1025
1138
|
if prefix is None:
|
1026
1139
|
prefix = gettext('Usage: ')
|
@@ -1074,7 +1187,7 @@ class Cmd2HelpFormatter(RawTextRichHelpFormatter):
|
|
1074
1187
|
# End cmd2 customization
|
1075
1188
|
|
1076
1189
|
# helper for wrapping lines
|
1077
|
-
def get_lines(parts: list[str], indent: str, prefix:
|
1190
|
+
def get_lines(parts: list[str], indent: str, prefix: str | None = None) -> list[str]:
|
1078
1191
|
lines: list[str] = []
|
1079
1192
|
line: list[str] = []
|
1080
1193
|
line_len = len(prefix) - 1 if prefix is not None else len(indent) - 1
|
@@ -1154,8 +1267,8 @@ class Cmd2HelpFormatter(RawTextRichHelpFormatter):
|
|
1154
1267
|
def _determine_metavar(
|
1155
1268
|
self,
|
1156
1269
|
action: argparse.Action,
|
1157
|
-
default_metavar:
|
1158
|
-
) ->
|
1270
|
+
default_metavar: str,
|
1271
|
+
) -> str | tuple[str, ...]:
|
1159
1272
|
"""Determine what to use as the metavar value of an action."""
|
1160
1273
|
if action.metavar is not None:
|
1161
1274
|
result = action.metavar
|
@@ -1171,7 +1284,7 @@ class Cmd2HelpFormatter(RawTextRichHelpFormatter):
|
|
1171
1284
|
def _metavar_formatter(
|
1172
1285
|
self,
|
1173
1286
|
action: argparse.Action,
|
1174
|
-
default_metavar:
|
1287
|
+
default_metavar: str,
|
1175
1288
|
) -> Callable[[int], tuple[str, ...]]:
|
1176
1289
|
metavar = self._determine_metavar(action, default_metavar)
|
1177
1290
|
|
@@ -1182,7 +1295,7 @@ class Cmd2HelpFormatter(RawTextRichHelpFormatter):
|
|
1182
1295
|
|
1183
1296
|
return format_tuple
|
1184
1297
|
|
1185
|
-
def _format_args(self, action: argparse.Action, default_metavar:
|
1298
|
+
def _format_args(self, action: argparse.Action, default_metavar: str) -> str:
|
1186
1299
|
"""Handle ranged nargs and make other output less verbose."""
|
1187
1300
|
metavar = self._determine_metavar(action, default_metavar)
|
1188
1301
|
metavar_formatter = self._metavar_formatter(action, default_metavar)
|
@@ -1207,20 +1320,93 @@ class Cmd2HelpFormatter(RawTextRichHelpFormatter):
|
|
1207
1320
|
return super()._format_args(action, default_metavar) # type: ignore[arg-type]
|
1208
1321
|
|
1209
1322
|
|
1323
|
+
class RawDescriptionCmd2HelpFormatter(
|
1324
|
+
RawDescriptionRichHelpFormatter,
|
1325
|
+
Cmd2HelpFormatter,
|
1326
|
+
):
|
1327
|
+
"""Cmd2 help message formatter which retains any formatting in descriptions and epilogs."""
|
1328
|
+
|
1329
|
+
|
1330
|
+
class RawTextCmd2HelpFormatter(
|
1331
|
+
RawTextRichHelpFormatter,
|
1332
|
+
Cmd2HelpFormatter,
|
1333
|
+
):
|
1334
|
+
"""Cmd2 help message formatter which retains formatting of all help text."""
|
1335
|
+
|
1336
|
+
|
1337
|
+
class ArgumentDefaultsCmd2HelpFormatter(
|
1338
|
+
ArgumentDefaultsRichHelpFormatter,
|
1339
|
+
Cmd2HelpFormatter,
|
1340
|
+
):
|
1341
|
+
"""Cmd2 help message formatter which adds default values to argument help."""
|
1342
|
+
|
1343
|
+
|
1344
|
+
class MetavarTypeCmd2HelpFormatter(
|
1345
|
+
MetavarTypeRichHelpFormatter,
|
1346
|
+
Cmd2HelpFormatter,
|
1347
|
+
):
|
1348
|
+
"""Cmd2 help message formatter which uses the argument 'type' as the default
|
1349
|
+
metavar value (instead of the argument 'dest').
|
1350
|
+
""" # noqa: D205
|
1351
|
+
|
1352
|
+
|
1353
|
+
class TextGroup:
|
1354
|
+
"""A block of text which is formatted like an argparse argument group, including a title.
|
1355
|
+
|
1356
|
+
Title:
|
1357
|
+
Here is the first row of text.
|
1358
|
+
Here is yet another row of text.
|
1359
|
+
"""
|
1360
|
+
|
1361
|
+
def __init__(
|
1362
|
+
self,
|
1363
|
+
title: str,
|
1364
|
+
text: RenderableType,
|
1365
|
+
formatter_creator: Callable[[], Cmd2HelpFormatter],
|
1366
|
+
) -> None:
|
1367
|
+
"""TextGroup initializer.
|
1368
|
+
|
1369
|
+
:param title: the group's title
|
1370
|
+
:param text: the group's text (string or object that may be rendered by Rich)
|
1371
|
+
:param formatter_creator: callable which returns a Cmd2HelpFormatter instance
|
1372
|
+
"""
|
1373
|
+
self.title = title
|
1374
|
+
self.text = text
|
1375
|
+
self.formatter_creator = formatter_creator
|
1376
|
+
|
1377
|
+
def __rich__(self) -> Group:
|
1378
|
+
"""Return a renderable Rich Group object for the class instance.
|
1379
|
+
|
1380
|
+
This method formats the title and indents the text to match argparse
|
1381
|
+
group styling, making the object displayable by a Rich console.
|
1382
|
+
"""
|
1383
|
+
formatter = self.formatter_creator()
|
1384
|
+
|
1385
|
+
styled_title = Text(
|
1386
|
+
type(formatter).group_name_formatter(f"{self.title}:"),
|
1387
|
+
style=formatter.styles["argparse.groups"],
|
1388
|
+
)
|
1389
|
+
|
1390
|
+
# Indent text like an argparse argument group does
|
1391
|
+
indented_text = ru.indent(self.text, formatter._indent_increment)
|
1392
|
+
|
1393
|
+
return Group(styled_title, indented_text)
|
1394
|
+
|
1395
|
+
|
1210
1396
|
class Cmd2ArgumentParser(argparse.ArgumentParser):
|
1211
1397
|
"""Custom ArgumentParser class that improves error and help output."""
|
1212
1398
|
|
1213
1399
|
def __init__(
|
1214
1400
|
self,
|
1215
|
-
prog:
|
1216
|
-
usage:
|
1217
|
-
description:
|
1218
|
-
epilog:
|
1401
|
+
prog: str | None = None,
|
1402
|
+
usage: str | None = None,
|
1403
|
+
description: RenderableType | None = None,
|
1404
|
+
epilog: RenderableType | None = None,
|
1219
1405
|
parents: Sequence[argparse.ArgumentParser] = (),
|
1220
|
-
formatter_class: type[
|
1406
|
+
formatter_class: type[Cmd2HelpFormatter] = Cmd2HelpFormatter,
|
1221
1407
|
prefix_chars: str = '-',
|
1222
|
-
fromfile_prefix_chars:
|
1223
|
-
argument_default:
|
1408
|
+
fromfile_prefix_chars: str | None = None,
|
1409
|
+
argument_default: str | None = None,
|
1224
1410
|
conflict_handler: str = 'error',
|
1225
1411
|
add_help: bool = True,
|
1226
1412
|
allow_abbrev: bool = True,
|
@@ -1228,7 +1414,7 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
|
|
1228
1414
|
suggest_on_error: bool = False,
|
1229
1415
|
color: bool = False,
|
1230
1416
|
*,
|
1231
|
-
ap_completer_type:
|
1417
|
+
ap_completer_type: type['ArgparseCompleter'] | None = None,
|
1232
1418
|
) -> None:
|
1233
1419
|
"""Initialize the Cmd2ArgumentParser instance, a custom ArgumentParser added by cmd2.
|
1234
1420
|
|
@@ -1247,10 +1433,10 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
|
|
1247
1433
|
super().__init__(
|
1248
1434
|
prog=prog,
|
1249
1435
|
usage=usage,
|
1250
|
-
description=description,
|
1251
|
-
epilog=epilog,
|
1436
|
+
description=description, # type: ignore[arg-type]
|
1437
|
+
epilog=epilog, # type: ignore[arg-type]
|
1252
1438
|
parents=parents if parents else [],
|
1253
|
-
formatter_class=formatter_class,
|
1439
|
+
formatter_class=formatter_class,
|
1254
1440
|
prefix_chars=prefix_chars,
|
1255
1441
|
fromfile_prefix_chars=fromfile_prefix_chars,
|
1256
1442
|
argument_default=argument_default,
|
@@ -1261,6 +1447,10 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
|
|
1261
1447
|
**kwargs, # added in Python 3.14
|
1262
1448
|
)
|
1263
1449
|
|
1450
|
+
# Recast to assist type checkers since these can be Rich renderables in a Cmd2HelpFormatter.
|
1451
|
+
self.description: RenderableType | None = self.description # type: ignore[assignment]
|
1452
|
+
self.epilog: RenderableType | None = self.epilog # type: ignore[assignment]
|
1453
|
+
|
1264
1454
|
self.set_ap_completer_type(ap_completer_type) # type: ignore[attr-defined]
|
1265
1455
|
|
1266
1456
|
def add_subparsers(self, **kwargs: Any) -> argparse._SubParsersAction: # type: ignore[type-arg]
|
@@ -1290,8 +1480,18 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
|
|
1290
1480
|
formatted_message += '\n ' + line
|
1291
1481
|
|
1292
1482
|
self.print_usage(sys.stderr)
|
1293
|
-
|
1294
|
-
|
1483
|
+
|
1484
|
+
# Add error style to message
|
1485
|
+
console = self._get_formatter().console
|
1486
|
+
with console.capture() as capture:
|
1487
|
+
console.print(formatted_message, style=Cmd2Style.ERROR, crop=False)
|
1488
|
+
formatted_message = f"{capture.get()}"
|
1489
|
+
|
1490
|
+
self.exit(2, f'{formatted_message}\n')
|
1491
|
+
|
1492
|
+
def _get_formatter(self) -> Cmd2HelpFormatter:
|
1493
|
+
"""Override _get_formatter with customizations for Cmd2HelpFormatter."""
|
1494
|
+
return cast(Cmd2HelpFormatter, super()._get_formatter())
|
1295
1495
|
|
1296
1496
|
def format_help(self) -> str:
|
1297
1497
|
"""Return a string containing a help message, including the program usage and information about the arguments.
|
@@ -1301,7 +1501,7 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
|
|
1301
1501
|
formatter = self._get_formatter()
|
1302
1502
|
|
1303
1503
|
# usage
|
1304
|
-
formatter.add_usage(self.usage, self._actions, self._mutually_exclusive_groups)
|
1504
|
+
formatter.add_usage(self.usage, self._actions, self._mutually_exclusive_groups)
|
1305
1505
|
|
1306
1506
|
# description
|
1307
1507
|
formatter.add_text(self.description)
|
@@ -1310,10 +1510,7 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
|
|
1310
1510
|
|
1311
1511
|
# positionals, optionals and user-defined groups
|
1312
1512
|
for action_group in self._action_groups:
|
1313
|
-
|
1314
|
-
default_options_group = action_group.title == 'options'
|
1315
|
-
else:
|
1316
|
-
default_options_group = action_group.title == 'optional arguments'
|
1513
|
+
default_options_group = action_group.title == 'options'
|
1317
1514
|
|
1318
1515
|
if default_options_group:
|
1319
1516
|
# check if the arguments are required, group accordingly
|
@@ -1350,12 +1547,9 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
|
|
1350
1547
|
# determine help from format above
|
1351
1548
|
return formatter.format_help() + '\n'
|
1352
1549
|
|
1353
|
-
def
|
1354
|
-
|
1355
|
-
|
1356
|
-
if file is None:
|
1357
|
-
file = sys.stderr
|
1358
|
-
ansi.style_aware_write(file, message)
|
1550
|
+
def create_text_group(self, title: str, text: RenderableType) -> TextGroup:
|
1551
|
+
"""Create a TextGroup using this parser's formatter creator."""
|
1552
|
+
return TextGroup(title, text, self._get_formatter)
|
1359
1553
|
|
1360
1554
|
|
1361
1555
|
class Cmd2AttributeWrapper:
|
@@ -1378,15 +1572,20 @@ class Cmd2AttributeWrapper:
|
|
1378
1572
|
self.__attribute = new_val
|
1379
1573
|
|
1380
1574
|
|
1381
|
-
#
|
1382
|
-
|
1575
|
+
# Parser type used by cmd2's built-in commands.
|
1576
|
+
# Set it using cmd2.set_default_argument_parser_type().
|
1577
|
+
DEFAULT_ARGUMENT_PARSER: type[Cmd2ArgumentParser] = Cmd2ArgumentParser
|
1578
|
+
|
1579
|
+
|
1580
|
+
def set_default_argument_parser_type(parser_type: type[Cmd2ArgumentParser]) -> None:
|
1581
|
+
"""Set the default ArgumentParser class for cmd2's built-in commands.
|
1383
1582
|
|
1583
|
+
Since built-in commands rely on customizations made in Cmd2ArgumentParser,
|
1584
|
+
your custom parser class should inherit from Cmd2ArgumentParser.
|
1384
1585
|
|
1385
|
-
|
1386
|
-
"""Set the default ArgumentParser class for a cmd2 app.
|
1586
|
+
This should be called prior to instantiating your CLI object.
|
1387
1587
|
|
1388
|
-
|
1389
|
-
See examples/override_parser.py.
|
1588
|
+
See examples/custom_parser.py.
|
1390
1589
|
"""
|
1391
1590
|
global DEFAULT_ARGUMENT_PARSER # noqa: PLW0603
|
1392
1591
|
DEFAULT_ARGUMENT_PARSER = parser_type
|