cli-command-parser 2025.6.14__tar.gz → 2025.7.13__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-2025.6.14/lib/cli_command_parser.egg-info → cli_command_parser-2025.7.13}/PKG-INFO +6 -6
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/__version__.py +1 -1
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/context.py +5 -3
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/inputs/choices.py +10 -2
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/nargs.py +2 -19
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/parameters/actions.py +36 -21
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/parameters/base.py +8 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/parameters/choice_map.py +17 -12
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/parameters/options.py +12 -13
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/parse_tree.py +0 -4
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/parser.py +55 -41
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13/lib/cli_command_parser.egg-info}/PKG-INFO +6 -6
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/readme.rst +5 -5
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/LICENSE +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/MANIFEST.in +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/entry_points.txt +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/__init__.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/__main__.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/annotations.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/command_parameters.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/commands.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/compat.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/config.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/conversion/__init__.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/conversion/__main__.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/conversion/argparse_ast.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/conversion/argparse_utils.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/conversion/cli.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/conversion/command_builder.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/conversion/utils.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/conversion/visitor.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/core.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/documentation.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/error_handling/__init__.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/error_handling/base.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/error_handling/other.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/error_handling/windows.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/exceptions.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/formatting/__init__.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/formatting/commands.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/formatting/params.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/formatting/restructured_text.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/formatting/utils.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/inputs/__init__.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/inputs/base.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/inputs/exceptions.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/inputs/files.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/inputs/numeric.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/inputs/patterns.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/inputs/time.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/inputs/utils.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/metadata.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/parameters/__init__.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/parameters/groups.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/parameters/option_strings.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/parameters/pass_thru.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/parameters/positionals.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/testing.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/typing.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/utils.py +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser.egg-info/SOURCES.txt +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser.egg-info/dependency_links.txt +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser.egg-info/entry_points.txt +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser.egg-info/requires.txt +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser.egg-info/top_level.txt +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/pyproject.toml +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/requirements-dev.txt +0 -0
- {cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli_command_parser
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.7.13
|
|
4
4
|
Summary: CLI Command Parser
|
|
5
5
|
Home-page: https://github.com/dskrypa/cli_command_parser
|
|
6
6
|
Author: Doug Skrypa
|
|
@@ -58,11 +58,11 @@ CLI Command Parser is a class-based CLI argument parser that defines parameters
|
|
|
58
58
|
tools to quickly and easily get started with basic CLIs, and it scales well to support even very large and complex
|
|
59
59
|
CLIs while remaining readable and easy to maintain.
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
61
|
+
Some of the primary goals and key features of this project:
|
|
62
|
+
- Minimal boilerplate code is necessary to define CLI parameters and access their parsed values
|
|
63
|
+
- Easy to use type annotations for CLI parameters
|
|
64
|
+
- Subcommands can inherit common parameters so they don't need to be repeated
|
|
65
|
+
- Easy to handle common initialization tasks for all actions / subcommands once
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
Example Program
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/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__ = '2025.
|
|
4
|
+
__version__ = '2025.07.13'
|
|
5
5
|
__author__ = 'Doug Skrypa'
|
|
6
6
|
__author_email__ = 'dskrypa@gmail.com'
|
|
7
7
|
__license__ = 'Apache 2.0'
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/context.py
RENAMED
|
@@ -24,7 +24,7 @@ from .utils import Terminal, _NotSet
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
25
|
from .command_parameters import CommandParameters
|
|
26
26
|
from .commands import Command
|
|
27
|
-
from .parameters import ActionFlag, Option, Parameter
|
|
27
|
+
from .parameters import ActionFlag, BaseOption, Option, Parameter
|
|
28
28
|
from .typing import AnyConfig, Bool, CommandObj, CommandType, OptStr, ParamOrGroup, PathLike, StrSeq # noqa
|
|
29
29
|
|
|
30
30
|
__all__ = ['Context', 'ctx', 'get_current_context', 'get_or_create_context', 'get_context', 'get_parsed', 'get_raw_arg']
|
|
@@ -250,9 +250,11 @@ class Context(AbstractContextManager): # Extending AbstractContextManager to ma
|
|
|
250
250
|
"""Not intended to be called by users. Used during parsing to determine if any Parameters are missing."""
|
|
251
251
|
return [p for p in self.params.required_check_params() if not self._provided[p]]
|
|
252
252
|
|
|
253
|
-
def missing_options_with_env_var(self) -> Iterator[
|
|
253
|
+
def missing_options_with_env_var(self) -> Iterator[BaseOption]:
|
|
254
254
|
"""Yields Option parameters that have an environment variable configured, and did not have any CLI values."""
|
|
255
|
-
|
|
255
|
+
for param in self.params.options:
|
|
256
|
+
if param.env_var and not self._provided[param]:
|
|
257
|
+
yield param
|
|
256
258
|
|
|
257
259
|
# endregion
|
|
258
260
|
|
|
@@ -36,7 +36,12 @@ class _ChoicesBase(InputType[T], ABC):
|
|
|
36
36
|
return True
|
|
37
37
|
|
|
38
38
|
def _type_str(self) -> str:
|
|
39
|
-
|
|
39
|
+
if self.type is not None:
|
|
40
|
+
try:
|
|
41
|
+
return f'type={self.type.__name__}, '
|
|
42
|
+
except AttributeError: # type is not a class
|
|
43
|
+
pass
|
|
44
|
+
return ''
|
|
40
45
|
|
|
41
46
|
def __repr__(self) -> str:
|
|
42
47
|
cls_name = self.__class__.__name__
|
|
@@ -100,7 +105,10 @@ class Choices(_ChoicesBase[T]):
|
|
|
100
105
|
self.case_sensitive = case_sensitive
|
|
101
106
|
|
|
102
107
|
def _choices_repr(self, delim: str = ',') -> str:
|
|
103
|
-
|
|
108
|
+
try:
|
|
109
|
+
return delim.join(map(repr, sorted(self.choices)))
|
|
110
|
+
except TypeError: # The choice values are not sortable
|
|
111
|
+
return delim.join(sorted(map(repr, self.choices)))
|
|
104
112
|
|
|
105
113
|
def __call__(self, value: str) -> T:
|
|
106
114
|
choices = self.choices
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/nargs.py
RENAMED
|
@@ -6,9 +6,9 @@ Helpers for handling ``nargs=...`` for Parameters.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from typing import Any, Collection, FrozenSet,
|
|
9
|
+
from typing import Any, Collection, FrozenSet, Optional, Sequence, Set, Tuple, Union
|
|
10
10
|
|
|
11
|
-
__all__ = ['Nargs', 'NargsValue', 'REMAINDER'
|
|
11
|
+
__all__ = ['Nargs', 'NargsValue', 'REMAINDER']
|
|
12
12
|
|
|
13
13
|
REMAINDER = type('REMAINDER', (), {})()
|
|
14
14
|
_UNBOUND = (None, REMAINDER)
|
|
@@ -177,20 +177,3 @@ class Nargs:
|
|
|
177
177
|
@property
|
|
178
178
|
def upper_bound(self) -> Union[int, float]:
|
|
179
179
|
return self.max if self._has_upper_bound else float('inf')
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def nargs_min_and_max_sums(nargs_objects: Iterable[Nargs]) -> tuple[int, Union[int, float]]:
|
|
183
|
-
min_sum, max_sum = 0, 0
|
|
184
|
-
iter_nargs = iter(nargs_objects)
|
|
185
|
-
for obj in iter_nargs:
|
|
186
|
-
min_sum += obj.min
|
|
187
|
-
if obj._has_upper_bound:
|
|
188
|
-
max_sum += obj.max
|
|
189
|
-
else:
|
|
190
|
-
max_sum = float('inf')
|
|
191
|
-
break
|
|
192
|
-
|
|
193
|
-
for obj in iter_nargs: # If any had no upper bound, then this loop will complete the min total
|
|
194
|
-
min_sum += obj.min # Otherwise, it will not have anything to iterate over
|
|
195
|
-
|
|
196
|
-
return min_sum, max_sum
|
|
@@ -6,7 +6,7 @@ variables.
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
8
|
from abc import ABC, abstractmethod
|
|
9
|
-
from typing import TYPE_CHECKING, Callable, Iterable, Iterator, NoReturn, Sequence, TypeVar, Union
|
|
9
|
+
from typing import TYPE_CHECKING, Callable, Generic, Iterable, Iterator, NoReturn, Sequence, TypeVar, Union
|
|
10
10
|
|
|
11
11
|
from ..context import ctx
|
|
12
12
|
from ..exceptions import BadArgument, InvalidChoice, MissingArgument, ParamConflict, ParamUsageError, TooManyArguments
|
|
@@ -15,7 +15,8 @@ from ..nargs import Nargs
|
|
|
15
15
|
from ..utils import _NotSet, camel_to_snake_case
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
|
-
from
|
|
18
|
+
from .base import Parameter # noqa
|
|
19
|
+
from ..typing import Bool, CommandObj, T_co
|
|
19
20
|
|
|
20
21
|
__all__ = [
|
|
21
22
|
'ParamAction',
|
|
@@ -31,20 +32,19 @@ __all__ = [
|
|
|
31
32
|
|
|
32
33
|
_PANotSet = object()
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
Param = TypeVar('Param', bound='Parameter')
|
|
35
36
|
Found = Union[int, NoReturn]
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
class ParamAction(ABC):
|
|
39
|
+
class ParamAction(ABC, Generic[Param]):
|
|
39
40
|
__slots__ = ('param',)
|
|
40
41
|
name: str
|
|
41
|
-
|
|
42
|
+
param: Param
|
|
43
|
+
default = _NotSet
|
|
42
44
|
accepts_values: bool = False
|
|
43
45
|
accepts_consts: bool = False
|
|
44
46
|
|
|
45
|
-
def __init_subclass__(
|
|
46
|
-
cls, default: TD = _PANotSet, accepts_values: bool = None, accepts_consts: bool = None, **kwargs
|
|
47
|
-
):
|
|
47
|
+
def __init_subclass__(cls, default=_PANotSet, accepts_values: bool = None, accepts_consts: bool = None, **kwargs):
|
|
48
48
|
super().__init_subclass__(**kwargs)
|
|
49
49
|
cls.name = camel_to_snake_case(cls.__name__)
|
|
50
50
|
if default is not _PANotSet:
|
|
@@ -111,6 +111,15 @@ class ParamAction(ABC):
|
|
|
111
111
|
return False
|
|
112
112
|
return self.param.is_valid_arg(normalized)
|
|
113
113
|
|
|
114
|
+
def would_accept_all(self, values: list[str], combo: bool = False) -> bool:
|
|
115
|
+
prepare_validation_value, is_valid_arg = self.param.prepare_validation_value, self.param.is_valid_arg
|
|
116
|
+
try:
|
|
117
|
+
valid_values = all(is_valid_arg(prepare_validation_value(value, combo)) for value in values)
|
|
118
|
+
except BadArgument: # Potentially raised by prepare_validation_value; is_valid_arg handles exceptions
|
|
119
|
+
return False
|
|
120
|
+
else:
|
|
121
|
+
return valid_values and len(values) in self.param.nargs
|
|
122
|
+
|
|
114
123
|
# Note: Not used yet
|
|
115
124
|
# def _prep_and_validate(self, values: Sequence[str], combo: bool) -> Iterator[T_co]:
|
|
116
125
|
# prepare_value, validate = self.param.prepare_value, self.param.validate
|
|
@@ -123,10 +132,10 @@ class ParamAction(ABC):
|
|
|
123
132
|
|
|
124
133
|
# region Backtracking
|
|
125
134
|
|
|
126
|
-
def
|
|
135
|
+
def get_maybe_poppable_values(self) -> list[list[str]]:
|
|
127
136
|
"""
|
|
128
|
-
|
|
129
|
-
|
|
137
|
+
Groups of args that may be removed from this Parameter's parsed values such that the remaining number will
|
|
138
|
+
still be acceptable for its nargs.
|
|
130
139
|
"""
|
|
131
140
|
return []
|
|
132
141
|
|
|
@@ -162,7 +171,7 @@ class ParamAction(ABC):
|
|
|
162
171
|
|
|
163
172
|
class ValueMixin:
|
|
164
173
|
__slots__ = ()
|
|
165
|
-
param: Param
|
|
174
|
+
param: Param # noqa
|
|
166
175
|
get_default: Callable
|
|
167
176
|
|
|
168
177
|
def set_value(self, value):
|
|
@@ -195,7 +204,7 @@ class ValueMixin:
|
|
|
195
204
|
|
|
196
205
|
class ConstMixin:
|
|
197
206
|
__slots__ = ()
|
|
198
|
-
param: Param
|
|
207
|
+
param: Param # noqa
|
|
199
208
|
get_default: Callable
|
|
200
209
|
add_const: Callable
|
|
201
210
|
add_value: Callable
|
|
@@ -264,8 +273,8 @@ class Store(ValueMixin, ParamAction, default=None, accepts_values=True):
|
|
|
264
273
|
# ctx.record_action(self.param)
|
|
265
274
|
# if not values:
|
|
266
275
|
# raise MissingArgument(self.param)
|
|
267
|
-
# elif (val_count := len(values)) not in
|
|
268
|
-
# raise BadArgument(self.param, f'expected {nargs
|
|
276
|
+
# elif (val_count := len(values)) not in self.param.nargs:
|
|
277
|
+
# raise BadArgument(self.param, f'expected nargs={self.param.nargs} values but found {val_count}')
|
|
269
278
|
#
|
|
270
279
|
# self.set_value([value for value in self._prep_and_validate(values, combo)])
|
|
271
280
|
# return val_count
|
|
@@ -320,17 +329,13 @@ class Append(ValueMixin, ParamAction, accepts_values=True):
|
|
|
320
329
|
|
|
321
330
|
# region Backtracking
|
|
322
331
|
|
|
323
|
-
def
|
|
324
|
-
"""
|
|
325
|
-
:return: The indexes on which the parsed values may be split such that the remaining number of values will
|
|
326
|
-
still be acceptable for the Parameter's nargs.
|
|
327
|
-
"""
|
|
332
|
+
def get_maybe_poppable_values(self) -> list[list[str]]:
|
|
328
333
|
if not self.param.nargs.variable or self.param.type not in (None, str):
|
|
329
334
|
return []
|
|
330
335
|
elif (values := ctx.get_parsed_value(self.param)) is not _NotSet:
|
|
331
336
|
n_values = len(values)
|
|
332
337
|
satisfied = self.param.nargs.satisfied
|
|
333
|
-
return [i for i in range(1, n_values) if satisfied(n_values - i)]
|
|
338
|
+
return [values[-i:] for i in range(1, n_values) if satisfied(n_values - i)]
|
|
334
339
|
else:
|
|
335
340
|
return []
|
|
336
341
|
|
|
@@ -398,6 +403,9 @@ class BasicConstAction(ConstMixin, ParamAction, ABC, accepts_consts=True):
|
|
|
398
403
|
def would_accept(self, value: str, combo: bool = False) -> bool:
|
|
399
404
|
return False
|
|
400
405
|
|
|
406
|
+
def would_accept_all(self, values: list[str], combo: bool = False) -> bool:
|
|
407
|
+
return False
|
|
408
|
+
|
|
401
409
|
# endregion
|
|
402
410
|
|
|
403
411
|
|
|
@@ -507,6 +515,13 @@ class Concatenate(Append):
|
|
|
507
515
|
|
|
508
516
|
# endregion
|
|
509
517
|
|
|
518
|
+
# region Parsing
|
|
519
|
+
|
|
520
|
+
def would_accept_all(self, values: list[str], combo: bool = False) -> bool:
|
|
521
|
+
return self.param.is_valid_arg(values)
|
|
522
|
+
|
|
523
|
+
# endregion
|
|
524
|
+
|
|
510
525
|
# region Parsed Value / Default Finalization
|
|
511
526
|
|
|
512
527
|
def finalize_default(self, value):
|
|
@@ -96,6 +96,14 @@ class ParamBase(ABC):
|
|
|
96
96
|
|
|
97
97
|
# endregion
|
|
98
98
|
|
|
99
|
+
def __eq__(self, other: ParamBase) -> bool:
|
|
100
|
+
return (
|
|
101
|
+
self.__class__ == other.__class__
|
|
102
|
+
and self._attr_name == other._attr_name
|
|
103
|
+
and self._name == other._name
|
|
104
|
+
and self.command == other.command
|
|
105
|
+
)
|
|
106
|
+
|
|
99
107
|
def __hash__(self) -> int:
|
|
100
108
|
return hash(self.__class__) ^ hash(self._attr_name) ^ hash(self._name) ^ hash(self.command)
|
|
101
109
|
|
|
@@ -9,13 +9,13 @@ from __future__ import annotations
|
|
|
9
9
|
from functools import partial
|
|
10
10
|
from string import printable, whitespace
|
|
11
11
|
from types import MethodType
|
|
12
|
-
from typing import TYPE_CHECKING, Callable, Collection, Generic, Mapping, NoReturn,
|
|
12
|
+
from typing import TYPE_CHECKING, Callable, Collection, Generic, Mapping, NoReturn, Sequence, Type, TypeVar
|
|
13
13
|
|
|
14
14
|
from ..context import ctx
|
|
15
15
|
from ..exceptions import BadArgument, CommandDefinitionError, InvalidChoice, ParameterDefinitionError
|
|
16
16
|
from ..formatting.utils import format_help_entry
|
|
17
17
|
from ..nargs import Nargs
|
|
18
|
-
from ..typing import
|
|
18
|
+
from ..typing import CommandCls
|
|
19
19
|
from ..utils import _NotSet, camel_to_snake_case, short_repr
|
|
20
20
|
from .actions import Concatenate
|
|
21
21
|
from .base import BasePositional
|
|
@@ -23,12 +23,12 @@ from .base import BasePositional
|
|
|
23
23
|
if TYPE_CHECKING:
|
|
24
24
|
from ..formatting.params import ChoiceMapHelpFormatter
|
|
25
25
|
from ..metadata import ProgramMetadata
|
|
26
|
+
from ..typing import Bool, CommandObj, OptStr
|
|
26
27
|
|
|
27
28
|
__all__ = ['SubCommand', 'Action', 'Choice', 'ChoiceMap']
|
|
28
29
|
|
|
29
30
|
T = TypeVar('T')
|
|
30
31
|
TD = TypeVar('TD')
|
|
31
|
-
OptStr = Optional[str]
|
|
32
32
|
# TODO: Combine SubCommand and Action, replacing `local_choices` with stackable decorators on the target method,
|
|
33
33
|
# optionally injecting the selected choice into positional args for the decorated method, which may be main?
|
|
34
34
|
|
|
@@ -142,7 +142,7 @@ class ChoiceMap(BasePositional[str], Generic[T], actions=(Concatenate,)):
|
|
|
142
142
|
def _register_choice(
|
|
143
143
|
self,
|
|
144
144
|
choice: OptStr,
|
|
145
|
-
target:
|
|
145
|
+
target: T | None = _NotSet,
|
|
146
146
|
help: str = None, # noqa
|
|
147
147
|
local: bool = False,
|
|
148
148
|
):
|
|
@@ -162,21 +162,26 @@ class ChoiceMap(BasePositional[str], Generic[T], actions=(Concatenate,)):
|
|
|
162
162
|
|
|
163
163
|
# region Argument Handling
|
|
164
164
|
|
|
165
|
-
def validate(self, value: str, joined: Bool = False):
|
|
165
|
+
def validate(self, value: str | Sequence[str], joined: Bool = False):
|
|
166
166
|
if not self.choices:
|
|
167
167
|
self._no_choices_error()
|
|
168
168
|
|
|
169
169
|
parsed = ctx.get_parsed_value(self)
|
|
170
|
-
|
|
170
|
+
if parsed is _NotSet:
|
|
171
|
+
values = (value,) if isinstance(value, str) else value
|
|
172
|
+
else:
|
|
173
|
+
values = (*parsed, value) if isinstance(value, str) else (*parsed, *value)
|
|
174
|
+
|
|
171
175
|
if (choice := ' '.join(values)) in self.choices:
|
|
172
176
|
return
|
|
173
177
|
elif len(values) > self.nargs.max:
|
|
174
178
|
raise BadArgument(self, 'too many values')
|
|
179
|
+
|
|
175
180
|
prefix = choice + ' '
|
|
176
181
|
if not any(c.startswith(prefix) for c in self.choices if c):
|
|
177
182
|
raise InvalidChoice(self, prefix[:-1], self.choices)
|
|
178
183
|
|
|
179
|
-
def result(self, command: CommandObj | None = None, missing_default: TD = _NotSet) ->
|
|
184
|
+
def result(self, command: CommandObj | None = None, missing_default: TD = _NotSet) -> OptStr | TD:
|
|
180
185
|
if not self.choices:
|
|
181
186
|
self._no_choices_error()
|
|
182
187
|
return super().result(command, missing_default)
|
|
@@ -211,7 +216,7 @@ class SubCommand(ChoiceMap[CommandCls], title='Subcommands', choice_validation_e
|
|
|
211
216
|
*,
|
|
212
217
|
required: Bool = True,
|
|
213
218
|
default_help: str = None,
|
|
214
|
-
local_choices:
|
|
219
|
+
local_choices: Mapping[str, str] | Collection[str] | None = None,
|
|
215
220
|
**kwargs,
|
|
216
221
|
):
|
|
217
222
|
"""
|
|
@@ -237,7 +242,7 @@ class SubCommand(ChoiceMap[CommandCls], title='Subcommands', choice_validation_e
|
|
|
237
242
|
def has_local_choices(self) -> bool:
|
|
238
243
|
return None in self.choices or any(c.target is None for c in self.choices.values())
|
|
239
244
|
|
|
240
|
-
def _register_local_choices(self, local_choices:
|
|
245
|
+
def _register_local_choices(self, local_choices: Mapping[str, str] | Collection[str]):
|
|
241
246
|
try:
|
|
242
247
|
choice_help_iter = local_choices.items()
|
|
243
248
|
except AttributeError:
|
|
@@ -273,7 +278,7 @@ class SubCommand(ChoiceMap[CommandCls], title='Subcommands', choice_validation_e
|
|
|
273
278
|
|
|
274
279
|
def register(
|
|
275
280
|
self,
|
|
276
|
-
command_or_choice:
|
|
281
|
+
command_or_choice: str | CommandCls | None = None,
|
|
277
282
|
*,
|
|
278
283
|
choice: str = None,
|
|
279
284
|
help: str = None, # noqa
|
|
@@ -344,12 +349,12 @@ class Action(ChoiceMap[MethodType], title='Actions'):
|
|
|
344
349
|
|
|
345
350
|
def register(
|
|
346
351
|
self,
|
|
347
|
-
method_or_choice:
|
|
352
|
+
method_or_choice: str | MethodType | None = None,
|
|
348
353
|
*,
|
|
349
354
|
choice: str = None,
|
|
350
355
|
help: str = None, # noqa
|
|
351
356
|
default: Bool = False,
|
|
352
|
-
) ->
|
|
357
|
+
) -> MethodType | Callable[[MethodType], MethodType]:
|
|
353
358
|
"""
|
|
354
359
|
Decorator that registers the wrapped method to be called when the given choice is specified for this parameter.
|
|
355
360
|
Methods may also be registered by decorating them with the instantiated Action parameter directly - doing so
|
|
@@ -7,9 +7,8 @@ Optional Parameters
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
|
-
from abc import ABC
|
|
11
10
|
from functools import partial, update_wrapper
|
|
12
|
-
from typing import TYPE_CHECKING, Any, Callable, Literal, NoReturn,
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Callable, Literal, NoReturn, TypeVar, Union
|
|
13
12
|
|
|
14
13
|
from ..exceptions import BadArgument, CommandDefinitionError, ParameterDefinitionError, ParamUsageError, ParserExit
|
|
15
14
|
from ..inputs import normalize_input_type
|
|
@@ -188,7 +187,7 @@ class Flag(BaseOption[Union[TD, TC]], actions=(StoreConst, AppendConst)):
|
|
|
188
187
|
def register_default_cb(self, method):
|
|
189
188
|
raise ParameterDefinitionError(f'{self.__class__.__name__}s do not support default callback methods')
|
|
190
189
|
|
|
191
|
-
def get_env_const(self, value: str, env_var: str) -> tuple[
|
|
190
|
+
def get_env_const(self, value: str, env_var: str) -> tuple[TC | TD, bool]:
|
|
192
191
|
try:
|
|
193
192
|
parsed = self.type(value)
|
|
194
193
|
except Exception as e:
|
|
@@ -198,7 +197,7 @@ class Flag(BaseOption[Union[TD, TC]], actions=(StoreConst, AppendConst)):
|
|
|
198
197
|
return parsed, self.use_env_value
|
|
199
198
|
|
|
200
199
|
|
|
201
|
-
class TriFlag(BaseOption[Union[TD, TC, TA]],
|
|
200
|
+
class TriFlag(BaseOption[Union[TD, TC, TA]], actions=(StoreConst, AppendConst)):
|
|
202
201
|
"""
|
|
203
202
|
A trinary / ternary Flag. While :class:`.Flag` only supports 1 constant when provided, with 1 default if not
|
|
204
203
|
provided, this class accepts a pair of constants for the primary and alternate values to store, along with a
|
|
@@ -298,13 +297,13 @@ class TriFlag(BaseOption[Union[TD, TC, TA]], ABC, actions=(StoreConst, AppendCon
|
|
|
298
297
|
self.default = _NotSet # The default was set by __init__ - remove it so the method can be registered
|
|
299
298
|
return super().register_default_cb(method)
|
|
300
299
|
|
|
301
|
-
def get_const(self, opt_str: OptStr = None) ->
|
|
300
|
+
def get_const(self, opt_str: OptStr = None) -> TC | TA:
|
|
302
301
|
if opt_str in self.option_strs.alt_allowed:
|
|
303
302
|
return self.consts[1]
|
|
304
303
|
else:
|
|
305
304
|
return self.consts[0]
|
|
306
305
|
|
|
307
|
-
def get_env_const(self, value: str, env_var: str) -> tuple[
|
|
306
|
+
def get_env_const(self, value: str, env_var: str) -> tuple[TC | TA | TD, bool]:
|
|
308
307
|
try:
|
|
309
308
|
parsed = self.type(value)
|
|
310
309
|
except Exception as e:
|
|
@@ -342,7 +341,7 @@ class ActionFlag(Flag, repr_attrs=('order', 'before_main')):
|
|
|
342
341
|
def __init__(
|
|
343
342
|
self,
|
|
344
343
|
*option_strs: str,
|
|
345
|
-
order:
|
|
344
|
+
order: int | float = 1,
|
|
346
345
|
func: Callable = None,
|
|
347
346
|
before_main: Bool = True, # noqa # pylint: disable=W0621
|
|
348
347
|
always_available: Bool = False,
|
|
@@ -364,7 +363,7 @@ class ActionFlag(Flag, repr_attrs=('order', 'before_main')):
|
|
|
364
363
|
return self._func
|
|
365
364
|
|
|
366
365
|
@func.setter
|
|
367
|
-
def func(self, func:
|
|
366
|
+
def func(self, func: Callable | None):
|
|
368
367
|
self._func = func
|
|
369
368
|
if func is not None:
|
|
370
369
|
if self.help is None:
|
|
@@ -403,7 +402,7 @@ class ActionFlag(Flag, repr_attrs=('order', 'before_main')):
|
|
|
403
402
|
self.func = func
|
|
404
403
|
return self
|
|
405
404
|
|
|
406
|
-
def __get__(self, command:
|
|
405
|
+
def __get__(self, command: CommandObj | None, owner: CommandCls) -> ActionFlag | Callable:
|
|
407
406
|
# Allow the method to be called, regardless of whether it was specified
|
|
408
407
|
if command is None:
|
|
409
408
|
return self
|
|
@@ -424,12 +423,12 @@ class ActionFlag(Flag, repr_attrs=('order', 'before_main')):
|
|
|
424
423
|
action_flag = ActionFlag # pylint: disable=C0103
|
|
425
424
|
|
|
426
425
|
|
|
427
|
-
def before_main(*option_strs: str, order:
|
|
426
|
+
def before_main(*option_strs: str, order: int | float = 1, func: Callable = None, **kwargs) -> ActionFlag:
|
|
428
427
|
"""An ActionFlag that will be executed before :meth:`.Command.main`"""
|
|
429
428
|
return ActionFlag(*option_strs, order=order, func=func, before_main=True, **kwargs)
|
|
430
429
|
|
|
431
430
|
|
|
432
|
-
def after_main(*option_strs: str, order:
|
|
431
|
+
def after_main(*option_strs: str, order: int | float = 1, func: Callable = None, **kwargs) -> ActionFlag:
|
|
433
432
|
"""An ActionFlag that will be executed after :meth:`.Command.main`"""
|
|
434
433
|
return ActionFlag(*option_strs, order=order, func=func, before_main=False, **kwargs)
|
|
435
434
|
|
|
@@ -500,7 +499,7 @@ class Counter(BaseOption[int], actions=(Count,)):
|
|
|
500
499
|
self.default_cb = None
|
|
501
500
|
return super().register_default_cb(method)
|
|
502
501
|
|
|
503
|
-
def prepare_value(self, value:
|
|
502
|
+
def prepare_value(self, value: str | None, short_combo: bool = False, env_var: str = None) -> int:
|
|
504
503
|
try:
|
|
505
504
|
return self.type(value)
|
|
506
505
|
except (ValueError, TypeError) as e:
|
|
@@ -516,7 +515,7 @@ class Counter(BaseOption[int], actions=(Count,)):
|
|
|
516
515
|
if value is None or isinstance(value, self.type):
|
|
517
516
|
return
|
|
518
517
|
try:
|
|
519
|
-
|
|
518
|
+
self.type(value)
|
|
520
519
|
except (ValueError, TypeError) as e:
|
|
521
520
|
raise BadArgument(self, f'invalid {value=} (expected an integer)') from e
|
|
522
521
|
else:
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/parse_tree.py
RENAMED
|
@@ -7,7 +7,6 @@ from __future__ import annotations
|
|
|
7
7
|
from typing import TYPE_CHECKING, Collection, Iterable, Iterator, MutableMapping, Optional, Union
|
|
8
8
|
|
|
9
9
|
from .exceptions import AmbiguousParseTree
|
|
10
|
-
from .nargs import nargs_min_and_max_sums
|
|
11
10
|
from .utils import _parse_tree_target_repr
|
|
12
11
|
|
|
13
12
|
if TYPE_CHECKING:
|
|
@@ -89,9 +88,6 @@ class PosNode(MutableMapping[Word, 'PosNode']):
|
|
|
89
88
|
for node in self.values():
|
|
90
89
|
yield from node._link_params(_has_upper_bound(node))
|
|
91
90
|
|
|
92
|
-
def nargs_min_and_max(self) -> tuple[int, Union[int, float]]:
|
|
93
|
-
return nargs_min_and_max_sums(p.nargs for p in self.link_params(True))
|
|
94
|
-
|
|
95
91
|
# region AnyWord Methods
|
|
96
92
|
|
|
97
93
|
@property
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/parser.py
RENAMED
|
@@ -9,7 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
import logging
|
|
10
10
|
from collections import deque
|
|
11
11
|
from os import environ
|
|
12
|
-
from typing import TYPE_CHECKING, Deque,
|
|
12
|
+
from typing import TYPE_CHECKING, Deque, Sequence
|
|
13
13
|
|
|
14
14
|
from .context import ActionPhase, Context
|
|
15
15
|
from .core import get_parent
|
|
@@ -22,7 +22,7 @@ from .exceptions import (
|
|
|
22
22
|
ParamUsageError,
|
|
23
23
|
UsageError,
|
|
24
24
|
)
|
|
25
|
-
from .nargs import REMAINDER
|
|
25
|
+
from .nargs import REMAINDER
|
|
26
26
|
from .parameters.base import BaseOption, BasePositional, Parameter
|
|
27
27
|
from .parse_tree import PosNode
|
|
28
28
|
|
|
@@ -45,12 +45,12 @@ class CommandParser:
|
|
|
45
45
|
|
|
46
46
|
__slots__ = ('_last', 'arg_deque', 'ctx', 'config', 'deferred', 'params', 'positionals')
|
|
47
47
|
|
|
48
|
-
arg_deque:
|
|
48
|
+
arg_deque: Deque[str] | None
|
|
49
49
|
config: CommandConfig
|
|
50
|
-
deferred:
|
|
50
|
+
deferred: list[str] | None
|
|
51
51
|
params: CommandParameters
|
|
52
52
|
positionals: list[BasePositional]
|
|
53
|
-
_last:
|
|
53
|
+
_last: Parameter | None
|
|
54
54
|
|
|
55
55
|
def __init__(self, ctx: Context, params: CommandParameters, config: CommandConfig):
|
|
56
56
|
self._last = None
|
|
@@ -62,7 +62,7 @@ class CommandParser:
|
|
|
62
62
|
PosNode.build_tree(ctx.command_cls)
|
|
63
63
|
|
|
64
64
|
@classmethod
|
|
65
|
-
def parse_args_and_get_next_cmd(cls, ctx: Context) ->
|
|
65
|
+
def parse_args_and_get_next_cmd(cls, ctx: Context) -> CommandType | None:
|
|
66
66
|
try:
|
|
67
67
|
return cls(ctx, ctx.params, ctx.config).get_next_cmd(ctx)
|
|
68
68
|
except UsageError:
|
|
@@ -70,7 +70,7 @@ class CommandParser:
|
|
|
70
70
|
raise
|
|
71
71
|
return None
|
|
72
72
|
|
|
73
|
-
def get_next_cmd(self, ctx: Context) ->
|
|
73
|
+
def get_next_cmd(self, ctx: Context) -> CommandType | None:
|
|
74
74
|
self._parse_args(ctx)
|
|
75
75
|
self._validate_groups()
|
|
76
76
|
missing = ctx.get_missing()
|
|
@@ -273,7 +273,7 @@ class CommandParser:
|
|
|
273
273
|
if len(self.positionals) == 1 and 0 in self.positionals[0].nargs:
|
|
274
274
|
raise NextCommand
|
|
275
275
|
else:
|
|
276
|
-
raise ParamUsageError(param, 'subcommand arguments must be provided after the subcommand')
|
|
276
|
+
raise ParamUsageError(param, 'subcommand arguments must be provided after the subcommand') # noqa
|
|
277
277
|
|
|
278
278
|
# region Backtracking
|
|
279
279
|
|
|
@@ -287,26 +287,58 @@ class CommandParser:
|
|
|
287
287
|
:param found: The number of values that were consumed by the given Parameter
|
|
288
288
|
:return: The updated found count, if backtracking was possible, otherwise the unmodified found count
|
|
289
289
|
"""
|
|
290
|
-
if self.positionals:
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
290
|
+
if not self.positionals:
|
|
291
|
+
return found
|
|
292
|
+
elif rollback_count := self._get_backtrack_count(param):
|
|
293
|
+
self.arg_deque.extendleft(reversed(self.ctx.roll_back_parsed_values(param, rollback_count)))
|
|
294
|
+
return found - rollback_count
|
|
295
|
+
else:
|
|
296
|
+
return found
|
|
297
|
+
|
|
298
|
+
def _get_backtrack_count(
|
|
299
|
+
self, param: Parameter, extras: Sequence[str] = (), positionals: Sequence[BasePositional] = ()
|
|
300
|
+
) -> int:
|
|
301
|
+
if poppable_groups := param.action.get_maybe_poppable_values():
|
|
302
|
+
return next((len(g) for g in poppable_groups if self._should_backtrack(g, extras, positionals)), 0)
|
|
303
|
+
return 0
|
|
304
|
+
|
|
305
|
+
def _should_backtrack(
|
|
306
|
+
self, group: list[str], extras: Sequence[str] = (), positionals: Sequence[BasePositional] = ()
|
|
307
|
+
) -> bool:
|
|
308
|
+
args = [*group, *extras, *self.arg_deque]
|
|
309
|
+
for pos_param in positionals or self.positionals:
|
|
310
|
+
n = pos_param.nargs.min
|
|
311
|
+
if not n and 1 in pos_param.nargs:
|
|
312
|
+
n = 1
|
|
313
|
+
|
|
314
|
+
param_args = args[:n]
|
|
315
|
+
if len(param_args) != n or not pos_param.action.would_accept_all(param_args):
|
|
316
|
+
return False
|
|
317
|
+
|
|
318
|
+
args = args[n:]
|
|
296
319
|
|
|
297
|
-
|
|
320
|
+
return True
|
|
321
|
+
|
|
322
|
+
def _maybe_backtrack_last_positional(self, param: BasePositional):
|
|
298
323
|
"""
|
|
299
324
|
Similar to :meth:`._maybe_backtrack`, but allows backtracking even after starting to process a Positional.
|
|
325
|
+
|
|
326
|
+
By the time this method is called, it has already been discovered that `found` does not satisfy `param`'s
|
|
327
|
+
nargs requirements.
|
|
300
328
|
"""
|
|
301
329
|
if not self.config.allow_backtrack:
|
|
302
|
-
# This method is called
|
|
330
|
+
# This method is called extremely rarely & it's cleaner to have this check here than in _finalize_consume
|
|
303
331
|
return
|
|
304
332
|
|
|
305
|
-
|
|
306
|
-
# It is extremely unlikely for this point to be reached without this resulting in triggering
|
|
307
|
-
if
|
|
333
|
+
parsed = self.ctx.get_parsed_value(param, ())
|
|
334
|
+
# It is extremely unlikely for this point to be reached without this resulting in triggering backtrack
|
|
335
|
+
if num := self._get_backtrack_count(self._last, parsed, (param, *self.positionals)):
|
|
336
|
+
# log.debug(f'Rolling back {num} parsed values from {self._last=} / {param=} and triggering Backtrack')
|
|
337
|
+
# Reset all of this param's parsed args because the previous param's roll back args need to be injected
|
|
338
|
+
# before them so they can be processed by this parameter.
|
|
308
339
|
self.arg_deque.extendleft(reversed(self.ctx.pop_parsed_value(param)))
|
|
309
|
-
|
|
340
|
+
# Roll back a subset of the previous param's parsed args
|
|
341
|
+
self.arg_deque.extendleft(reversed(self.ctx.roll_back_parsed_values(self._last, num)))
|
|
310
342
|
raise Backtrack
|
|
311
343
|
|
|
312
344
|
# endregion
|
|
@@ -353,15 +385,13 @@ class CommandParser:
|
|
|
353
385
|
# log.debug(f'{value=} was rejected by {param=}', exc_info=True)
|
|
354
386
|
return self._finalize_consume(param, value, found, e)
|
|
355
387
|
|
|
356
|
-
# TODO: Positional(nargs='?') with no values will steal values intended for an Option(nargs='+')
|
|
357
|
-
# (likely occurs with nargs='*' for the Positional as well)
|
|
358
|
-
|
|
359
388
|
# log.debug(f'Ran out of values in deque while processing {param=}')
|
|
360
389
|
if found >= 2 and self.config.allow_backtrack:
|
|
361
390
|
found = self._maybe_backtrack(param, found)
|
|
362
391
|
return self._finalize_consume(param, None, found)
|
|
363
392
|
|
|
364
|
-
def _finalize_consume(self, param: Parameter, value: OptStr, found: int, exc:
|
|
393
|
+
def _finalize_consume(self, param: Parameter, value: OptStr, found: int, exc: Exception | None = None) -> int:
|
|
394
|
+
# log.debug(f'Finalizing arg consumption for {param=}, {value=}, {found=}, {exc=}')
|
|
365
395
|
nargs = param.nargs
|
|
366
396
|
if nargs.satisfied(found):
|
|
367
397
|
# Even if an exception was passed to this method, if the found number of values is acceptable, then it
|
|
@@ -373,7 +403,7 @@ class CommandParser:
|
|
|
373
403
|
elif exc:
|
|
374
404
|
raise exc
|
|
375
405
|
elif self._last and isinstance(param, BasePositional) and param.action.can_reset():
|
|
376
|
-
self.
|
|
406
|
+
self._maybe_backtrack_last_positional(param)
|
|
377
407
|
|
|
378
408
|
s = '' if nargs.min == 1 else 's'
|
|
379
409
|
raise MissingArgument(param, f'expected {nargs.min} value{s}, but only found {found}')
|
|
@@ -382,22 +412,6 @@ class CommandParser:
|
|
|
382
412
|
parse_args_and_get_next_cmd = CommandParser.parse_args_and_get_next_cmd
|
|
383
413
|
|
|
384
414
|
|
|
385
|
-
def _to_pop(positionals: Iterable[BasePositional], can_pop: list[int], available: int, req_mod: int = 0) -> int:
|
|
386
|
-
if not can_pop:
|
|
387
|
-
return 0
|
|
388
|
-
|
|
389
|
-
required, acceptable = nargs_min_and_max_sums(p.nargs for p in positionals)
|
|
390
|
-
if available < required:
|
|
391
|
-
return 0
|
|
392
|
-
|
|
393
|
-
required -= req_mod
|
|
394
|
-
for n in can_pop:
|
|
395
|
-
if required <= n <= acceptable:
|
|
396
|
-
return n
|
|
397
|
-
|
|
398
|
-
return 0
|
|
399
|
-
|
|
400
|
-
|
|
401
415
|
def get_opt_prefix(text: str) -> OptStr:
|
|
402
416
|
if not text or text[0] != '-':
|
|
403
417
|
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli_command_parser
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.7.13
|
|
4
4
|
Summary: CLI Command Parser
|
|
5
5
|
Home-page: https://github.com/dskrypa/cli_command_parser
|
|
6
6
|
Author: Doug Skrypa
|
|
@@ -58,11 +58,11 @@ CLI Command Parser is a class-based CLI argument parser that defines parameters
|
|
|
58
58
|
tools to quickly and easily get started with basic CLIs, and it scales well to support even very large and complex
|
|
59
59
|
CLIs while remaining readable and easy to maintain.
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
61
|
+
Some of the primary goals and key features of this project:
|
|
62
|
+
- Minimal boilerplate code is necessary to define CLI parameters and access their parsed values
|
|
63
|
+
- Easy to use type annotations for CLI parameters
|
|
64
|
+
- Subcommands can inherit common parameters so they don't need to be repeated
|
|
65
|
+
- Easy to handle common initialization tasks for all actions / subcommands once
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
Example Program
|
|
@@ -26,11 +26,11 @@ CLI Command Parser is a class-based CLI argument parser that defines parameters
|
|
|
26
26
|
tools to quickly and easily get started with basic CLIs, and it scales well to support even very large and complex
|
|
27
27
|
CLIs while remaining readable and easy to maintain.
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
29
|
+
Some of the primary goals and key features of this project:
|
|
30
|
+
- Minimal boilerplate code is necessary to define CLI parameters and access their parsed values
|
|
31
|
+
- Easy to use type annotations for CLI parameters
|
|
32
|
+
- Subcommands can inherit common parameters so they don't need to be repeated
|
|
33
|
+
- Easy to handle common initialization tasks for all actions / subcommands once
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
Example Program
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/__init__.py
RENAMED
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/__main__.py
RENAMED
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/annotations.py
RENAMED
|
File without changes
|
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/commands.py
RENAMED
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/compat.py
RENAMED
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/core.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/inputs/base.py
RENAMED
|
File without changes
|
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/inputs/files.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/inputs/time.py
RENAMED
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/inputs/utils.py
RENAMED
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/metadata.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/testing.py
RENAMED
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/typing.py
RENAMED
|
File without changes
|
{cli_command_parser-2025.6.14 → cli_command_parser-2025.7.13}/lib/cli_command_parser/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|