cli-command-parser 2026.2.1__tar.gz → 2026.6.27__tar.gz
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.
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/MANIFEST.in +1 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/PKG-INFO +12 -4
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/__init__.py +2 -1
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/__version__.py +1 -1
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/command_parameters.py +39 -27
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/commands.py +17 -34
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/compat.py +1 -1
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/config.py +64 -56
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/context.py +51 -31
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/conversion/argparse_ast.py +144 -68
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/conversion/cli.py +11 -6
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/conversion/command_builder.py +35 -31
- cli_command_parser-2026.6.27/lib/cli_command_parser/conversion/utils.py +58 -0
- cli_command_parser-2026.6.27/lib/cli_command_parser/conversion/visitor.py +346 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/core.py +51 -30
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/documentation.py +37 -29
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/error_handling/base.py +13 -9
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/error_handling/windows.py +2 -2
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/exceptions.py +11 -12
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/formatting/commands.py +31 -15
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/formatting/params.py +106 -79
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/formatting/restructured_text.py +21 -13
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/formatting/utils.py +3 -3
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/inputs/__init__.py +40 -14
- cli_command_parser-2026.6.27/lib/cli_command_parser/inputs/_typing.py +78 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/inputs/base.py +2 -2
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/inputs/choices.py +29 -19
- cli_command_parser-2026.6.27/lib/cli_command_parser/inputs/files.py +538 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/inputs/numeric.py +132 -46
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/inputs/patterns.py +51 -19
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/inputs/time.py +128 -55
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/inputs/utils.py +156 -47
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/metadata.py +94 -65
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/nargs.py +38 -18
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/parameters/__init__.py +1 -1
- cli_command_parser-2026.6.27/lib/cli_command_parser/parameters/_typing.py +15 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/parameters/actions.py +52 -40
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/parameters/base.py +290 -108
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/parameters/choice_map.py +54 -53
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/parameters/groups.py +11 -8
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/parameters/option_strings.py +4 -12
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/parameters/options.py +314 -80
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/parameters/pass_thru.py +4 -2
- cli_command_parser-2026.6.27/lib/cli_command_parser/parameters/positionals.py +167 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/parse_tree.py +66 -51
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/parser.py +18 -18
- cli_command_parser-2026.6.27/lib/cli_command_parser/py.typed +0 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/testing.py +46 -38
- cli_command_parser-2026.6.27/lib/cli_command_parser/typing.py +60 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/utils.py +35 -20
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser.egg-info/PKG-INFO +12 -4
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser.egg-info/SOURCES.txt +3 -0
- cli_command_parser-2026.6.27/lib/cli_command_parser.egg-info/requires.txt +10 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/pyproject.toml +1 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/readme.rst +5 -3
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/requirements-dev.txt +1 -5
- cli_command_parser-2026.2.1/lib/cli_command_parser/conversion/utils.py +0 -38
- cli_command_parser-2026.2.1/lib/cli_command_parser/conversion/visitor.py +0 -227
- cli_command_parser-2026.2.1/lib/cli_command_parser/inputs/files.py +0 -246
- cli_command_parser-2026.2.1/lib/cli_command_parser/parameters/positionals.py +0 -91
- cli_command_parser-2026.2.1/lib/cli_command_parser/typing.py +0 -82
- cli_command_parser-2026.2.1/lib/cli_command_parser.egg-info/requires.txt +0 -3
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/LICENSE +0 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/__main__.py +0 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/annotations.py +0 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/conversion/__init__.py +0 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/conversion/__main__.py +0 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/conversion/argparse_utils.py +0 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/error_handling/__init__.py +0 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/error_handling/other.py +0 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/formatting/__init__.py +0 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/inputs/exceptions.py +0 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser.egg-info/dependency_links.txt +0 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser.egg-info/entry_points.txt +0 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser.egg-info/top_level.txt +0 -0
- {cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli_command_parser
|
|
3
|
-
Version: 2026.
|
|
3
|
+
Version: 2026.6.27
|
|
4
4
|
Summary: CLI Command Parser
|
|
5
5
|
Author-email: Doug Skrypa <dskrypa@gmail.com>
|
|
6
6
|
Project-URL: Source, https://github.com/dskrypa/cli_command_parser
|
|
@@ -25,6 +25,12 @@ Description-Content-Type: text/x-rst
|
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
Provides-Extra: wcwidth
|
|
27
27
|
Requires-Dist: wcwidth; extra == "wcwidth"
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: ruff; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
32
|
+
Requires-Dist: coverage; extra == "dev"
|
|
33
|
+
Requires-Dist: mypy>=2.1.0; extra == "dev"
|
|
28
34
|
Dynamic: license-file
|
|
29
35
|
|
|
30
36
|
CLI Command Parser
|
|
@@ -57,7 +63,7 @@ CLIs while remaining readable and easy to maintain.
|
|
|
57
63
|
|
|
58
64
|
Some of the primary goals and key features of this project:
|
|
59
65
|
- Minimal boilerplate code is necessary to define CLI parameters and access their parsed values
|
|
60
|
-
-
|
|
66
|
+
- Typing support for CLI parameters that can be validated via type checkers
|
|
61
67
|
- Subcommands can inherit common parameters so they don't need to be repeated
|
|
62
68
|
- Easy to handle common initialization tasks for all actions / subcommands once
|
|
63
69
|
|
|
@@ -69,9 +75,11 @@ Example Program
|
|
|
69
75
|
|
|
70
76
|
from cli_command_parser import Command, Option, main
|
|
71
77
|
|
|
72
|
-
class Hello(Command
|
|
78
|
+
class Hello(Command):
|
|
79
|
+
"""Simple greeting example"""
|
|
80
|
+
|
|
73
81
|
name = Option('-n', default='World', help='The person to say hello to')
|
|
74
|
-
count
|
|
82
|
+
count = Option('-c', type=int, default=1, help='Number of times to repeat the message')
|
|
75
83
|
|
|
76
84
|
def main(self):
|
|
77
85
|
for _ in range(self.count):
|
{cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/__init__.py
RENAMED
|
@@ -42,6 +42,7 @@ from .parameters import (
|
|
|
42
42
|
Counter,
|
|
43
43
|
Flag,
|
|
44
44
|
Option,
|
|
45
|
+
Param,
|
|
45
46
|
Parameter,
|
|
46
47
|
ParamGroup,
|
|
47
48
|
PassThru,
|
|
@@ -52,4 +53,4 @@ from .parameters import (
|
|
|
52
53
|
after_main,
|
|
53
54
|
before_main,
|
|
54
55
|
)
|
|
55
|
-
from .typing import
|
|
56
|
+
from .typing import ParamOrGroup
|
{cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/__version__.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
__title__ = 'cli_command_parser'
|
|
2
2
|
__description__ = 'CLI Command Parser'
|
|
3
3
|
__url__ = 'https://github.com/dskrypa/cli_command_parser'
|
|
4
|
-
__version__ = '2026.
|
|
4
|
+
__version__ = '2026.06.27'
|
|
5
5
|
__author__ = 'Doug Skrypa'
|
|
6
6
|
__author_email__ = 'dskrypa@gmail.com'
|
|
7
7
|
__license__ = 'Apache 2.0'
|
|
@@ -12,20 +12,25 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
from collections import defaultdict
|
|
14
14
|
from functools import cached_property
|
|
15
|
-
from typing import TYPE_CHECKING, Collection, Iterator
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Collection, Iterator, Type, TypeAlias
|
|
16
16
|
|
|
17
17
|
from .config import AmbiguousComboMode, CommandConfig
|
|
18
18
|
from .exceptions import AmbiguousCombo, AmbiguousShortForm, CommandDefinitionError, ParameterDefinitionError
|
|
19
|
-
from .parameters import
|
|
19
|
+
from .parameters import ActionFlag, ParamGroup, PassThru, help_action
|
|
20
20
|
from .parameters.base import BaseOption, BasePositional, ParamBase, Parameter
|
|
21
|
+
from .parameters.choice_map import Action, SubCommand
|
|
21
22
|
|
|
22
23
|
if TYPE_CHECKING:
|
|
24
|
+
from .commands import Command
|
|
23
25
|
from .context import Context
|
|
26
|
+
from .core import CommandMeta
|
|
24
27
|
from .formatting.commands import CommandHelpFormatter
|
|
25
|
-
from .typing import
|
|
28
|
+
from .typing import Bool, Strings
|
|
26
29
|
|
|
30
|
+
CommandCls: TypeAlias = Type[Command] | CommandMeta
|
|
27
31
|
OptionMap = dict[str, BaseOption]
|
|
28
32
|
ActionFlags = list[ActionFlag]
|
|
33
|
+
Positionals = list[BasePositional] | tuple[()]
|
|
29
34
|
|
|
30
35
|
__all__ = ['CommandParameters']
|
|
31
36
|
|
|
@@ -33,18 +38,14 @@ __all__ = ['CommandParameters']
|
|
|
33
38
|
class CommandParameters:
|
|
34
39
|
# fmt: off
|
|
35
40
|
command: CommandCls #: The Command associated with this CommandParameters object
|
|
36
|
-
formatter: CommandHelpFormatter #: The formatter used for this Command's help text
|
|
37
41
|
parent: CommandParameters | None #: The parent Command's CommandParameters
|
|
38
|
-
action: Action | None = None #: An Action Parameter, if specified
|
|
39
|
-
_pass_thru: PassThru | None = None #: A PassThru Parameter, if specified
|
|
40
|
-
sub_command: SubCommand | None = None #: A SubCommand Parameter, if specified
|
|
41
42
|
action_flags: ActionFlags #: List of action flags
|
|
42
43
|
split_action_flags: tuple[ActionFlags, ActionFlags] #: Action flags split by before/after main
|
|
43
44
|
options: list[BaseOption] #: List of optional Parameters
|
|
44
45
|
combo_option_map: OptionMap #: Mapping of {short opt: Parameter} (no dash characters)
|
|
45
46
|
groups: list[ParamGroup] #: List of ParamGroup objects
|
|
46
47
|
positionals: list[BasePositional] #: List of positional Parameters
|
|
47
|
-
_deferred_positionals:
|
|
48
|
+
_deferred_positionals: Positionals = () #: Positional Parameters that are deferred to sub commands
|
|
48
49
|
option_map: OptionMap #: Mapping of {--opt / -opt: Parameter}
|
|
49
50
|
# fmt: on
|
|
50
51
|
|
|
@@ -52,6 +53,12 @@ class CommandParameters:
|
|
|
52
53
|
self.command = command
|
|
53
54
|
self.parent = parent_params
|
|
54
55
|
self.config = config
|
|
56
|
+
# fmt: off
|
|
57
|
+
# These are annotated here because mypy thinks they're invoked as descriptors when annotated at the class level
|
|
58
|
+
self.action: Action | None = None #: An Action Parameter, if specified
|
|
59
|
+
self.sub_command: SubCommand | None = None #: A SubCommand Parameter, if specified
|
|
60
|
+
self._pass_thru: PassThru | None = None #: A PassThru Parameter, if specified
|
|
61
|
+
# fmt: on
|
|
55
62
|
self._process_parameters()
|
|
56
63
|
|
|
57
64
|
def __repr__(self) -> str:
|
|
@@ -77,11 +84,8 @@ class CommandParameters:
|
|
|
77
84
|
|
|
78
85
|
@cached_property
|
|
79
86
|
def all_positionals(self) -> list[BasePositional]:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return self.parent.all_positionals + self.positionals
|
|
83
|
-
except AttributeError:
|
|
84
|
-
pass
|
|
87
|
+
if self.parent and not self.parent.sub_command:
|
|
88
|
+
return self.parent.all_positionals + self.positionals
|
|
85
89
|
return self.positionals
|
|
86
90
|
|
|
87
91
|
def get_positionals_to_parse(self, ctx: Context) -> list[BasePositional]:
|
|
@@ -94,6 +98,7 @@ class CommandParameters:
|
|
|
94
98
|
|
|
95
99
|
@cached_property
|
|
96
100
|
def formatter(self) -> CommandHelpFormatter:
|
|
101
|
+
"""The formatter used for this Command's help text."""
|
|
97
102
|
from .formatting.commands import CommandHelpFormatter
|
|
98
103
|
|
|
99
104
|
formatter_factory = self.config.command_formatter or CommandHelpFormatter
|
|
@@ -105,13 +110,13 @@ class CommandParameters:
|
|
|
105
110
|
return formatter
|
|
106
111
|
|
|
107
112
|
@cached_property
|
|
108
|
-
def _has_help(self) ->
|
|
113
|
+
def _has_help(self) -> Bool:
|
|
109
114
|
return help_action in self.action_flags or (self.parent and self.parent._has_help)
|
|
110
115
|
|
|
111
116
|
# region Initialization
|
|
112
117
|
|
|
113
118
|
def _iter_parameters(self) -> Iterator[ParamBase]:
|
|
114
|
-
name_param_map = {} # Allow subclasses to override names, but not within a given command
|
|
119
|
+
name_param_map: dict[str, Any] = {} # Allow subclasses to override names, but not within a given command
|
|
115
120
|
for item in self.command.__dict__.items():
|
|
116
121
|
attr, param = item
|
|
117
122
|
if attr.startswith('__') or not isinstance(param, ParamBase): # Name mangled Parameters are still processed
|
|
@@ -173,7 +178,9 @@ class CommandParameters:
|
|
|
173
178
|
self.groups = sorted(groups) if groups else []
|
|
174
179
|
|
|
175
180
|
def _process_positionals(self, params: list[BasePositional]):
|
|
176
|
-
unfollowable
|
|
181
|
+
unfollowable: BasePositional | None = None
|
|
182
|
+
action_or_sub_cmd: SubCommand | Action | None = None
|
|
183
|
+
split_index: int = 0
|
|
177
184
|
if self.parent and (deferred := self.parent._deferred_positionals):
|
|
178
185
|
params = deferred + params
|
|
179
186
|
|
|
@@ -186,26 +193,28 @@ class CommandParameters:
|
|
|
186
193
|
raise CommandDefinitionError(
|
|
187
194
|
f'Additional Positional parameters cannot follow {unfollowable} {why} - {param=} is invalid'
|
|
188
195
|
)
|
|
189
|
-
|
|
196
|
+
|
|
197
|
+
if isinstance(param, (SubCommand, Action)):
|
|
190
198
|
if action_or_sub_cmd:
|
|
191
199
|
raise CommandDefinitionError(
|
|
192
200
|
f'Only 1 Action xor SubCommand is allowed in a given Command - {self.command.__name__} cannot'
|
|
193
201
|
f' contain both {action_or_sub_cmd} and {param}'
|
|
194
202
|
)
|
|
195
|
-
|
|
203
|
+
|
|
204
|
+
if isinstance(param, SubCommand):
|
|
196
205
|
self.sub_command = action_or_sub_cmd = param
|
|
197
206
|
split_index = i + 1
|
|
198
207
|
if param.has_choices and 0 in param.nargs: # It has local choices or is not required
|
|
199
208
|
unfollowable = param
|
|
200
209
|
else: # It's an Action
|
|
201
|
-
self.action = action_or_sub_cmd = param
|
|
210
|
+
self.action = action_or_sub_cmd = param
|
|
202
211
|
if not param.has_choices:
|
|
203
212
|
raise CommandDefinitionError(f'No choices were registered for {self.action}')
|
|
204
213
|
elif 0 in param.nargs or (param.nargs.variable and not param.has_choices):
|
|
205
214
|
unfollowable = param
|
|
206
215
|
|
|
207
216
|
if split_index:
|
|
208
|
-
if self.sub_command.has_local_choices:
|
|
217
|
+
if self.sub_command.has_local_choices: # type: ignore[union-attr]
|
|
209
218
|
self._deferred_positionals = params[split_index:]
|
|
210
219
|
else:
|
|
211
220
|
params, self._deferred_positionals = params[:split_index], params[split_index:]
|
|
@@ -252,19 +261,22 @@ class CommandParameters:
|
|
|
252
261
|
f'{opt_type} {option=} conflict for command={self.command!r} between {existing} and {param}'
|
|
253
262
|
)
|
|
254
263
|
|
|
255
|
-
def _process_action_flags(self):
|
|
256
|
-
action_flags = sorted(p for p in self.options if isinstance(p, ActionFlag))
|
|
257
|
-
grouped_ordered_flags
|
|
264
|
+
def _process_action_flags(self) -> None:
|
|
265
|
+
action_flags: ActionFlags = sorted(p for p in self.options if isinstance(p, ActionFlag)) # type: ignore[misc]
|
|
266
|
+
grouped_ordered_flags: dict[bool, dict[int | float, ActionFlags]] = {
|
|
267
|
+
True: defaultdict(list),
|
|
268
|
+
False: defaultdict(list),
|
|
269
|
+
}
|
|
258
270
|
for param in action_flags:
|
|
259
271
|
if param.func is None:
|
|
260
272
|
raise ParameterDefinitionError(f'No function was registered for {param=}')
|
|
261
273
|
grouped_ordered_flags[param.before_main][param.order].append(param)
|
|
262
274
|
|
|
263
275
|
found_non_always = False
|
|
264
|
-
invalid = {}
|
|
276
|
+
invalid: dict[tuple[bool, int | float], ActionFlags | ActionFlag] = {}
|
|
265
277
|
for before_main, prio_params in grouped_ordered_flags.items():
|
|
266
278
|
for prio, params in prio_params.items():
|
|
267
|
-
param
|
|
279
|
+
param = params[0] # Don't pop and check `if params` - all are needed for the group check
|
|
268
280
|
if found_non_always and param.always_available:
|
|
269
281
|
invalid[(before_main, prio)] = param
|
|
270
282
|
elif not param.always_available:
|
|
@@ -296,7 +308,7 @@ class CommandParameters:
|
|
|
296
308
|
@cached_property
|
|
297
309
|
def _classified_combo_options(self) -> tuple[OptionMap, OptionMap]:
|
|
298
310
|
"""Tuple of (single char short:Option map, multi-char short:Option map) for options available in this command"""
|
|
299
|
-
multi_char_combos = {}
|
|
311
|
+
multi_char_combos: OptionMap = {}
|
|
300
312
|
items = self.combo_option_map.items()
|
|
301
313
|
for combo, param in items:
|
|
302
314
|
if len(combo) == 1: # combo_option_map is sorted in reverse length order, so all following will be 1 char
|
|
@@ -368,7 +380,7 @@ class CommandParameters:
|
|
|
368
380
|
# Note: if the option is not in this Command's option_map, the KeyError is handled by CommandParser
|
|
369
381
|
return [(option, self.option_map[option], value)], True
|
|
370
382
|
else:
|
|
371
|
-
value = None
|
|
383
|
+
value = None # type: ignore[assignment]
|
|
372
384
|
|
|
373
385
|
try:
|
|
374
386
|
param = self.option_map[option]
|
{cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/commands.py
RENAMED
|
@@ -9,16 +9,16 @@ from __future__ import annotations
|
|
|
9
9
|
import logging
|
|
10
10
|
from abc import ABC
|
|
11
11
|
from contextlib import ExitStack
|
|
12
|
-
from typing import TYPE_CHECKING, Sequence, TextIO, Type
|
|
12
|
+
from typing import TYPE_CHECKING, Sequence, TextIO, Type
|
|
13
13
|
|
|
14
14
|
from .context import ActionPhase, Context, get_or_create_context
|
|
15
|
-
from .core import CommandMeta, get_params, get_top_level_commands
|
|
15
|
+
from .core import CommandMeta, get_metadata, get_params, get_top_level_commands
|
|
16
16
|
from .exceptions import ParamConflict, ParserExit
|
|
17
17
|
from .parser import parse_args_and_get_next_cmd
|
|
18
18
|
from .utils import maybe_await
|
|
19
19
|
|
|
20
20
|
if TYPE_CHECKING:
|
|
21
|
-
from .typing import Bool,
|
|
21
|
+
from .typing import Bool, Self
|
|
22
22
|
|
|
23
23
|
__all__ = ['Command', 'AsyncCommand', 'main', 'print_help']
|
|
24
24
|
log = logging.getLogger(__name__)
|
|
@@ -32,34 +32,25 @@ class Command(ABC, metaclass=CommandMeta):
|
|
|
32
32
|
#: The parsing Context used for this Command. Provided here for convenience - this reference to it is not used by
|
|
33
33
|
#: any CLI Command Parser internals, so it is safe for subclasses to redefine / overwrite it.
|
|
34
34
|
ctx: Context
|
|
35
|
+
__ctx: Context
|
|
35
36
|
|
|
36
|
-
def __new__(cls):
|
|
37
|
+
def __new__(cls) -> Command:
|
|
37
38
|
# By storing the Context here instead of __init__, every single subclass won't need to
|
|
38
39
|
# call super().__init__(...) from their own __init__ for this step
|
|
39
40
|
self = super().__new__(cls)
|
|
40
41
|
self.__ctx = ctx = get_or_create_context(cls, command=self)
|
|
41
42
|
if not hasattr(self, 'ctx'):
|
|
42
|
-
self.ctx
|
|
43
|
+
self.ctx = ctx # noqa # PyCharm complains this is invalid, but doesn't understand it without it
|
|
43
44
|
return self
|
|
44
45
|
|
|
45
46
|
def __repr__(self) -> str:
|
|
46
47
|
cls = self.__class__
|
|
47
|
-
return f'<{cls.__name__} in prog={
|
|
48
|
+
return f'<{cls.__name__} in prog={get_metadata(cls).prog!r}>'
|
|
48
49
|
|
|
49
50
|
# region Parse & Run
|
|
50
51
|
|
|
51
52
|
@classmethod
|
|
52
|
-
|
|
53
|
-
def parse_and_run(cls: Type[CommandObj], argv: Argv = None, **kwargs) -> CommandObj | None:
|
|
54
|
-
# These overloads indicate that an instance of the same type or another may be returned
|
|
55
|
-
...
|
|
56
|
-
|
|
57
|
-
@classmethod
|
|
58
|
-
@overload
|
|
59
|
-
def parse_and_run(cls, argv: Argv = None, **kwargs) -> CommandObj | None: ...
|
|
60
|
-
|
|
61
|
-
@classmethod
|
|
62
|
-
def parse_and_run(cls, argv=None, **kwargs):
|
|
53
|
+
def parse_and_run(cls, argv: Argv | None = None, **kwargs) -> Self | None:
|
|
63
54
|
"""
|
|
64
55
|
Primary entry point for parsing arguments, resolving subcommands, and running a command.
|
|
65
56
|
|
|
@@ -90,15 +81,7 @@ class Command(ABC, metaclass=CommandMeta):
|
|
|
90
81
|
# region Parse
|
|
91
82
|
|
|
92
83
|
@classmethod
|
|
93
|
-
|
|
94
|
-
def parse(cls: Type[CommandObj], argv: Argv = None) -> CommandObj: ...
|
|
95
|
-
|
|
96
|
-
@classmethod
|
|
97
|
-
@overload
|
|
98
|
-
def parse(cls, argv: Argv = None) -> CommandObj: ...
|
|
99
|
-
|
|
100
|
-
@classmethod
|
|
101
|
-
def parse(cls, argv=None):
|
|
84
|
+
def parse(cls, argv: Argv | None = None) -> Self:
|
|
102
85
|
"""
|
|
103
86
|
Parses the specified arguments (or :data:`sys.argv`), and resolves the final subcommand class based on the
|
|
104
87
|
parsed arguments, if necessary. Initializes the Command, but does not call any of its other methods.
|
|
@@ -111,7 +94,7 @@ class Command(ABC, metaclass=CommandMeta):
|
|
|
111
94
|
with ExitStack() as stack:
|
|
112
95
|
stack.enter_context(ctx)
|
|
113
96
|
while sub_cmd := parse_args_and_get_next_cmd(ctx):
|
|
114
|
-
cmd_cls = sub_cmd
|
|
97
|
+
cmd_cls = sub_cmd # type: ignore[assignment]
|
|
115
98
|
ctx = stack.enter_context(ctx._sub_context(cmd_cls))
|
|
116
99
|
|
|
117
100
|
return cmd_cls()
|
|
@@ -308,14 +291,14 @@ class AsyncCommand(Command, ABC):
|
|
|
308
291
|
await maybe_await(self(**kwargs))
|
|
309
292
|
return self
|
|
310
293
|
|
|
311
|
-
async def __call__(self, *args, **kwargs) -> int:
|
|
294
|
+
async def __call__(self, *args, **kwargs) -> int: # type: ignore[override]
|
|
312
295
|
"""Asynchronous version of :meth:`Command.__call__`."""
|
|
313
|
-
with self._Command__ctx as ctx, ctx.get_error_handler(): #
|
|
296
|
+
with self._Command__ctx as ctx, ctx.get_error_handler(): # type: ignore[attr-defined]
|
|
314
297
|
await maybe_await(self._pre_init_actions_(*args, **kwargs))
|
|
315
298
|
await maybe_await(self._init_command_(*args, **kwargs))
|
|
316
299
|
await maybe_await(self._before_main_(*args, **kwargs))
|
|
317
300
|
try:
|
|
318
|
-
await maybe_await(self.main(*args, **kwargs))
|
|
301
|
+
await maybe_await(self.main(*args, **kwargs)) # type: ignore[arg-type]
|
|
319
302
|
except BaseException:
|
|
320
303
|
if ctx.config.always_run_after_main:
|
|
321
304
|
log.debug('Caught exception - running _after_main_ before propagating', exc_info=True)
|
|
@@ -328,7 +311,7 @@ class AsyncCommand(Command, ABC):
|
|
|
328
311
|
|
|
329
312
|
async def _run_actions_(self, phase: ActionPhase, args: tuple, kwargs: dict):
|
|
330
313
|
"""Asynchronous version of :meth:`Command._run_actions_`."""
|
|
331
|
-
for param in self._Command__ctx.iter_action_flags(phase): #
|
|
314
|
+
for param in self._Command__ctx.iter_action_flags(phase): # type: ignore[attr-defined]
|
|
332
315
|
await maybe_await(param.func(self, *args, **kwargs))
|
|
333
316
|
|
|
334
317
|
async def _pre_init_actions_(self, *args, **kwargs):
|
|
@@ -340,9 +323,9 @@ class AsyncCommand(Command, ABC):
|
|
|
340
323
|
"""Asynchronous version of :meth:`Command._before_main_`."""
|
|
341
324
|
await self._run_actions_(ActionPhase.BEFORE_MAIN, args, kwargs)
|
|
342
325
|
|
|
343
|
-
async def main(self, *args, **kwargs) -> int | None:
|
|
326
|
+
async def main(self, *args, **kwargs) -> int | None: # type: ignore[override]
|
|
344
327
|
"""Asynchronous version of :meth:`Command.main`."""
|
|
345
|
-
with self._Command__ctx as ctx: #
|
|
328
|
+
with self._Command__ctx as ctx: # type: ignore[attr-defined]
|
|
346
329
|
action = get_params(self).action
|
|
347
330
|
if action is not None and (ctx.actions_taken == 0 or ctx.config.action_after_action_flags):
|
|
348
331
|
ctx.actions_taken += 1
|
|
@@ -355,7 +338,7 @@ class AsyncCommand(Command, ABC):
|
|
|
355
338
|
await self._run_actions_(ActionPhase.AFTER_MAIN, args, kwargs)
|
|
356
339
|
|
|
357
340
|
|
|
358
|
-
def main(argv: Argv = None, return_command: Bool = False, **kwargs) ->
|
|
341
|
+
def main(argv: Argv | None = None, return_command: Bool = False, **kwargs) -> Command | None:
|
|
359
342
|
"""
|
|
360
343
|
Convenience function that can be used as the main entry point for a program.
|
|
361
344
|
|
{cli_command_parser-2026.2.1 → cli_command_parser-2026.6.27}/lib/cli_command_parser/compat.py
RENAMED
|
@@ -39,7 +39,7 @@ class WCTextWrapper(TextWrapper):
|
|
|
39
39
|
if len(indent) + len(self.placeholder.lstrip()) > self.width:
|
|
40
40
|
raise ValueError('placeholder too large for max width')
|
|
41
41
|
|
|
42
|
-
lines = []
|
|
42
|
+
lines: list[str] = []
|
|
43
43
|
# Arrange in reverse order so items can be efficiently popped from a stack of chucks.
|
|
44
44
|
chunks.reverse()
|
|
45
45
|
while chunks:
|