cmd2 2.6.2__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 +359 -151
- cmd2/clipboard.py +1 -1
- cmd2/cmd2.py +1272 -845
- 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.6.2.dist-info → cmd2-3.0.0b1.dist-info}/METADATA +23 -44
- cmd2-3.0.0b1.dist-info/RECORD +27 -0
- cmd2/ansi.py +0 -1093
- cmd2/table_creator.py +0 -1122
- cmd2-2.6.2.dist-info/RECORD +0 -24
- {cmd2-2.6.2.dist-info → cmd2-3.0.0b1.dist-info}/WHEEL +0 -0
- {cmd2-2.6.2.dist-info → cmd2-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
- {cmd2-2.6.2.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.
|
@@ -229,26 +263,41 @@ from argparse import (
|
|
229
263
|
ZERO_OR_MORE,
|
230
264
|
ArgumentError,
|
231
265
|
)
|
232
|
-
from collections.abc import
|
233
|
-
|
234
|
-
|
266
|
+
from collections.abc import (
|
267
|
+
Callable,
|
268
|
+
Iterable,
|
269
|
+
Sequence,
|
235
270
|
)
|
271
|
+
from gettext import gettext
|
236
272
|
from typing import (
|
237
|
-
IO,
|
238
273
|
TYPE_CHECKING,
|
239
274
|
Any,
|
275
|
+
ClassVar,
|
240
276
|
NoReturn,
|
241
|
-
Optional,
|
242
277
|
Protocol,
|
243
|
-
Union,
|
244
278
|
cast,
|
245
279
|
runtime_checkable,
|
246
280
|
)
|
247
281
|
|
248
|
-
from . import (
|
249
|
-
|
250
|
-
|
282
|
+
from rich.console import (
|
283
|
+
Group,
|
284
|
+
RenderableType,
|
251
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,
|
295
|
+
)
|
296
|
+
|
297
|
+
from . import constants
|
298
|
+
from . import rich_utils as ru
|
299
|
+
from .rich_utils import Cmd2RichArgparseConsole
|
300
|
+
from .styles import Cmd2Style
|
252
301
|
|
253
302
|
if TYPE_CHECKING: # pragma: no cover
|
254
303
|
from .argparse_completer import (
|
@@ -275,6 +324,56 @@ def generate_range_error(range_min: int, range_max: float) -> str:
|
|
275
324
|
return err_str
|
276
325
|
|
277
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
|
+
|
278
377
|
class CompletionItem(str): # noqa: SLOT000
|
279
378
|
"""Completion item with descriptive text attached.
|
280
379
|
|
@@ -285,15 +384,22 @@ class CompletionItem(str): # noqa: SLOT000
|
|
285
384
|
"""Responsible for creating and returning a new instance, called before __init__ when an object is instantiated."""
|
286
385
|
return super().__new__(cls, value)
|
287
386
|
|
288
|
-
def __init__(self, value: object,
|
387
|
+
def __init__(self, value: object, descriptive_data: Sequence[Any], *args: Any) -> None:
|
289
388
|
"""CompletionItem Initializer.
|
290
389
|
|
291
390
|
:param value: the value being tab completed
|
292
|
-
: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.
|
293
394
|
:param args: args for str __init__
|
294
395
|
"""
|
295
396
|
super().__init__(*args)
|
296
|
-
|
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)
|
297
403
|
|
298
404
|
# Save the original value to support CompletionItems as argparse choices.
|
299
405
|
# cmd2 has patched argparse so input is compared to this value instead of the CompletionItem instance.
|
@@ -326,7 +432,7 @@ class ChoicesProviderFuncWithTokens(Protocol):
|
|
326
432
|
"""Enable instances to be called like functions."""
|
327
433
|
|
328
434
|
|
329
|
-
ChoicesProviderFunc =
|
435
|
+
ChoicesProviderFunc = ChoicesProviderFuncBase | ChoicesProviderFuncWithTokens
|
330
436
|
|
331
437
|
|
332
438
|
@runtime_checkable
|
@@ -359,7 +465,7 @@ class CompleterFuncWithTokens(Protocol):
|
|
359
465
|
"""Enable instances to be called like functions."""
|
360
466
|
|
361
467
|
|
362
|
-
CompleterFunc =
|
468
|
+
CompleterFunc = CompleterFuncBase | CompleterFuncWithTokens
|
363
469
|
|
364
470
|
|
365
471
|
class ChoicesCallable:
|
@@ -371,7 +477,7 @@ class ChoicesCallable:
|
|
371
477
|
def __init__(
|
372
478
|
self,
|
373
479
|
is_completer: bool,
|
374
|
-
to_call:
|
480
|
+
to_call: CompleterFunc | ChoicesProviderFunc,
|
375
481
|
) -> None:
|
376
482
|
"""Initialize the ChoiceCallable instance.
|
377
483
|
|
@@ -419,7 +525,7 @@ class ChoicesCallable:
|
|
419
525
|
ATTR_CHOICES_CALLABLE = 'choices_callable'
|
420
526
|
|
421
527
|
# Descriptive header that prints when using CompletionItems
|
422
|
-
|
528
|
+
ATTR_DESCRIPTIVE_HEADERS = 'descriptive_headers'
|
423
529
|
|
424
530
|
# A tuple specifying nargs as a range (min, max)
|
425
531
|
ATTR_NARGS_RANGE = 'nargs_range'
|
@@ -432,7 +538,7 @@ ATTR_SUPPRESS_TAB_HINT = 'suppress_tab_hint'
|
|
432
538
|
############################################################################################################
|
433
539
|
# Patch argparse.Action with accessors for choice_callable attribute
|
434
540
|
############################################################################################################
|
435
|
-
def _action_get_choices_callable(self: argparse.Action) ->
|
541
|
+
def _action_get_choices_callable(self: argparse.Action) -> ChoicesCallable | None:
|
436
542
|
"""Get the choices_callable attribute of an argparse Action.
|
437
543
|
|
438
544
|
This function is added by cmd2 as a method called ``get_choices_callable()`` to ``argparse.Action`` class.
|
@@ -442,7 +548,7 @@ def _action_get_choices_callable(self: argparse.Action) -> Optional[ChoicesCalla
|
|
442
548
|
:param self: argparse Action being queried
|
443
549
|
:return: A ChoicesCallable instance or None if attribute does not exist
|
444
550
|
"""
|
445
|
-
return cast(
|
551
|
+
return cast(ChoicesCallable | None, getattr(self, ATTR_CHOICES_CALLABLE, None))
|
446
552
|
|
447
553
|
|
448
554
|
setattr(argparse.Action, 'get_choices_callable', _action_get_choices_callable)
|
@@ -516,44 +622,44 @@ setattr(argparse.Action, 'set_completer', _action_set_completer)
|
|
516
622
|
|
517
623
|
|
518
624
|
############################################################################################################
|
519
|
-
# Patch argparse.Action with accessors for
|
625
|
+
# Patch argparse.Action with accessors for descriptive_headers attribute
|
520
626
|
############################################################################################################
|
521
|
-
def
|
522
|
-
"""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.
|
523
629
|
|
524
|
-
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.
|
525
631
|
|
526
|
-
To call: ``action.
|
632
|
+
To call: ``action.get_descriptive_headers()``
|
527
633
|
|
528
634
|
:param self: argparse Action being queried
|
529
|
-
:return: The value of
|
635
|
+
:return: The value of descriptive_headers or None if attribute does not exist
|
530
636
|
"""
|
531
|
-
return cast(
|
637
|
+
return cast(Sequence[str | Column] | None, getattr(self, ATTR_DESCRIPTIVE_HEADERS, None))
|
532
638
|
|
533
639
|
|
534
|
-
setattr(argparse.Action, '
|
640
|
+
setattr(argparse.Action, 'get_descriptive_headers', _action_get_descriptive_headers)
|
535
641
|
|
536
642
|
|
537
|
-
def
|
538
|
-
"""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.
|
539
645
|
|
540
|
-
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.
|
541
647
|
|
542
|
-
To call: ``action.
|
648
|
+
To call: ``action.set_descriptive_headers(descriptive_headers)``
|
543
649
|
|
544
650
|
:param self: argparse Action being updated
|
545
|
-
:param
|
651
|
+
:param descriptive_headers: value being assigned
|
546
652
|
"""
|
547
|
-
setattr(self,
|
653
|
+
setattr(self, ATTR_DESCRIPTIVE_HEADERS, descriptive_headers)
|
548
654
|
|
549
655
|
|
550
|
-
setattr(argparse.Action, '
|
656
|
+
setattr(argparse.Action, 'set_descriptive_headers', _action_set_descriptive_headers)
|
551
657
|
|
552
658
|
|
553
659
|
############################################################################################################
|
554
660
|
# Patch argparse.Action with accessors for nargs_range attribute
|
555
661
|
############################################################################################################
|
556
|
-
def _action_get_nargs_range(self: argparse.Action) ->
|
662
|
+
def _action_get_nargs_range(self: argparse.Action) -> tuple[int, int | float] | None:
|
557
663
|
"""Get the nargs_range attribute of an argparse Action.
|
558
664
|
|
559
665
|
This function is added by cmd2 as a method called ``get_nargs_range()`` to ``argparse.Action`` class.
|
@@ -563,13 +669,13 @@ def _action_get_nargs_range(self: argparse.Action) -> Optional[tuple[int, Union[
|
|
563
669
|
:param self: argparse Action being queried
|
564
670
|
:return: The value of nargs_range or None if attribute does not exist
|
565
671
|
"""
|
566
|
-
return cast(
|
672
|
+
return cast(tuple[int, int | float] | None, getattr(self, ATTR_NARGS_RANGE, None))
|
567
673
|
|
568
674
|
|
569
675
|
setattr(argparse.Action, 'get_nargs_range', _action_get_nargs_range)
|
570
676
|
|
571
677
|
|
572
|
-
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:
|
573
679
|
"""Set the nargs_range attribute of an argparse Action.
|
574
680
|
|
575
681
|
This function is added by cmd2 as a method called ``set_nargs_range()`` to ``argparse.Action`` class.
|
@@ -628,7 +734,7 @@ CUSTOM_ACTION_ATTRIBS: set[str] = set()
|
|
628
734
|
_CUSTOM_ATTRIB_PFX = '_attr_'
|
629
735
|
|
630
736
|
|
631
|
-
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:
|
632
738
|
"""Register a custom argparse argument parameter.
|
633
739
|
|
634
740
|
The registered name will then be a recognized keyword parameter to the parser's `add_argument()` function.
|
@@ -694,11 +800,11 @@ orig_actions_container_add_argument = argparse._ActionsContainer.add_argument
|
|
694
800
|
def _add_argument_wrapper(
|
695
801
|
self: argparse._ActionsContainer,
|
696
802
|
*args: Any,
|
697
|
-
nargs:
|
698
|
-
choices_provider:
|
699
|
-
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,
|
700
806
|
suppress_tab_hint: bool = False,
|
701
|
-
|
807
|
+
descriptive_headers: list[Column | str] | None = None,
|
702
808
|
**kwargs: Any,
|
703
809
|
) -> argparse.Action:
|
704
810
|
"""Wrap ActionsContainer.add_argument() which supports more settings used by cmd2.
|
@@ -718,8 +824,8 @@ def _add_argument_wrapper(
|
|
718
824
|
current argument's help text as a hint. Set this to True to suppress the hint. If this
|
719
825
|
argument's help text is set to argparse.SUPPRESS, then tab hints will not display
|
720
826
|
regardless of the value passed for suppress_tab_hint. Defaults to False.
|
721
|
-
:param
|
722
|
-
|
827
|
+
:param descriptive_headers: if the provided choices are CompletionItems, then these are the headers
|
828
|
+
of the descriptive data. Defaults to None.
|
723
829
|
|
724
830
|
# Args from original function
|
725
831
|
:param kwargs: keyword-arguments recognized by argparse._ActionsContainer.add_argument
|
@@ -744,7 +850,7 @@ def _add_argument_wrapper(
|
|
744
850
|
nargs_range = None
|
745
851
|
|
746
852
|
if nargs is not None:
|
747
|
-
nargs_adjusted:
|
853
|
+
nargs_adjusted: int | str | tuple[int] | tuple[int, int] | tuple[int, float] | None
|
748
854
|
# Check if nargs was given as a range
|
749
855
|
if isinstance(nargs, tuple):
|
750
856
|
# Handle 1-item tuple by setting max to INFINITY
|
@@ -754,11 +860,11 @@ def _add_argument_wrapper(
|
|
754
860
|
# Validate nargs tuple
|
755
861
|
if (
|
756
862
|
len(nargs) != 2
|
757
|
-
or not isinstance(nargs[0], int)
|
758
|
-
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)
|
759
865
|
):
|
760
866
|
raise ValueError('Ranged values for nargs must be a tuple of 1 or 2 integers')
|
761
|
-
if nargs[0] >= nargs[1]:
|
867
|
+
if nargs[0] >= nargs[1]:
|
762
868
|
raise ValueError('Invalid nargs range. The first value must be less than the second')
|
763
869
|
if nargs[0] < 0:
|
764
870
|
raise ValueError('Negative numbers are invalid for nargs range')
|
@@ -766,7 +872,7 @@ def _add_argument_wrapper(
|
|
766
872
|
# Save the nargs tuple as our range setting
|
767
873
|
nargs_range = nargs
|
768
874
|
range_min = nargs_range[0]
|
769
|
-
range_max = nargs_range[1]
|
875
|
+
range_max = nargs_range[1]
|
770
876
|
|
771
877
|
# Convert nargs into a format argparse recognizes
|
772
878
|
if range_min == 0:
|
@@ -802,7 +908,7 @@ def _add_argument_wrapper(
|
|
802
908
|
new_arg = orig_actions_container_add_argument(self, *args, **kwargs)
|
803
909
|
|
804
910
|
# Set the custom attributes
|
805
|
-
new_arg.set_nargs_range(nargs_range) # type: ignore[
|
911
|
+
new_arg.set_nargs_range(nargs_range) # type: ignore[attr-defined]
|
806
912
|
|
807
913
|
if choices_provider:
|
808
914
|
new_arg.set_choices_provider(choices_provider) # type: ignore[attr-defined]
|
@@ -810,7 +916,7 @@ def _add_argument_wrapper(
|
|
810
916
|
new_arg.set_completer(completer) # type: ignore[attr-defined]
|
811
917
|
|
812
918
|
new_arg.set_suppress_tab_hint(suppress_tab_hint) # type: ignore[attr-defined]
|
813
|
-
new_arg.
|
919
|
+
new_arg.set_descriptive_headers(descriptive_headers) # type: ignore[attr-defined]
|
814
920
|
|
815
921
|
for keyword, value in custom_attribs.items():
|
816
922
|
attr_setter = getattr(new_arg, f'set_{keyword}', None)
|
@@ -885,7 +991,7 @@ setattr(argparse.ArgumentParser, '_match_argument', _match_argument_wrapper)
|
|
885
991
|
ATTR_AP_COMPLETER_TYPE = 'ap_completer_type'
|
886
992
|
|
887
993
|
|
888
|
-
def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) ->
|
994
|
+
def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> type['ArgparseCompleter'] | None: # noqa: N802
|
889
995
|
"""Get the ap_completer_type attribute of an argparse ArgumentParser.
|
890
996
|
|
891
997
|
This function is added by cmd2 as a method called ``get_ap_completer_type()`` to ``argparse.ArgumentParser`` class.
|
@@ -895,7 +1001,7 @@ def _ArgumentParser_get_ap_completer_type(self: argparse.ArgumentParser) -> Opti
|
|
895
1001
|
:param self: ArgumentParser being queried
|
896
1002
|
:return: An ArgparseCompleter-based class or None if attribute does not exist
|
897
1003
|
"""
|
898
|
-
return cast(
|
1004
|
+
return cast(type['ArgparseCompleter'] | None, getattr(self, ATTR_AP_COMPLETER_TYPE, None))
|
899
1005
|
|
900
1006
|
|
901
1007
|
setattr(argparse.ArgumentParser, 'get_ap_completer_type', _ArgumentParser_get_ap_completer_type)
|
@@ -991,15 +1097,43 @@ setattr(argparse._SubParsersAction, 'remove_parser', _SubParsersAction_remove_pa
|
|
991
1097
|
############################################################################################################
|
992
1098
|
|
993
1099
|
|
994
|
-
class Cmd2HelpFormatter(
|
1100
|
+
class Cmd2HelpFormatter(RichHelpFormatter):
|
995
1101
|
"""Custom help formatter to configure ordering of help text."""
|
996
1102
|
|
1103
|
+
# Disable automatic highlighting in the help text.
|
1104
|
+
highlights: ClassVar[list[str]] = []
|
1105
|
+
|
1106
|
+
# Disable markup rendering in usage, help, description, and epilog text.
|
1107
|
+
# cmd2's built-in commands do not escape opening brackets in their help text
|
1108
|
+
# and therefore rely on these settings being False. If you desire to use
|
1109
|
+
# markup in your help text, inherit from Cmd2HelpFormatter and override
|
1110
|
+
# these settings in that child class.
|
1111
|
+
usage_markup: ClassVar[bool] = False
|
1112
|
+
help_markup: ClassVar[bool] = False
|
1113
|
+
text_markup: ClassVar[bool] = False
|
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
|
+
|
997
1131
|
def _format_usage(
|
998
1132
|
self,
|
999
|
-
usage:
|
1133
|
+
usage: str | None,
|
1000
1134
|
actions: Iterable[argparse.Action],
|
1001
1135
|
groups: Iterable[argparse._ArgumentGroup],
|
1002
|
-
prefix:
|
1136
|
+
prefix: str | None = None,
|
1003
1137
|
) -> str:
|
1004
1138
|
if prefix is None:
|
1005
1139
|
prefix = gettext('Usage: ')
|
@@ -1053,7 +1187,7 @@ class Cmd2HelpFormatter(argparse.RawTextHelpFormatter):
|
|
1053
1187
|
# End cmd2 customization
|
1054
1188
|
|
1055
1189
|
# helper for wrapping lines
|
1056
|
-
def get_lines(parts: list[str], indent: str, prefix:
|
1190
|
+
def get_lines(parts: list[str], indent: str, prefix: str | None = None) -> list[str]:
|
1057
1191
|
lines: list[str] = []
|
1058
1192
|
line: list[str] = []
|
1059
1193
|
line_len = len(prefix) - 1 if prefix is not None else len(indent) - 1
|
@@ -1133,8 +1267,8 @@ class Cmd2HelpFormatter(argparse.RawTextHelpFormatter):
|
|
1133
1267
|
def _determine_metavar(
|
1134
1268
|
self,
|
1135
1269
|
action: argparse.Action,
|
1136
|
-
default_metavar:
|
1137
|
-
) ->
|
1270
|
+
default_metavar: str,
|
1271
|
+
) -> str | tuple[str, ...]:
|
1138
1272
|
"""Determine what to use as the metavar value of an action."""
|
1139
1273
|
if action.metavar is not None:
|
1140
1274
|
result = action.metavar
|
@@ -1150,7 +1284,7 @@ class Cmd2HelpFormatter(argparse.RawTextHelpFormatter):
|
|
1150
1284
|
def _metavar_formatter(
|
1151
1285
|
self,
|
1152
1286
|
action: argparse.Action,
|
1153
|
-
default_metavar:
|
1287
|
+
default_metavar: str,
|
1154
1288
|
) -> Callable[[int], tuple[str, ...]]:
|
1155
1289
|
metavar = self._determine_metavar(action, default_metavar)
|
1156
1290
|
|
@@ -1161,7 +1295,7 @@ class Cmd2HelpFormatter(argparse.RawTextHelpFormatter):
|
|
1161
1295
|
|
1162
1296
|
return format_tuple
|
1163
1297
|
|
1164
|
-
def _format_args(self, action: argparse.Action, default_metavar:
|
1298
|
+
def _format_args(self, action: argparse.Action, default_metavar: str) -> str:
|
1165
1299
|
"""Handle ranged nargs and make other output less verbose."""
|
1166
1300
|
metavar = self._determine_metavar(action, default_metavar)
|
1167
1301
|
metavar_formatter = self._metavar_formatter(action, default_metavar)
|
@@ -1186,20 +1320,93 @@ class Cmd2HelpFormatter(argparse.RawTextHelpFormatter):
|
|
1186
1320
|
return super()._format_args(action, default_metavar) # type: ignore[arg-type]
|
1187
1321
|
|
1188
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
|
+
|
1189
1396
|
class Cmd2ArgumentParser(argparse.ArgumentParser):
|
1190
1397
|
"""Custom ArgumentParser class that improves error and help output."""
|
1191
1398
|
|
1192
1399
|
def __init__(
|
1193
1400
|
self,
|
1194
|
-
prog:
|
1195
|
-
usage:
|
1196
|
-
description:
|
1197
|
-
epilog:
|
1401
|
+
prog: str | None = None,
|
1402
|
+
usage: str | None = None,
|
1403
|
+
description: RenderableType | None = None,
|
1404
|
+
epilog: RenderableType | None = None,
|
1198
1405
|
parents: Sequence[argparse.ArgumentParser] = (),
|
1199
|
-
formatter_class: type[
|
1406
|
+
formatter_class: type[Cmd2HelpFormatter] = Cmd2HelpFormatter,
|
1200
1407
|
prefix_chars: str = '-',
|
1201
|
-
fromfile_prefix_chars:
|
1202
|
-
argument_default:
|
1408
|
+
fromfile_prefix_chars: str | None = None,
|
1409
|
+
argument_default: str | None = None,
|
1203
1410
|
conflict_handler: str = 'error',
|
1204
1411
|
add_help: bool = True,
|
1205
1412
|
allow_abbrev: bool = True,
|
@@ -1207,7 +1414,7 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
|
|
1207
1414
|
suggest_on_error: bool = False,
|
1208
1415
|
color: bool = False,
|
1209
1416
|
*,
|
1210
|
-
ap_completer_type:
|
1417
|
+
ap_completer_type: type['ArgparseCompleter'] | None = None,
|
1211
1418
|
) -> None:
|
1212
1419
|
"""Initialize the Cmd2ArgumentParser instance, a custom ArgumentParser added by cmd2.
|
1213
1420
|
|
@@ -1215,42 +1422,34 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
|
|
1215
1422
|
behavior on this parser. If this is None or not present, then cmd2 will use
|
1216
1423
|
argparse_completer.DEFAULT_AP_COMPLETER when tab completing this parser's arguments
|
1217
1424
|
"""
|
1425
|
+
kwargs: dict[str, bool] = {}
|
1218
1426
|
if sys.version_info >= (3, 14):
|
1219
1427
|
# Python >= 3.14 so pass new arguments to parent argparse.ArgumentParser class
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
# Python
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
formatter_class=formatter_class, # type: ignore[arg-type]
|
1246
|
-
prefix_chars=prefix_chars,
|
1247
|
-
fromfile_prefix_chars=fromfile_prefix_chars,
|
1248
|
-
argument_default=argument_default,
|
1249
|
-
conflict_handler=conflict_handler,
|
1250
|
-
add_help=add_help,
|
1251
|
-
allow_abbrev=allow_abbrev,
|
1252
|
-
exit_on_error=exit_on_error, # added in Python 3.9
|
1253
|
-
)
|
1428
|
+
kwargs = {
|
1429
|
+
"suggest_on_error": suggest_on_error,
|
1430
|
+
"color": color,
|
1431
|
+
}
|
1432
|
+
|
1433
|
+
super().__init__(
|
1434
|
+
prog=prog,
|
1435
|
+
usage=usage,
|
1436
|
+
description=description, # type: ignore[arg-type]
|
1437
|
+
epilog=epilog, # type: ignore[arg-type]
|
1438
|
+
parents=parents if parents else [],
|
1439
|
+
formatter_class=formatter_class,
|
1440
|
+
prefix_chars=prefix_chars,
|
1441
|
+
fromfile_prefix_chars=fromfile_prefix_chars,
|
1442
|
+
argument_default=argument_default,
|
1443
|
+
conflict_handler=conflict_handler,
|
1444
|
+
add_help=add_help,
|
1445
|
+
allow_abbrev=allow_abbrev,
|
1446
|
+
exit_on_error=exit_on_error, # added in Python 3.9
|
1447
|
+
**kwargs, # added in Python 3.14
|
1448
|
+
)
|
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]
|
1254
1453
|
|
1255
1454
|
self.set_ap_completer_type(ap_completer_type) # type: ignore[attr-defined]
|
1256
1455
|
|
@@ -1281,8 +1480,18 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
|
|
1281
1480
|
formatted_message += '\n ' + line
|
1282
1481
|
|
1283
1482
|
self.print_usage(sys.stderr)
|
1284
|
-
|
1285
|
-
|
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())
|
1286
1495
|
|
1287
1496
|
def format_help(self) -> str:
|
1288
1497
|
"""Return a string containing a help message, including the program usage and information about the arguments.
|
@@ -1292,7 +1501,7 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
|
|
1292
1501
|
formatter = self._get_formatter()
|
1293
1502
|
|
1294
1503
|
# usage
|
1295
|
-
formatter.add_usage(self.usage, self._actions, self._mutually_exclusive_groups)
|
1504
|
+
formatter.add_usage(self.usage, self._actions, self._mutually_exclusive_groups)
|
1296
1505
|
|
1297
1506
|
# description
|
1298
1507
|
formatter.add_text(self.description)
|
@@ -1301,10 +1510,7 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
|
|
1301
1510
|
|
1302
1511
|
# positionals, optionals and user-defined groups
|
1303
1512
|
for action_group in self._action_groups:
|
1304
|
-
|
1305
|
-
default_options_group = action_group.title == 'options'
|
1306
|
-
else:
|
1307
|
-
default_options_group = action_group.title == 'optional arguments'
|
1513
|
+
default_options_group = action_group.title == 'options'
|
1308
1514
|
|
1309
1515
|
if default_options_group:
|
1310
1516
|
# check if the arguments are required, group accordingly
|
@@ -1341,12 +1547,9 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
|
|
1341
1547
|
# determine help from format above
|
1342
1548
|
return formatter.format_help() + '\n'
|
1343
1549
|
|
1344
|
-
def
|
1345
|
-
|
1346
|
-
|
1347
|
-
if file is None:
|
1348
|
-
file = sys.stderr
|
1349
|
-
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)
|
1350
1553
|
|
1351
1554
|
|
1352
1555
|
class Cmd2AttributeWrapper:
|
@@ -1369,15 +1572,20 @@ class Cmd2AttributeWrapper:
|
|
1369
1572
|
self.__attribute = new_val
|
1370
1573
|
|
1371
1574
|
|
1372
|
-
#
|
1373
|
-
|
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.
|
1374
1582
|
|
1583
|
+
Since built-in commands rely on customizations made in Cmd2ArgumentParser,
|
1584
|
+
your custom parser class should inherit from Cmd2ArgumentParser.
|
1375
1585
|
|
1376
|
-
|
1377
|
-
"""Set the default ArgumentParser class for a cmd2 app.
|
1586
|
+
This should be called prior to instantiating your CLI object.
|
1378
1587
|
|
1379
|
-
|
1380
|
-
See examples/override_parser.py.
|
1588
|
+
See examples/custom_parser.py.
|
1381
1589
|
"""
|
1382
1590
|
global DEFAULT_ARGUMENT_PARSER # noqa: PLW0603
|
1383
1591
|
DEFAULT_ARGUMENT_PARSER = parser_type
|