cmd2 3.0.0rc1__py3-none-any.whl → 3.1.1__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/argparse_completer.py +1 -1
- cmd2/argparse_custom.py +59 -22
- cmd2/cmd2.py +66 -46
- cmd2/parsing.py +6 -4
- cmd2/py_bridge.py +1 -1
- cmd2/rich_utils.py +1 -74
- cmd2/styles.py +2 -4
- cmd2/transcript.py +1 -1
- {cmd2-3.0.0rc1.dist-info → cmd2-3.1.1.dist-info}/METADATA +9 -8
- {cmd2-3.0.0rc1.dist-info → cmd2-3.1.1.dist-info}/RECORD +13 -13
- {cmd2-3.0.0rc1.dist-info → cmd2-3.1.1.dist-info}/WHEEL +1 -1
- cmd2-3.1.1.dist-info/licenses/LICENSE +18 -0
- cmd2-3.0.0rc1.dist-info/licenses/LICENSE +0 -21
- {cmd2-3.0.0rc1.dist-info → cmd2-3.1.1.dist-info}/top_level.txt +0 -0
cmd2/argparse_completer.py
CHANGED
|
@@ -39,7 +39,7 @@ from .exceptions import CompletionError
|
|
|
39
39
|
from .styles import Cmd2Style
|
|
40
40
|
|
|
41
41
|
# If no descriptive headers are supplied, then this will be used instead
|
|
42
|
-
DEFAULT_DESCRIPTIVE_HEADERS: Sequence[str | Column] =
|
|
42
|
+
DEFAULT_DESCRIPTIVE_HEADERS: Sequence[str | Column] = ['Description']
|
|
43
43
|
|
|
44
44
|
# Name of the choice/completer function argument that, if present, will be passed a dictionary of
|
|
45
45
|
# command line tokens up through the token being completed mapped to their argparse destination name.
|
cmd2/argparse_custom.py
CHANGED
|
@@ -258,14 +258,11 @@ sub-parser from a sub-parsers group. See _SubParsersAction_remove_parser` for mo
|
|
|
258
258
|
import argparse
|
|
259
259
|
import re
|
|
260
260
|
import sys
|
|
261
|
-
from argparse import
|
|
262
|
-
ONE_OR_MORE,
|
|
263
|
-
ZERO_OR_MORE,
|
|
264
|
-
ArgumentError,
|
|
265
|
-
)
|
|
261
|
+
from argparse import ArgumentError
|
|
266
262
|
from collections.abc import (
|
|
267
263
|
Callable,
|
|
268
264
|
Iterable,
|
|
265
|
+
Iterator,
|
|
269
266
|
Sequence,
|
|
270
267
|
)
|
|
271
268
|
from gettext import gettext
|
|
@@ -293,6 +290,7 @@ from rich_argparse import (
|
|
|
293
290
|
RawTextRichHelpFormatter,
|
|
294
291
|
RichHelpFormatter,
|
|
295
292
|
)
|
|
293
|
+
from typing_extensions import Self
|
|
296
294
|
|
|
297
295
|
from . import constants
|
|
298
296
|
from . import rich_utils as ru
|
|
@@ -380,7 +378,7 @@ class CompletionItem(str): # noqa: SLOT000
|
|
|
380
378
|
See header of this file for more information
|
|
381
379
|
"""
|
|
382
380
|
|
|
383
|
-
def __new__(cls, value: object, *_args: Any, **_kwargs: Any) ->
|
|
381
|
+
def __new__(cls, value: object, *_args: Any, **_kwargs: Any) -> Self:
|
|
384
382
|
"""Responsible for creating and returning a new instance, called before __init__ when an object is instantiated."""
|
|
385
383
|
return super().__new__(cls, value)
|
|
386
384
|
|
|
@@ -804,7 +802,7 @@ def _add_argument_wrapper(
|
|
|
804
802
|
choices_provider: ChoicesProviderFunc | None = None,
|
|
805
803
|
completer: CompleterFunc | None = None,
|
|
806
804
|
suppress_tab_hint: bool = False,
|
|
807
|
-
descriptive_headers:
|
|
805
|
+
descriptive_headers: Sequence[str | Column] | None = None,
|
|
808
806
|
**kwargs: Any,
|
|
809
807
|
) -> argparse.Action:
|
|
810
808
|
"""Wrap ActionsContainer.add_argument() which supports more settings used by cmd2.
|
|
@@ -1295,29 +1293,68 @@ class Cmd2HelpFormatter(RichHelpFormatter):
|
|
|
1295
1293
|
|
|
1296
1294
|
return format_tuple
|
|
1297
1295
|
|
|
1296
|
+
def _build_nargs_range_str(self, nargs_range: tuple[int, int | float]) -> str:
|
|
1297
|
+
"""Generate nargs range string for help text."""
|
|
1298
|
+
if nargs_range[1] == constants.INFINITY:
|
|
1299
|
+
# {min+}
|
|
1300
|
+
range_str = f"{{{nargs_range[0]}+}}"
|
|
1301
|
+
else:
|
|
1302
|
+
# {min..max}
|
|
1303
|
+
range_str = f"{{{nargs_range[0]}..{nargs_range[1]}}}"
|
|
1304
|
+
|
|
1305
|
+
return range_str
|
|
1306
|
+
|
|
1298
1307
|
def _format_args(self, action: argparse.Action, default_metavar: str) -> str:
|
|
1299
|
-
"""
|
|
1308
|
+
"""Override to handle cmd2's custom nargs formatting.
|
|
1309
|
+
|
|
1310
|
+
All formats in this function need to be handled by _rich_metavar_parts().
|
|
1311
|
+
"""
|
|
1300
1312
|
metavar = self._determine_metavar(action, default_metavar)
|
|
1301
1313
|
metavar_formatter = self._metavar_formatter(action, default_metavar)
|
|
1302
1314
|
|
|
1303
1315
|
# Handle nargs specified as a range
|
|
1304
1316
|
nargs_range = action.get_nargs_range() # type: ignore[attr-defined]
|
|
1305
1317
|
if nargs_range is not None:
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
return
|
|
1309
|
-
|
|
1310
|
-
#
|
|
1311
|
-
#
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1318
|
+
arg_str = '%s' % metavar_formatter(1) # noqa: UP031
|
|
1319
|
+
range_str = self._build_nargs_range_str(nargs_range)
|
|
1320
|
+
return f"{arg_str}{range_str}"
|
|
1321
|
+
|
|
1322
|
+
# When nargs is just a number, argparse repeats the arg in the help text.
|
|
1323
|
+
# For instance, when nargs=5 the help text looks like: 'command arg arg arg arg arg'.
|
|
1324
|
+
# To make this less verbose, format it like: 'command arg{5}'.
|
|
1325
|
+
# Do not customize the output when metavar is a tuple of strings. Allow argparse's
|
|
1326
|
+
# formatter to handle that instead.
|
|
1327
|
+
if isinstance(metavar, str) and isinstance(action.nargs, int) and action.nargs > 1:
|
|
1328
|
+
arg_str = '%s' % metavar_formatter(1) # noqa: UP031
|
|
1329
|
+
return f"{arg_str}{{{action.nargs}}}"
|
|
1330
|
+
|
|
1331
|
+
# Fallback to parent for all other cases
|
|
1332
|
+
return super()._format_args(action, default_metavar)
|
|
1333
|
+
|
|
1334
|
+
def _rich_metavar_parts(
|
|
1335
|
+
self,
|
|
1336
|
+
action: argparse.Action,
|
|
1337
|
+
default_metavar: str,
|
|
1338
|
+
) -> Iterator[tuple[str, bool]]:
|
|
1339
|
+
"""Override to handle all cmd2-specific formatting in _format_args()."""
|
|
1340
|
+
metavar = self._determine_metavar(action, default_metavar)
|
|
1341
|
+
metavar_formatter = self._metavar_formatter(action, default_metavar)
|
|
1319
1342
|
|
|
1320
|
-
|
|
1343
|
+
# Handle nargs specified as a range
|
|
1344
|
+
nargs_range = action.get_nargs_range() # type: ignore[attr-defined]
|
|
1345
|
+
if nargs_range is not None:
|
|
1346
|
+
yield "%s" % metavar_formatter(1), True # noqa: UP031
|
|
1347
|
+
yield self._build_nargs_range_str(nargs_range), False
|
|
1348
|
+
return
|
|
1349
|
+
|
|
1350
|
+
# Handle specific integer nargs (e.g., nargs=5 -> arg{5})
|
|
1351
|
+
if isinstance(metavar, str) and isinstance(action.nargs, int) and action.nargs > 1:
|
|
1352
|
+
yield "%s" % metavar_formatter(1), True # noqa: UP031
|
|
1353
|
+
yield f"{{{action.nargs}}}", False
|
|
1354
|
+
return
|
|
1355
|
+
|
|
1356
|
+
# Fallback to parent for all other cases
|
|
1357
|
+
yield from super()._rich_metavar_parts(action, default_metavar)
|
|
1321
1358
|
|
|
1322
1359
|
|
|
1323
1360
|
class RawDescriptionCmd2HelpFormatter(
|
cmd2/cmd2.py
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
"""cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python.
|
|
2
|
+
|
|
3
|
+
cmd2 is a tool for building interactive command line applications in Python. Its goal is to make it quick and easy for
|
|
4
|
+
developers to build feature-rich and user-friendly interactive command line applications. It provides a simple API which
|
|
5
|
+
is an extension of Python's built-in cmd module. cmd2 provides a wealth of features on top of cmd to make your life easier
|
|
6
|
+
and eliminates much of the boilerplate code which would be necessary when using cmd.
|
|
7
|
+
|
|
8
|
+
Extra features include:
|
|
9
|
+
- Searchable command history (commands: "history")
|
|
10
|
+
- Run commands from file, save to file, edit commands in file
|
|
11
|
+
- Multi-line commands
|
|
12
|
+
- Special-character shortcut commands (beyond cmd's "?" and "!")
|
|
13
|
+
- Settable environment parameters
|
|
14
|
+
- Parsing commands with `argparse` argument parsers (flags)
|
|
15
|
+
- Redirection to file or paste buffer (clipboard) with > or >>
|
|
16
|
+
- Easy transcript-based testing of applications (see examples/transcript_example.py)
|
|
17
|
+
- Bash-style ``select`` available
|
|
15
18
|
|
|
16
19
|
Note, if self.stdout is different than sys.stdout, then redirection with > and |
|
|
17
20
|
will only work if `self.poutput()` is used in place of `print`.
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Git repository on GitHub at https://github.com/python-cmd2/cmd2
|
|
22
|
+
GitHub: https://github.com/python-cmd2/cmd2
|
|
23
|
+
Documentation: https://cmd2.readthedocs.io/
|
|
22
24
|
"""
|
|
23
25
|
|
|
24
26
|
# This module has many imports, quite a few of which are only
|
|
@@ -26,7 +28,6 @@ Git repository on GitHub at https://github.com/python-cmd2/cmd2
|
|
|
26
28
|
# import this module, many of these imports are lazy-loaded
|
|
27
29
|
# i.e. we only import the module when we use it.
|
|
28
30
|
import argparse
|
|
29
|
-
import cmd
|
|
30
31
|
import contextlib
|
|
31
32
|
import copy
|
|
32
33
|
import functools
|
|
@@ -64,7 +65,7 @@ from typing import (
|
|
|
64
65
|
)
|
|
65
66
|
|
|
66
67
|
import rich.box
|
|
67
|
-
from rich.console import Group
|
|
68
|
+
from rich.console import Group, RenderableType
|
|
68
69
|
from rich.highlighter import ReprHighlighter
|
|
69
70
|
from rich.rule import Rule
|
|
70
71
|
from rich.style import Style, StyleType
|
|
@@ -286,7 +287,7 @@ class _CommandParsers:
|
|
|
286
287
|
del self._parsers[full_method_name]
|
|
287
288
|
|
|
288
289
|
|
|
289
|
-
class Cmd
|
|
290
|
+
class Cmd:
|
|
290
291
|
"""An easy but powerful framework for writing line-oriented command interpreters.
|
|
291
292
|
|
|
292
293
|
Extends the Python Standard Library's cmd package by adding a lot of useful features
|
|
@@ -304,6 +305,8 @@ class Cmd(cmd.Cmd):
|
|
|
304
305
|
# List for storing transcript test file names
|
|
305
306
|
testfiles: ClassVar[list[str]] = []
|
|
306
307
|
|
|
308
|
+
DEFAULT_PROMPT = '(Cmd) '
|
|
309
|
+
|
|
307
310
|
def __init__(
|
|
308
311
|
self,
|
|
309
312
|
completekey: str = 'tab',
|
|
@@ -326,6 +329,7 @@ class Cmd(cmd.Cmd):
|
|
|
326
329
|
auto_load_commands: bool = False,
|
|
327
330
|
allow_clipboard: bool = True,
|
|
328
331
|
suggest_similar_command: bool = False,
|
|
332
|
+
intro: RenderableType = '',
|
|
329
333
|
) -> None:
|
|
330
334
|
"""Easy but powerful framework for writing line-oriented command interpreters, extends Python's cmd package.
|
|
331
335
|
|
|
@@ -376,6 +380,7 @@ class Cmd(cmd.Cmd):
|
|
|
376
380
|
:param suggest_similar_command: If ``True``, ``cmd2`` will attempt to suggest the most
|
|
377
381
|
similar command when the user types a command that does
|
|
378
382
|
not exist. Default: ``False``.
|
|
383
|
+
"param intro: Intro banner to print when starting the application.
|
|
379
384
|
"""
|
|
380
385
|
# Check if py or ipy need to be disabled in this instance
|
|
381
386
|
if not include_py:
|
|
@@ -384,11 +389,28 @@ class Cmd(cmd.Cmd):
|
|
|
384
389
|
setattr(self, 'do_ipy', None) # noqa: B010
|
|
385
390
|
|
|
386
391
|
# initialize plugin system
|
|
387
|
-
# needs to be done before we
|
|
392
|
+
# needs to be done before we most of the other stuff below
|
|
388
393
|
self._initialize_plugin_system()
|
|
389
394
|
|
|
390
|
-
#
|
|
391
|
-
|
|
395
|
+
# Configure a few defaults
|
|
396
|
+
self.prompt = Cmd.DEFAULT_PROMPT
|
|
397
|
+
self.intro = intro
|
|
398
|
+
self.use_rawinput = True
|
|
399
|
+
|
|
400
|
+
# What to use for standard input
|
|
401
|
+
if stdin is not None:
|
|
402
|
+
self.stdin = stdin
|
|
403
|
+
else:
|
|
404
|
+
self.stdin = sys.stdin
|
|
405
|
+
|
|
406
|
+
# What to use for standard output
|
|
407
|
+
if stdout is not None:
|
|
408
|
+
self.stdout = stdout
|
|
409
|
+
else:
|
|
410
|
+
self.stdout = sys.stdout
|
|
411
|
+
|
|
412
|
+
# Key used for tab completion
|
|
413
|
+
self.completekey = completekey
|
|
392
414
|
|
|
393
415
|
# Attributes which should NOT be dynamically settable via the set command at runtime
|
|
394
416
|
self.default_to_shell = False # Attempt to run unrecognized commands as shell commands
|
|
@@ -2412,9 +2434,7 @@ class Cmd(cmd.Cmd):
|
|
|
2412
2434
|
if len(self.completion_matches) == 1 and self.allow_closing_quote and completion_token_quote:
|
|
2413
2435
|
self.completion_matches[0] += completion_token_quote
|
|
2414
2436
|
|
|
2415
|
-
def complete(
|
|
2416
|
-
self, text: str, state: int, custom_settings: utils.CustomCompletionSettings | None = None
|
|
2417
|
-
) -> str | None:
|
|
2437
|
+
def complete(self, text: str, state: int, custom_settings: utils.CustomCompletionSettings | None = None) -> str | None:
|
|
2418
2438
|
"""Override of cmd's complete method which returns the next possible completion for 'text'.
|
|
2419
2439
|
|
|
2420
2440
|
This completer function is called by readline as complete(text, state), for state in 0, 1, 2, …,
|
|
@@ -2693,10 +2713,6 @@ class Cmd(cmd.Cmd):
|
|
|
2693
2713
|
def parseline(self, line: str) -> tuple[str, str, str]:
|
|
2694
2714
|
"""Parse the line into a command name and a string containing the arguments.
|
|
2695
2715
|
|
|
2696
|
-
NOTE: This is an override of a parent class method. It is only used by other parent class methods.
|
|
2697
|
-
|
|
2698
|
-
Different from the parent class method, this ignores self.identchars.
|
|
2699
|
-
|
|
2700
2716
|
:param line: line read by readline
|
|
2701
2717
|
:return: tuple containing (command, args, line)
|
|
2702
2718
|
"""
|
|
@@ -3086,7 +3102,7 @@ class Cmd(cmd.Cmd):
|
|
|
3086
3102
|
|
|
3087
3103
|
# Initialize the redirection saved state
|
|
3088
3104
|
redir_saved_state = utils.RedirectionSavedState(
|
|
3089
|
-
|
|
3105
|
+
self.stdout, stdouts_match, self._cur_pipe_proc_reader, self._redirecting
|
|
3090
3106
|
)
|
|
3091
3107
|
|
|
3092
3108
|
# The ProcReader for this command
|
|
@@ -3141,7 +3157,7 @@ class Cmd(cmd.Cmd):
|
|
|
3141
3157
|
new_stdout.close()
|
|
3142
3158
|
raise RedirectionError(f'Pipe process exited with code {proc.returncode} before command could run')
|
|
3143
3159
|
redir_saved_state.redirecting = True
|
|
3144
|
-
cmd_pipe_proc_reader = utils.ProcReader(proc,
|
|
3160
|
+
cmd_pipe_proc_reader = utils.ProcReader(proc, self.stdout, sys.stderr)
|
|
3145
3161
|
|
|
3146
3162
|
self.stdout = new_stdout
|
|
3147
3163
|
if stdouts_match:
|
|
@@ -3276,7 +3292,7 @@ class Cmd(cmd.Cmd):
|
|
|
3276
3292
|
|
|
3277
3293
|
return stop if stop is not None else False
|
|
3278
3294
|
|
|
3279
|
-
def default(self, statement: Statement) -> bool | None:
|
|
3295
|
+
def default(self, statement: Statement) -> bool | None:
|
|
3280
3296
|
"""Execute when the command given isn't a recognized command implemented by a do_* method.
|
|
3281
3297
|
|
|
3282
3298
|
:param statement: Statement object with parsed input
|
|
@@ -3293,6 +3309,15 @@ class Cmd(cmd.Cmd):
|
|
|
3293
3309
|
self.perror(err_msg, style=None)
|
|
3294
3310
|
return None
|
|
3295
3311
|
|
|
3312
|
+
def completedefault(self, *_ignored: list[str]) -> list[str]:
|
|
3313
|
+
"""Call to complete an input line when no command-specific complete_*() method is available.
|
|
3314
|
+
|
|
3315
|
+
This method is only called for non-argparse-based commands.
|
|
3316
|
+
|
|
3317
|
+
By default, it returns an empty list.
|
|
3318
|
+
"""
|
|
3319
|
+
return []
|
|
3320
|
+
|
|
3296
3321
|
def _suggest_similar_command(self, command: str) -> str | None:
|
|
3297
3322
|
return suggest_similar(command, self.get_visible_commands())
|
|
3298
3323
|
|
|
@@ -4131,10 +4156,6 @@ class Cmd(cmd.Cmd):
|
|
|
4131
4156
|
)
|
|
4132
4157
|
return help_parser
|
|
4133
4158
|
|
|
4134
|
-
# Get rid of cmd's complete_help() functions so ArgparseCompleter will complete the help command
|
|
4135
|
-
if getattr(cmd.Cmd, 'complete_help', None) is not None:
|
|
4136
|
-
delattr(cmd.Cmd, 'complete_help')
|
|
4137
|
-
|
|
4138
4159
|
@with_argparser(_build_help_parser)
|
|
4139
4160
|
def do_help(self, args: argparse.Namespace) -> None:
|
|
4140
4161
|
"""List available commands or provide detailed help for a specific command."""
|
|
@@ -4640,7 +4661,7 @@ class Cmd(cmd.Cmd):
|
|
|
4640
4661
|
**kwargs,
|
|
4641
4662
|
)
|
|
4642
4663
|
|
|
4643
|
-
proc_reader = utils.ProcReader(proc,
|
|
4664
|
+
proc_reader = utils.ProcReader(proc, self.stdout, sys.stderr)
|
|
4644
4665
|
proc_reader.wait()
|
|
4645
4666
|
|
|
4646
4667
|
# Save the return code of the application for use in a pyscript
|
|
@@ -5029,7 +5050,7 @@ class Cmd(cmd.Cmd):
|
|
|
5029
5050
|
history_action_group.add_argument('-e', '--edit', action='store_true', help='edit and then run selected history items')
|
|
5030
5051
|
history_action_group.add_argument(
|
|
5031
5052
|
'-o',
|
|
5032
|
-
'--
|
|
5053
|
+
'--output-file',
|
|
5033
5054
|
metavar='FILE',
|
|
5034
5055
|
help='output commands to a script file, implies -s',
|
|
5035
5056
|
completer=cls.path_complete,
|
|
@@ -5359,7 +5380,7 @@ class Cmd(cmd.Cmd):
|
|
|
5359
5380
|
transcript += command
|
|
5360
5381
|
|
|
5361
5382
|
# Use a StdSim object to capture output
|
|
5362
|
-
stdsim = utils.StdSim(
|
|
5383
|
+
stdsim = utils.StdSim(self.stdout)
|
|
5363
5384
|
self.stdout = cast(TextIO, stdsim)
|
|
5364
5385
|
|
|
5365
5386
|
# then run the command and let the output go into our buffer
|
|
@@ -5385,7 +5406,7 @@ class Cmd(cmd.Cmd):
|
|
|
5385
5406
|
with self.sigint_protection:
|
|
5386
5407
|
# Restore altered attributes to their original state
|
|
5387
5408
|
self.echo = saved_echo
|
|
5388
|
-
self.stdout =
|
|
5409
|
+
self.stdout = saved_stdout
|
|
5389
5410
|
|
|
5390
5411
|
# Check if all commands ran
|
|
5391
5412
|
if commands_run < len(history):
|
|
@@ -5880,11 +5901,10 @@ class Cmd(cmd.Cmd):
|
|
|
5880
5901
|
"""
|
|
5881
5902
|
self.perror(message_to_print, style=None)
|
|
5882
5903
|
|
|
5883
|
-
def cmdloop(self, intro:
|
|
5904
|
+
def cmdloop(self, intro: RenderableType = '') -> int:
|
|
5884
5905
|
"""Deal with extra features provided by cmd2, this is an outer wrapper around _cmdloop().
|
|
5885
5906
|
|
|
5886
|
-
_cmdloop() provides the main loop
|
|
5887
|
-
the following extra features provided by cmd2:
|
|
5907
|
+
_cmdloop() provides the main loop. This provides the following extra features provided by cmd2:
|
|
5888
5908
|
- transcript testing
|
|
5889
5909
|
- intro banner
|
|
5890
5910
|
- exit code
|
|
@@ -5922,11 +5942,11 @@ class Cmd(cmd.Cmd):
|
|
|
5922
5942
|
self._run_transcript_tests([os.path.expanduser(tf) for tf in self._transcript_files])
|
|
5923
5943
|
else:
|
|
5924
5944
|
# If an intro was supplied in the method call, allow it to override the default
|
|
5925
|
-
if intro
|
|
5945
|
+
if intro:
|
|
5926
5946
|
self.intro = intro
|
|
5927
5947
|
|
|
5928
5948
|
# Print the intro, if there is one, right after the preloop
|
|
5929
|
-
if self.intro
|
|
5949
|
+
if self.intro:
|
|
5930
5950
|
self.poutput(self.intro)
|
|
5931
5951
|
|
|
5932
5952
|
# And then call _cmdloop() to enter the main loop
|
cmd2/parsing.py
CHANGED
|
@@ -9,6 +9,8 @@ from dataclasses import (
|
|
|
9
9
|
)
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
+
from typing_extensions import Self
|
|
13
|
+
|
|
12
14
|
from . import (
|
|
13
15
|
constants,
|
|
14
16
|
utils,
|
|
@@ -105,10 +107,10 @@ class Statement(str): # noqa: SLOT000
|
|
|
105
107
|
whether positional or denoted with switches.
|
|
106
108
|
|
|
107
109
|
2. For commands with simple positional arguments, use
|
|
108
|
-
[args][cmd2.Statement.args] or [arg_list][cmd2.Statement.arg_list]
|
|
110
|
+
[args][cmd2.parsing.Statement.args] or [arg_list][cmd2.parsing.Statement.arg_list]
|
|
109
111
|
|
|
110
112
|
3. If you don't want to have to worry about quoted arguments, see
|
|
111
|
-
[argv][cmd2.Statement.argv] for a trick which strips quotes off for you.
|
|
113
|
+
[argv][cmd2.parsing.Statement.argv] for a trick which strips quotes off for you.
|
|
112
114
|
"""
|
|
113
115
|
|
|
114
116
|
# the arguments, but not the command, nor the output redirection clauses.
|
|
@@ -144,7 +146,7 @@ class Statement(str): # noqa: SLOT000
|
|
|
144
146
|
# Used in JSON dictionaries
|
|
145
147
|
_args_field = 'args'
|
|
146
148
|
|
|
147
|
-
def __new__(cls, value: object, *_pos_args: Any, **_kw_args: Any) ->
|
|
149
|
+
def __new__(cls, value: object, *_pos_args: Any, **_kw_args: Any) -> Self:
|
|
148
150
|
"""Create a new instance of Statement.
|
|
149
151
|
|
|
150
152
|
We must override __new__ because we are subclassing `str` which is
|
|
@@ -193,7 +195,7 @@ class Statement(str): # noqa: SLOT000
|
|
|
193
195
|
|
|
194
196
|
@property
|
|
195
197
|
def expanded_command_line(self) -> str:
|
|
196
|
-
"""Concatenate [
|
|
198
|
+
"""Concatenate [cmd2.parsing.Statement.command_and_args]() and [cmd2.parsing.Statement.post_command]()."""
|
|
197
199
|
return self.command_and_args + self.post_command
|
|
198
200
|
|
|
199
201
|
@property
|
cmd2/py_bridge.py
CHANGED
|
@@ -137,7 +137,7 @@ class PyBridge:
|
|
|
137
137
|
)
|
|
138
138
|
finally:
|
|
139
139
|
with self._cmd2_app.sigint_protection:
|
|
140
|
-
self._cmd2_app.stdout = cast(
|
|
140
|
+
self._cmd2_app.stdout = cast(TextIO, copy_cmd_stdout.inner_stream)
|
|
141
141
|
if stdouts_match:
|
|
142
142
|
sys.stdout = self._cmd2_app.stdout
|
|
143
143
|
|
cmd2/rich_utils.py
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"""Provides common utilities to support Rich in cmd2-based applications."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
-
from collections.abc import
|
|
5
|
-
Iterable,
|
|
6
|
-
Mapping,
|
|
7
|
-
)
|
|
4
|
+
from collections.abc import Mapping
|
|
8
5
|
from enum import Enum
|
|
9
6
|
from typing import (
|
|
10
7
|
IO,
|
|
@@ -22,7 +19,6 @@ from rich.console import (
|
|
|
22
19
|
from rich.padding import Padding
|
|
23
20
|
from rich.pretty import is_expandable
|
|
24
21
|
from rich.protocol import rich_cast
|
|
25
|
-
from rich.segment import Segment
|
|
26
22
|
from rich.style import StyleType
|
|
27
23
|
from rich.table import (
|
|
28
24
|
Column,
|
|
@@ -380,72 +376,3 @@ def _from_ansi_has_newline_bug() -> bool:
|
|
|
380
376
|
# Only apply the monkey patch if the bug is present
|
|
381
377
|
if _from_ansi_has_newline_bug():
|
|
382
378
|
Text.from_ansi = _from_ansi_wrapper # type: ignore[assignment]
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
###################################################################################
|
|
386
|
-
# Segment.apply_style() monkey patch
|
|
387
|
-
###################################################################################
|
|
388
|
-
|
|
389
|
-
# Save original Segment.apply_style() so we can call it in our wrapper
|
|
390
|
-
_orig_segment_apply_style = Segment.apply_style
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
@classmethod # type: ignore[misc]
|
|
394
|
-
def _apply_style_wrapper(cls: type[Segment], *args: Any, **kwargs: Any) -> Iterable["Segment"]:
|
|
395
|
-
r"""Wrap Segment.apply_style() to fix bug with styling newlines.
|
|
396
|
-
|
|
397
|
-
This wrapper handles an issue where Segment.apply_style() includes newlines
|
|
398
|
-
within styled Segments. As a result, when printing text using a background color
|
|
399
|
-
and soft wrapping, the background color incorrectly carries over onto the following line.
|
|
400
|
-
|
|
401
|
-
You can reproduce this behavior by calling console.print() using a background color
|
|
402
|
-
and soft wrapping.
|
|
403
|
-
|
|
404
|
-
For example:
|
|
405
|
-
console.print("line_1", style="blue on white", soft_wrap=True)
|
|
406
|
-
|
|
407
|
-
When soft wrapping is disabled, console.print() splits Segments into their individual
|
|
408
|
-
lines, which separates the newlines from the styled text. Therefore, the background color
|
|
409
|
-
issue does not occur in that mode.
|
|
410
|
-
|
|
411
|
-
This function copies that behavior to fix this the issue even when soft wrapping is enabled.
|
|
412
|
-
|
|
413
|
-
There is currently a pull request on Rich to fix this.
|
|
414
|
-
https://github.com/Textualize/rich/pull/3839
|
|
415
|
-
"""
|
|
416
|
-
styled_segments = list(_orig_segment_apply_style(*args, **kwargs))
|
|
417
|
-
newline_segment = cls.line()
|
|
418
|
-
|
|
419
|
-
# If the final segment ends in a newline, that newline will be stripped by Segment.split_lines().
|
|
420
|
-
# Save an unstyled newline to restore later.
|
|
421
|
-
end_segment = newline_segment if styled_segments and styled_segments[-1].text.endswith("\n") else None
|
|
422
|
-
|
|
423
|
-
# Use Segment.split_lines() to separate the styled text from the newlines.
|
|
424
|
-
# This way the ANSI reset code will appear before any newline.
|
|
425
|
-
sanitized_segments: list[Segment] = []
|
|
426
|
-
|
|
427
|
-
lines = list(Segment.split_lines(styled_segments))
|
|
428
|
-
for index, line in enumerate(lines):
|
|
429
|
-
sanitized_segments.extend(line)
|
|
430
|
-
if index < len(lines) - 1:
|
|
431
|
-
sanitized_segments.append(newline_segment)
|
|
432
|
-
|
|
433
|
-
if end_segment is not None:
|
|
434
|
-
sanitized_segments.append(end_segment)
|
|
435
|
-
|
|
436
|
-
return sanitized_segments
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
def _rich_has_styled_newline_bug() -> bool:
|
|
440
|
-
"""Check if newlines are styled when soft wrapping."""
|
|
441
|
-
console = Console(force_terminal=True)
|
|
442
|
-
with console.capture() as capture:
|
|
443
|
-
console.print("line_1", style="blue on white", soft_wrap=True)
|
|
444
|
-
|
|
445
|
-
# Check if we see a styled newline in the output
|
|
446
|
-
return "\x1b[34;47m\n\x1b[0m" in capture.get()
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
# Only apply the monkey patch if the bug is present
|
|
450
|
-
if _rich_has_styled_newline_bug():
|
|
451
|
-
Segment.apply_style = _apply_style_wrapper # type: ignore[assignment]
|
cmd2/styles.py
CHANGED
|
@@ -51,7 +51,6 @@ class Cmd2Style(StrEnum):
|
|
|
51
51
|
|
|
52
52
|
COMMAND_LINE = "cmd2.example" # Command line examples in help text
|
|
53
53
|
ERROR = "cmd2.error" # Error text (used by perror())
|
|
54
|
-
EXCEPTION_TYPE = "cmd2.exception.type" # Used by pexcept to mark an exception type
|
|
55
54
|
HELP_HEADER = "cmd2.help.header" # Help table header text
|
|
56
55
|
HELP_LEADER = "cmd2.help.leader" # Text right before the help tables are listed
|
|
57
56
|
SUCCESS = "cmd2.success" # Success text (used by psuccess())
|
|
@@ -63,9 +62,8 @@ class Cmd2Style(StrEnum):
|
|
|
63
62
|
DEFAULT_CMD2_STYLES: dict[str, StyleType] = {
|
|
64
63
|
Cmd2Style.COMMAND_LINE: Style(color=Color.CYAN, bold=True),
|
|
65
64
|
Cmd2Style.ERROR: Style(color=Color.BRIGHT_RED),
|
|
66
|
-
Cmd2Style.
|
|
67
|
-
Cmd2Style.
|
|
68
|
-
Cmd2Style.HELP_LEADER: Style(color=Color.CYAN, bold=True),
|
|
65
|
+
Cmd2Style.HELP_HEADER: Style(color=Color.BRIGHT_GREEN),
|
|
66
|
+
Cmd2Style.HELP_LEADER: Style(color=Color.CYAN),
|
|
69
67
|
Cmd2Style.SUCCESS: Style(color=Color.GREEN),
|
|
70
68
|
Cmd2Style.TABLE_BORDER: Style(color=Color.BRIGHT_GREEN),
|
|
71
69
|
Cmd2Style.WARNING: Style(color=Color.BRIGHT_YELLOW),
|
cmd2/transcript.py
CHANGED
|
@@ -46,7 +46,7 @@ class Cmd2TestCase(unittest.TestCase):
|
|
|
46
46
|
|
|
47
47
|
# Trap stdout
|
|
48
48
|
self._orig_stdout = self.cmdapp.stdout
|
|
49
|
-
self.cmdapp.stdout = cast(TextIO, utils.StdSim(
|
|
49
|
+
self.cmdapp.stdout = cast(TextIO, utils.StdSim(self.cmdapp.stdout))
|
|
50
50
|
|
|
51
51
|
def tearDown(self) -> None:
|
|
52
52
|
"""Instructions that will be executed after each test method."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cmd2
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1.1
|
|
4
4
|
Summary: cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python
|
|
5
5
|
Author: cmd2 Contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
19
20
|
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
|
|
20
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
22
|
Requires-Python: >=3.10
|
|
@@ -23,16 +24,16 @@ Description-Content-Type: text/markdown
|
|
|
23
24
|
License-File: LICENSE
|
|
24
25
|
Requires-Dist: backports.strenum; python_version == "3.10"
|
|
25
26
|
Requires-Dist: gnureadline>=8; platform_system == "Darwin"
|
|
26
|
-
Requires-Dist: pyperclip>=1.8
|
|
27
|
+
Requires-Dist: pyperclip>=1.8.2
|
|
27
28
|
Requires-Dist: pyreadline3>=3.4; platform_system == "Windows"
|
|
28
|
-
Requires-Dist: rich>=14.
|
|
29
|
+
Requires-Dist: rich>=14.3.0
|
|
29
30
|
Requires-Dist: rich-argparse>=1.7.1
|
|
30
31
|
Dynamic: license-file
|
|
31
32
|
|
|
32
33
|
<h1 align="center">cmd2 : immersive interactive command line applications</h1>
|
|
33
34
|
|
|
34
35
|
[](https://pypi.python.org/pypi/cmd2/)
|
|
35
|
-
[](https://github.com/python-cmd2/cmd2/actions/workflows/tests.yml)
|
|
36
37
|
[](https://codecov.io/gh/python-cmd2/cmd2)
|
|
37
38
|
[](http://cmd2.readthedocs.io/en/latest/?badge=latest)
|
|
38
39
|
<a href="https://discord.gg/RpVG6tk"><img src="https://img.shields.io/badge/chat-on%20discord-7289da.svg" alt="Chat"></a>
|
|
@@ -56,10 +57,10 @@ applications. It provides a simple API which is an extension of Python's built-i
|
|
|
56
57
|
of cmd to make your life easier and eliminates much of the boilerplate code which would be necessary
|
|
57
58
|
when using cmd.
|
|
58
59
|
|
|
59
|
-
> :warning:
|
|
60
|
-
>
|
|
61
|
-
>
|
|
62
|
-
>
|
|
60
|
+
> :warning: **`cmd2` 3.0.0 has been released and there are some significant backwards
|
|
61
|
+
> incompatibilities from version `2.x`. Please see the
|
|
62
|
+
> [Migration Guide](https://cmd2.readthedocs.io/en/latest/upgrades/) for tips on upgrading from
|
|
63
|
+
> `cmd2` 2.x to 3.x.**
|
|
63
64
|
|
|
64
65
|
## The developers toolbox
|
|
65
66
|
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
cmd2/__init__.py,sha256=JG-jiy2MMArRTujSiU8usvQpdgQbl3KLTim4tU5SCJw,2278
|
|
2
|
-
cmd2/argparse_completer.py,sha256=
|
|
3
|
-
cmd2/argparse_custom.py,sha256=
|
|
2
|
+
cmd2/argparse_completer.py,sha256=8hK5_QUnHgeHVY60C89kMKn2b91AUyDjyq4dykzjCRA,35680
|
|
3
|
+
cmd2/argparse_custom.py,sha256=7h68TXurxjDoMsCzoZy1uyy6tvTEEi3RornPQ9ZHJoA,68767
|
|
4
4
|
cmd2/clipboard.py,sha256=5PSKTe3uDe2pFFEDUEMMwAmzcyPkbXXz14SOQxFFncg,515
|
|
5
|
-
cmd2/cmd2.py,sha256=
|
|
5
|
+
cmd2/cmd2.py,sha256=rHSkJ2RF9QQiYdIktd5DHJYJL-X7swwzyVqM6TMbSZU,272373
|
|
6
6
|
cmd2/colors.py,sha256=gLAU8gjhPwZud9MNVk53RGE9ZSdwspLnye_kReeZKjc,7848
|
|
7
7
|
cmd2/command_definition.py,sha256=4FQgivn-9aZegU5tbUl6XsK_G_gZTMfsbGDortz-Ir8,7992
|
|
8
8
|
cmd2/constants.py,sha256=yDcaeEG4Y2DHmLwUpV-_lEiHkiYUZEuyf9mpDdAzmkg,1773
|
|
9
9
|
cmd2/decorators.py,sha256=_xxagAxlcUROLZowqjW5RYP935uNMEJyr2h8FkWalGw,17356
|
|
10
10
|
cmd2/exceptions.py,sha256=J8Ck0dhusB2cfVksRLkM4WVgBTGQnPdEcU26RfBjjRw,3440
|
|
11
11
|
cmd2/history.py,sha256=QNg5QOe3854hFsu9qw7QlqtnTQvmtXojOWgMZlg6JoQ,14755
|
|
12
|
-
cmd2/parsing.py,sha256
|
|
12
|
+
cmd2/parsing.py,sha256=-8TfpXZAZnlLbCJem_EziaC2JqZvmOkIUoera1xmM4Y,28035
|
|
13
13
|
cmd2/plugin.py,sha256=_KD44QaHPXncT3qSOWXyzesxKvEbJp5tQeMJ569mLNw,827
|
|
14
14
|
cmd2/py.typed,sha256=qrkHrYJvGoZpU2BpVLNxJB44LlhqVSKyYOwD_L_1m3s,10
|
|
15
|
-
cmd2/py_bridge.py,sha256=
|
|
16
|
-
cmd2/rich_utils.py,sha256
|
|
15
|
+
cmd2/py_bridge.py,sha256=oD7EwT59HvWSvF9FACWDi-_izkQY6mfitOYX20Y5daI,5190
|
|
16
|
+
cmd2/rich_utils.py,sha256=-BZW98c5OEdYpPq9J3LDkgWHPGoAb8CGMbZvFQrsONE,13461
|
|
17
17
|
cmd2/rl_utils.py,sha256=tlDsH5QnN5xlkJT62Q4ZSOHvccXtt1Nm6kcWJQh6cUc,11315
|
|
18
18
|
cmd2/string_utils.py,sha256=7F8ORonOc2xS0ZC56hjhQcnZhonE3lDFdQxij6v54dA,4430
|
|
19
|
-
cmd2/styles.py,sha256=
|
|
19
|
+
cmd2/styles.py,sha256=rh7N-4A2moKDEH82Co-yTrmzAknU75d2c0oR2G1euEg,2762
|
|
20
20
|
cmd2/terminal_utils.py,sha256=AOk1VjOAzxn7jou2wbALeD6FRIRlKgkQ0VCzIEY51DA,6052
|
|
21
|
-
cmd2/transcript.py,sha256=
|
|
21
|
+
cmd2/transcript.py,sha256=5a_1HGDzhhG1I47g9QnrVNWnOI6Mt_z7jGMH7wFm2XQ,9210
|
|
22
22
|
cmd2/utils.py,sha256=4jlhcHfgr8yoymP2hRPeCr7_1J37vI4MIIBkszyPK6M,32292
|
|
23
|
-
cmd2-3.
|
|
24
|
-
cmd2-3.
|
|
25
|
-
cmd2-3.
|
|
26
|
-
cmd2-3.
|
|
27
|
-
cmd2-3.
|
|
23
|
+
cmd2-3.1.1.dist-info/licenses/LICENSE,sha256=7JL3v4rPUpHV8b0yWAOl64e9r4YmwbHgl3SH_RGbsK8,1099
|
|
24
|
+
cmd2-3.1.1.dist-info/METADATA,sha256=KL2uOqWMRRCYFEWwYiSbejEAtzY0IqYTIZR7mSwgz0M,16346
|
|
25
|
+
cmd2-3.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
26
|
+
cmd2-3.1.1.dist-info/top_level.txt,sha256=gJbOJmyrARwLhm5diXAtzlNQdxbDZ8iRJ8HJi65_5hg,5
|
|
27
|
+
cmd2-3.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2008-2026 Catherine Devlin and others
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
6
|
+
associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
7
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
8
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
12
|
+
portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
15
|
+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
16
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
|
17
|
+
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
The MIT License (MIT)
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2008-2025 Catherine Devlin and others
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
|
13
|
-
all copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
-
THE SOFTWARE.
|
|
File without changes
|