cli-command-parser 2024.11.2__tar.gz → 2025.5.10__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-2024.11.2/lib/cli_command_parser.egg-info → cli_command_parser-2025.5.10}/PKG-INFO +134 -130
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/__version__.py +2 -2
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/exceptions.py +8 -8
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/inputs/base.py +3 -3
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/inputs/choices.py +6 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/inputs/numeric.py +1 -1
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/parameters/actions.py +23 -21
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/parameters/base.py +16 -8
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/parameters/options.py +9 -6
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/parser.py +4 -3
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/testing.py +3 -4
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10/lib/cli_command_parser.egg-info}/PKG-INFO +134 -130
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/readme.rst +4 -1
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/setup.cfg +50 -50
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/LICENSE +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/MANIFEST.in +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/entry_points.txt +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/__init__.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/__main__.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/annotations.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/command_parameters.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/commands.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/compat.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/config.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/context.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/conversion/__init__.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/conversion/__main__.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/conversion/argparse_ast.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/conversion/argparse_utils.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/conversion/cli.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/conversion/command_builder.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/conversion/utils.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/conversion/visitor.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/core.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/documentation.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/error_handling.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/formatting/__init__.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/formatting/commands.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/formatting/params.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/formatting/restructured_text.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/formatting/utils.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/inputs/__init__.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/inputs/exceptions.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/inputs/files.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/inputs/patterns.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/inputs/time.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/inputs/utils.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/metadata.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/nargs.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/parameters/__init__.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/parameters/choice_map.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/parameters/groups.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/parameters/option_strings.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/parameters/pass_thru.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/parameters/positionals.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/parse_tree.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/typing.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/utils.py +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser.egg-info/SOURCES.txt +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser.egg-info/dependency_links.txt +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser.egg-info/entry_points.txt +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser.egg-info/requires.txt +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser.egg-info/top_level.txt +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/pyproject.toml +0 -0
- {cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/requirements-dev.txt +0 -0
|
@@ -1,130 +1,134 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
2
|
-
Name: cli_command_parser
|
|
3
|
-
Version:
|
|
4
|
-
Summary: CLI Command Parser
|
|
5
|
-
Home-page: https://github.com/dskrypa/cli_command_parser
|
|
6
|
-
Author: Doug Skrypa
|
|
7
|
-
Author-email: dskrypa@gmail.com
|
|
8
|
-
License: Apache 2.0
|
|
9
|
-
Project-URL: Source, https://github.com/dskrypa/cli_command_parser
|
|
10
|
-
Project-URL: Documentation, https://dskrypa.github.io/cli_command_parser
|
|
11
|
-
Project-URL: Issues, https://github.com/dskrypa/cli_command_parser/issues
|
|
12
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
-
Classifier: Environment :: Console
|
|
14
|
-
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
-
Classifier: Operating System :: OS Independent
|
|
17
|
-
Classifier: Programming Language :: Python
|
|
18
|
-
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
-
Classifier: Topic :: Software Development :: User Interfaces
|
|
25
|
-
Classifier: Topic :: Text Processing
|
|
26
|
-
Requires-Python: >=3.9
|
|
27
|
-
Description-Content-Type: text/x-rst
|
|
28
|
-
License-File: LICENSE
|
|
29
|
-
Provides-Extra: wcwidth
|
|
30
|
-
Requires-Dist: wcwidth; extra == "wcwidth"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
-
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cli_command_parser
|
|
3
|
+
Version: 2025.5.10
|
|
4
|
+
Summary: CLI Command Parser
|
|
5
|
+
Home-page: https://github.com/dskrypa/cli_command_parser
|
|
6
|
+
Author: Doug Skrypa
|
|
7
|
+
Author-email: dskrypa@gmail.com
|
|
8
|
+
License: Apache 2.0
|
|
9
|
+
Project-URL: Source, https://github.com/dskrypa/cli_command_parser
|
|
10
|
+
Project-URL: Documentation, https://dskrypa.github.io/cli_command_parser
|
|
11
|
+
Project-URL: Issues, https://github.com/dskrypa/cli_command_parser/issues
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
25
|
+
Classifier: Topic :: Text Processing
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Description-Content-Type: text/x-rst
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Provides-Extra: wcwidth
|
|
30
|
+
Requires-Dist: wcwidth; extra == "wcwidth"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
CLI Command Parser
|
|
34
|
+
##################
|
|
35
|
+
|
|
36
|
+
|downloads| |py_version| |coverage_badge| |build_status| |Ruff| |OpenSSF Best Practices|
|
|
37
|
+
|
|
38
|
+
.. |py_version| image:: https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20-blue
|
|
39
|
+
:target: https://pypi.org/project/cli-command-parser/
|
|
40
|
+
|
|
41
|
+
.. |coverage_badge| image:: https://codecov.io/gh/dskrypa/cli_command_parser/branch/main/graph/badge.svg
|
|
42
|
+
:target: https://codecov.io/gh/dskrypa/cli_command_parser
|
|
43
|
+
|
|
44
|
+
.. |build_status| image:: https://github.com/dskrypa/cli_command_parser/actions/workflows/run-tests.yml/badge.svg
|
|
45
|
+
:target: https://github.com/dskrypa/cli_command_parser/actions/workflows/run-tests.yml
|
|
46
|
+
|
|
47
|
+
.. |Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
|
|
48
|
+
:target: https://docs.astral.sh/ruff/
|
|
49
|
+
|
|
50
|
+
.. |downloads| image:: https://img.shields.io/pypi/dm/cli-command-parser
|
|
51
|
+
:target: https://pypistats.org/packages/cli-command-parser
|
|
52
|
+
|
|
53
|
+
.. |OpenSSF Best Practices| image:: https://www.bestpractices.dev/projects/9845/badge
|
|
54
|
+
:target: https://www.bestpractices.dev/projects/9845
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
CLI Command Parser is a class-based CLI argument parser that defines parameters with descriptors. It provides the
|
|
58
|
+
tools to quickly and easily get started with basic CLIs, and it scales well to support even very large and complex
|
|
59
|
+
CLIs while remaining readable and easy to maintain.
|
|
60
|
+
|
|
61
|
+
The primary goals of this project:
|
|
62
|
+
- Make it easy to define subcommands and actions in an clean and organized manner
|
|
63
|
+
- Allow for inheritance so that common parameters don't need to be repeated
|
|
64
|
+
- Make it easy to handle common initialization tasks for all actions / subcommands once
|
|
65
|
+
- Reduce the amount of boilerplate code that is necessary for setting up parsing and handling argument values
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
Example Program
|
|
69
|
+
***************
|
|
70
|
+
|
|
71
|
+
.. code-block:: python
|
|
72
|
+
|
|
73
|
+
from cli_command_parser import Command, Option, main
|
|
74
|
+
|
|
75
|
+
class Hello(Command, description='Simple greeting example'):
|
|
76
|
+
name = Option('-n', default='World', help='The person to say hello to')
|
|
77
|
+
count: int = Option('-c', default=1, help='Number of times to repeat the message')
|
|
78
|
+
|
|
79
|
+
def main(self):
|
|
80
|
+
for _ in range(self.count):
|
|
81
|
+
print(f'Hello {self.name}!')
|
|
82
|
+
|
|
83
|
+
if __name__ == '__main__':
|
|
84
|
+
main()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
.. code-block:: shell-session
|
|
88
|
+
|
|
89
|
+
$ hello_world.py --name Bob -c 3
|
|
90
|
+
Hello Bob!
|
|
91
|
+
Hello Bob!
|
|
92
|
+
Hello Bob!
|
|
93
|
+
|
|
94
|
+
$ hello_world.py -h
|
|
95
|
+
usage: hello_world.py [--name NAME] [--count COUNT] [--help]
|
|
96
|
+
|
|
97
|
+
Simple greeting example
|
|
98
|
+
|
|
99
|
+
Optional arguments:
|
|
100
|
+
--name NAME, -n NAME The person to say hello to (default: 'World')
|
|
101
|
+
--count COUNT, -c COUNT Number of times to repeat the message (default: 1)
|
|
102
|
+
--help, -h Show this help message and exit
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
Installing CLI Command Parser
|
|
106
|
+
*****************************
|
|
107
|
+
|
|
108
|
+
CLI Command Parser can be installed and updated via `pip <https://pip.pypa.io/en/stable/getting-started/>`__::
|
|
109
|
+
|
|
110
|
+
$ pip install -U cli-command-parser
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
There are no required dependencies. Support for formatting wide characters correctly in help text descriptions can
|
|
114
|
+
be included by adding `wcwidth <https://wcwidth.readthedocs.io>`__ to your project's requirements, and/or by installing
|
|
115
|
+
with optional dependencies::
|
|
116
|
+
|
|
117
|
+
$ pip install -U cli-command-parser[wcwidth]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
Python Version Compatibility
|
|
121
|
+
============================
|
|
122
|
+
|
|
123
|
+
Python versions 3.9 and above are currently supported. The last release of CLI Command Parser that supported 3.8 was
|
|
124
|
+
2024-09-07. Support for Python 3.8 `officially ended on 2024-10-07 <https://devguide.python.org/versions/>`__.
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
Links
|
|
128
|
+
*****
|
|
129
|
+
|
|
130
|
+
- Documentation: https://dskrypa.github.io/cli_command_parser/
|
|
131
|
+
- Example Scripts: https://github.com/dskrypa/cli_command_parser/tree/main/examples
|
|
132
|
+
- PyPI Releases: https://pypi.org/project/cli-command-parser/
|
|
133
|
+
- Source Code: https://github.com/dskrypa/cli_command_parser
|
|
134
|
+
- Issue Tracker: https://github.com/dskrypa/cli_command_parser/issues
|
{cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/__version__.py
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
__title__ = 'cli_command_parser'
|
|
2
2
|
__description__ = 'CLI Command Parser'
|
|
3
3
|
__url__ = 'https://github.com/dskrypa/cli_command_parser'
|
|
4
|
-
__version__ = '
|
|
4
|
+
__version__ = '2025.05.10'
|
|
5
5
|
__author__ = 'Doug Skrypa'
|
|
6
6
|
__author_email__ = 'dskrypa@gmail.com'
|
|
7
7
|
__license__ = 'Apache 2.0'
|
|
8
|
-
__copyright__ = 'Copyright
|
|
8
|
+
__copyright__ = 'Copyright 2024 Doug Skrypa'
|
{cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/exceptions.py
RENAMED
|
@@ -8,14 +8,14 @@ Exceptions for Command Parser
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import sys
|
|
11
|
-
from typing import TYPE_CHECKING, Any,
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Collection, Mapping, Optional
|
|
12
12
|
|
|
13
13
|
from .utils import _parse_tree_target_repr
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
|
-
from .parameters import
|
|
16
|
+
from .parameters import BaseOption, Parameter
|
|
17
|
+
from .parse_tree import PosNode, Target, Word
|
|
17
18
|
from .typing import ParamOrGroup
|
|
18
|
-
from .parse_tree import PosNode, Word, Target
|
|
19
19
|
|
|
20
20
|
__all__ = [
|
|
21
21
|
'CommandParserException',
|
|
@@ -211,13 +211,13 @@ class BadArgument(ParamUsageError):
|
|
|
211
211
|
class InvalidChoice(BadArgument):
|
|
212
212
|
"""Error raised when a value that does not match one of the pre-defined choices was provided for a Parameter"""
|
|
213
213
|
|
|
214
|
-
def __init__(self, param: Optional[Parameter], invalid: Any, choices: Collection[Any]):
|
|
214
|
+
def __init__(self, param: Optional[Parameter], invalid: Any, choices: Collection[Any], env_var: str = None):
|
|
215
|
+
src = f' from env var={env_var!r}' if env_var else ''
|
|
215
216
|
if isinstance(invalid, Collection) and not isinstance(invalid, str):
|
|
216
|
-
bad_str = f'choices: {", ".join(map(repr, invalid))}'
|
|
217
|
+
bad_str = f'choices{src}: {", ".join(map(repr, invalid))}'
|
|
217
218
|
else:
|
|
218
|
-
bad_str = f'choice: {invalid!r}'
|
|
219
|
-
|
|
220
|
-
super().__init__(param, f'invalid {bad_str} (choose from: {choices_str})')
|
|
219
|
+
bad_str = f'choice{src}: {invalid!r}'
|
|
220
|
+
super().__init__(param, f'invalid {bad_str} (choose from: {", ".join(map(repr, choices))})')
|
|
221
221
|
|
|
222
222
|
|
|
223
223
|
class MissingArgument(BadArgument):
|
{cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/inputs/base.py
RENAMED
|
@@ -7,7 +7,7 @@ Custom input handlers for Parameters
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
from typing import Any, Generic, Optional
|
|
9
9
|
|
|
10
|
-
from ..typing import
|
|
10
|
+
from ..typing import Bool, T
|
|
11
11
|
|
|
12
12
|
__all__ = ['InputType']
|
|
13
13
|
|
|
@@ -25,11 +25,11 @@ class InputType(Generic[T], ABC):
|
|
|
25
25
|
|
|
26
26
|
def is_valid_type(self, value: str) -> bool: # pylint: disable=W0613
|
|
27
27
|
"""
|
|
28
|
-
Called during parsing when :meth:`.
|
|
28
|
+
Called during parsing when :meth:`.ParamAction.would_accept` is called to determine if the value would be
|
|
29
29
|
accepted later for processing / conversion via :meth:`.__call__`. May be overridden in subclasses to
|
|
30
30
|
provide actual validation, if necessary.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Note: value validation should happen in :meth:`.__call__`, not in this method.
|
|
33
33
|
|
|
34
34
|
:param value: A parsed argument
|
|
35
35
|
:return: True if this input would accept it for processing later (where it may still be rejected), False if
|
|
@@ -147,6 +147,12 @@ class ChoiceMap(Choices[T]):
|
|
|
147
147
|
|
|
148
148
|
return self._case_insensitive_map_choice(value)
|
|
149
149
|
|
|
150
|
+
def fix_default(self, value: Any) -> T:
|
|
151
|
+
if value in self.choices.values():
|
|
152
|
+
return value
|
|
153
|
+
else:
|
|
154
|
+
return self(value)
|
|
155
|
+
|
|
150
156
|
|
|
151
157
|
class EnumChoices(_ChoicesBase[EnumT]):
|
|
152
158
|
"""
|
|
@@ -26,7 +26,7 @@ class NumericInput(InputType[NT], ABC):
|
|
|
26
26
|
|
|
27
27
|
def is_valid_type(self, value: str) -> bool:
|
|
28
28
|
"""
|
|
29
|
-
Called during parsing when :meth:`.
|
|
29
|
+
Called during parsing when :meth:`.ParamAction.would_accept` is called to determine if the value would be
|
|
30
30
|
accepted later for processing / conversion when called.
|
|
31
31
|
|
|
32
32
|
:param value: The parsed argument to validate
|
|
@@ -72,41 +72,41 @@ class ParamAction(ABC):
|
|
|
72
72
|
# region Add Parsed Value / Constant Methods
|
|
73
73
|
|
|
74
74
|
@abstractmethod
|
|
75
|
-
def add_value(self, value: str, *,
|
|
75
|
+
def add_value(self, value: str, *, combo: bool = False, joined: bool = False, env_var: str = None) -> Found:
|
|
76
76
|
"""
|
|
77
77
|
Execute this action for the given Parameter and value.
|
|
78
78
|
|
|
79
79
|
:param value: The value that was provided, if any.
|
|
80
|
-
:param opt: The option string that preceded the given value in the case of optional params, or that
|
|
81
|
-
represents a flag so a constant value can be stored, if any.
|
|
82
80
|
:param combo: Only True when a short option was provided, where the option string was combined with
|
|
83
81
|
either a real value or a sequence of 1-char combinable versions of short option strings.
|
|
84
82
|
:param joined: True if the value was provided as ``--option=value``, False otherwise.
|
|
83
|
+
:param env_var: The name of the environment variable that was used as the source of the given value, if
|
|
84
|
+
applicable.
|
|
85
85
|
:return: The number of new values discovered
|
|
86
86
|
"""
|
|
87
87
|
raise NotImplementedError
|
|
88
88
|
|
|
89
|
+
def add_env_value(self, value: str, env_var: str) -> Found:
|
|
90
|
+
return self.add_value(value, env_var=env_var)
|
|
91
|
+
|
|
89
92
|
# Note: Not used yet
|
|
90
|
-
# def add_values(self, values: Sequence[str], *,
|
|
93
|
+
# def add_values(self, values: Sequence[str], *, combo: bool = False) -> Found:
|
|
91
94
|
# added = 0
|
|
92
95
|
# for value in values:
|
|
93
|
-
# added += self.add_value(value,
|
|
96
|
+
# added += self.add_value(value, combo=combo)
|
|
94
97
|
# return added
|
|
95
98
|
|
|
96
99
|
def add_const(self, *, opt: str = None, combo: bool = False) -> Found: # noqa
|
|
97
100
|
ctx.record_action(self.param)
|
|
98
101
|
raise MissingArgument(self.param)
|
|
99
102
|
|
|
100
|
-
def add_env_value(self, value: str, env_var: str):
|
|
101
|
-
return self.add_value(value)
|
|
102
|
-
|
|
103
103
|
# endregion
|
|
104
104
|
|
|
105
105
|
# region Parsing
|
|
106
106
|
|
|
107
107
|
def would_accept(self, value: str, combo: bool = False) -> bool:
|
|
108
108
|
try:
|
|
109
|
-
normalized = self.param.
|
|
109
|
+
normalized = self.param.prepare_validation_value(value, combo)
|
|
110
110
|
except BadArgument:
|
|
111
111
|
return False
|
|
112
112
|
return self.param.is_valid_arg(normalized)
|
|
@@ -145,6 +145,8 @@ class ParamAction(ABC):
|
|
|
145
145
|
return self.default
|
|
146
146
|
|
|
147
147
|
def finalize_default(self, value):
|
|
148
|
+
if self.param.strict_default:
|
|
149
|
+
return value
|
|
148
150
|
if (type_func := self.param.type) and isinstance(type_func, InputType):
|
|
149
151
|
return type_func.fix_default(value)
|
|
150
152
|
return value
|
|
@@ -225,7 +227,7 @@ class ConstMixin:
|
|
|
225
227
|
#
|
|
226
228
|
# parsed.extend(consts)
|
|
227
229
|
|
|
228
|
-
def add_env_value(self, value: str, env_var: str):
|
|
230
|
+
def add_env_value(self, value: str, env_var: str) -> Found:
|
|
229
231
|
const, use_value = self.param.get_env_const(value, env_var)
|
|
230
232
|
# The const may only be _NotSet once StoreValueOrConst / AppendValueOrConst are put into use
|
|
231
233
|
# if const is _NotSet: # It does not support storing constants
|
|
@@ -250,15 +252,15 @@ class Store(ValueMixin, ParamAction, default=None, accepts_values=True):
|
|
|
250
252
|
|
|
251
253
|
# region Add Parsed Value / Constant Methods
|
|
252
254
|
|
|
253
|
-
def add_value(self, value: str, *,
|
|
255
|
+
def add_value(self, value: str, *, combo: bool = False, joined: Bool = False, env_var: str = None) -> Found:
|
|
254
256
|
ctx.record_action(self.param)
|
|
255
|
-
value = self.param.prepare_value(value, combo)
|
|
257
|
+
value = self.param.prepare_value(value, combo, env_var)
|
|
256
258
|
self.param.validate(value, joined)
|
|
257
259
|
self.set_value(value)
|
|
258
260
|
return 1
|
|
259
261
|
|
|
260
262
|
# Note: Not used yet
|
|
261
|
-
# def add_values(self, values: Sequence[str], *,
|
|
263
|
+
# def add_values(self, values: Sequence[str], *, combo: bool = False) -> Found:
|
|
262
264
|
# ctx.record_action(self.param)
|
|
263
265
|
# if not values:
|
|
264
266
|
# raise MissingArgument(self.param)
|
|
@@ -286,15 +288,15 @@ class Append(ValueMixin, ParamAction, accepts_values=True):
|
|
|
286
288
|
|
|
287
289
|
# region Add Parsed Value / Constant Methods
|
|
288
290
|
|
|
289
|
-
def add_value(self, value: str, *,
|
|
291
|
+
def add_value(self, value: str, *, combo: bool = False, joined: Bool = False, env_var: str = None) -> Found:
|
|
290
292
|
ctx.record_action(self.param)
|
|
291
|
-
value = self.param.prepare_value(value, combo)
|
|
293
|
+
value = self.param.prepare_value(value, combo, env_var)
|
|
292
294
|
self.param.validate(value)
|
|
293
295
|
self.append_value(value)
|
|
294
296
|
return 1
|
|
295
297
|
|
|
296
298
|
# Note: Not used yet
|
|
297
|
-
# def add_values(self, values: Sequence[str], *,
|
|
299
|
+
# def add_values(self, values: Sequence[str], *, combo: bool = False) -> Found:
|
|
298
300
|
# ctx.record_action(self.param)
|
|
299
301
|
# if not values:
|
|
300
302
|
# raise MissingArgument(self.param)
|
|
@@ -385,7 +387,7 @@ class BasicConstAction(ConstMixin, ParamAction, ABC, accepts_consts=True):
|
|
|
385
387
|
|
|
386
388
|
# region Add Parsed Value / Constant Methods
|
|
387
389
|
|
|
388
|
-
def add_value(self, value: str, *,
|
|
390
|
+
def add_value(self, value: str, *, combo: bool = False, joined: Bool = False, env_var: str = None) -> Found: # noqa
|
|
389
391
|
ctx.record_action(self.param)
|
|
390
392
|
raise BadArgument(self.param, f'does not accept values, but {value=} was provided')
|
|
391
393
|
|
|
@@ -471,9 +473,9 @@ class Count(ParamAction, accepts_values=True, accepts_consts=True):
|
|
|
471
473
|
self._add(self.param.get_const(opt))
|
|
472
474
|
return 1
|
|
473
475
|
|
|
474
|
-
def add_value(self, value: str, *,
|
|
476
|
+
def add_value(self, value: str, *, combo: bool = False, joined: Bool = False, env_var: str = None) -> Found:
|
|
475
477
|
ctx.record_action(self.param)
|
|
476
|
-
value = self.param.prepare_value(value, combo)
|
|
478
|
+
value = self.param.prepare_value(value, combo, env_var)
|
|
477
479
|
self.param.validate(value, joined)
|
|
478
480
|
self._add(value)
|
|
479
481
|
return 1
|
|
@@ -486,7 +488,7 @@ class Concatenate(Append):
|
|
|
486
488
|
|
|
487
489
|
# region Add Parsed Value / Constant Methods
|
|
488
490
|
|
|
489
|
-
def add_value(self, value: str, *,
|
|
491
|
+
def add_value(self, value: str, *, combo: bool = False, joined: Bool = False, env_var: str = None) -> Found:
|
|
490
492
|
param = self.param
|
|
491
493
|
values = value.split()
|
|
492
494
|
if not param.is_valid_arg(' '.join(values)):
|
|
@@ -525,7 +527,7 @@ class StoreAll(Store):
|
|
|
525
527
|
|
|
526
528
|
# region Add Parsed Value / Constant Methods
|
|
527
529
|
|
|
528
|
-
def add_values(self, values: list[str], *,
|
|
530
|
+
def add_values(self, values: list[str], *, combo: bool = False) -> Found:
|
|
529
531
|
param = self.param
|
|
530
532
|
ctx.record_action(param)
|
|
531
533
|
|
|
@@ -325,20 +325,28 @@ class Parameter(ParamBase, Generic[T_co], ABC):
|
|
|
325
325
|
def get_env_const(self, value: str, env_var: str) -> tuple[T_co, bool]:
|
|
326
326
|
return _NotSet, False
|
|
327
327
|
|
|
328
|
-
def prepare_value(self, value: str, short_combo: Bool = False,
|
|
329
|
-
|
|
330
|
-
if type_func is None or (pre_action and isinstance(type_func, InputType) and type_func.is_valid_type(value)):
|
|
328
|
+
def prepare_value(self, value: str, short_combo: Bool = False, env_var: str = None) -> T_co:
|
|
329
|
+
if self.type is None:
|
|
331
330
|
return value
|
|
332
331
|
try:
|
|
333
|
-
return
|
|
332
|
+
return self.type(value)
|
|
334
333
|
except InvalidChoiceError as e:
|
|
335
|
-
raise InvalidChoice(self, e.invalid, e.choices) from e
|
|
334
|
+
raise InvalidChoice(self, e.invalid, e.choices, env_var) from e
|
|
336
335
|
except InputValidationError as e:
|
|
337
|
-
|
|
336
|
+
suffix = f' from env var={env_var!r}' if env_var else ''
|
|
337
|
+
raise BadArgument(self, f'invalid input{suffix} - {e}') from e
|
|
338
338
|
except (TypeError, ValueError) as e:
|
|
339
|
-
|
|
339
|
+
suffix = f' from env var={env_var!r}' if env_var else ''
|
|
340
|
+
raise BadArgument(self, f'bad {value=} for type={self.type!r}{suffix}: {e}') from e
|
|
340
341
|
except Exception as e:
|
|
341
|
-
|
|
342
|
+
suffix = f' from env var={env_var!r}' if env_var else ''
|
|
343
|
+
raise BadArgument(self, f'unable to cast {value=} to type={self.type!r}{suffix}') from e
|
|
344
|
+
|
|
345
|
+
def prepare_validation_value(self, value: str, short_combo: Bool = False) -> T_co:
|
|
346
|
+
if self.type is None or (isinstance(self.type, InputType) and self.type.is_valid_type(value)):
|
|
347
|
+
return value
|
|
348
|
+
else:
|
|
349
|
+
return self.prepare_value(value, short_combo)
|
|
342
350
|
|
|
343
351
|
def validate(self, value: Union[T_co, None], joined: Bool = False):
|
|
344
352
|
if not isinstance(value, str) or not value or not value[0] == '-':
|
|
@@ -192,9 +192,9 @@ class Flag(BaseOption[Union[TD, TC]], actions=(StoreConst, AppendConst)):
|
|
|
192
192
|
try:
|
|
193
193
|
parsed = self.type(value)
|
|
194
194
|
except Exception as e:
|
|
195
|
-
raise ParamUsageError(self, f'unable to parse {value=} from {env_var
|
|
195
|
+
raise ParamUsageError(self, f'unable to parse {value=} from env var={env_var!r}: {e}') from e
|
|
196
196
|
if self.use_env_value and parsed != self.const and parsed != self.default:
|
|
197
|
-
raise BadArgument(self, f'invalid value={parsed!r} from {env_var
|
|
197
|
+
raise BadArgument(self, f'invalid value={parsed!r} from env var={env_var!r}')
|
|
198
198
|
return parsed, self.use_env_value
|
|
199
199
|
|
|
200
200
|
|
|
@@ -308,10 +308,10 @@ class TriFlag(BaseOption[Union[TD, TC, TA]], ABC, actions=(StoreConst, AppendCon
|
|
|
308
308
|
try:
|
|
309
309
|
parsed = self.type(value)
|
|
310
310
|
except Exception as e:
|
|
311
|
-
raise ParamUsageError(self, f'unable to parse {value=} from {env_var
|
|
311
|
+
raise ParamUsageError(self, f'unable to parse {value=} from env var={env_var!r}: {e}') from e
|
|
312
312
|
if self.use_env_value:
|
|
313
313
|
if parsed not in self.consts and parsed != self.default:
|
|
314
|
-
raise BadArgument(self, f'invalid value={parsed!r} from {env_var
|
|
314
|
+
raise BadArgument(self, f'invalid value={parsed!r} from env var={env_var!r}')
|
|
315
315
|
return parsed, True
|
|
316
316
|
else:
|
|
317
317
|
const = self.consts[0] if parsed else self.consts[1]
|
|
@@ -491,14 +491,17 @@ class Counter(BaseOption[int], actions=(Count,)):
|
|
|
491
491
|
self.default_cb = None
|
|
492
492
|
return super().register_default_cb(method)
|
|
493
493
|
|
|
494
|
-
def prepare_value(self, value: Optional[str], short_combo: bool = False,
|
|
494
|
+
def prepare_value(self, value: Optional[str], short_combo: bool = False, env_var: str = None) -> int:
|
|
495
495
|
try:
|
|
496
496
|
return self.type(value)
|
|
497
497
|
except (ValueError, TypeError) as e:
|
|
498
498
|
combinable = self.option_strs.combinable
|
|
499
499
|
if short_combo and combinable and all(c in combinable for c in value):
|
|
500
500
|
return len(value) + 1 # +1 for the -short that preceded this value
|
|
501
|
-
|
|
501
|
+
suffix = f' from env var={env_var!r}' if env_var else ''
|
|
502
|
+
raise BadArgument(self, f'bad counter {value=}{suffix}') from e
|
|
503
|
+
|
|
504
|
+
prepare_validation_value = prepare_value
|
|
502
505
|
|
|
503
506
|
def validate(self, value: Any, joined: Bool = False):
|
|
504
507
|
if value is None or isinstance(value, self.type):
|
{cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/parser.py
RENAMED
|
@@ -36,6 +36,9 @@ log = logging.getLogger(__name__)
|
|
|
36
36
|
|
|
37
37
|
_PRE_INIT = ActionPhase.PRE_INIT
|
|
38
38
|
|
|
39
|
+
# TODO: When an invalid choice for a positional is provided with -h / --help, the invalid choice error is shown instead
|
|
40
|
+
# of help, but help should be shown instead
|
|
41
|
+
|
|
39
42
|
|
|
40
43
|
class CommandParser:
|
|
41
44
|
"""Stateful parser used for a single pass of argument parsing"""
|
|
@@ -111,8 +114,6 @@ class CommandParser:
|
|
|
111
114
|
self._parse_env_vars(ctx)
|
|
112
115
|
|
|
113
116
|
def _parse_env_vars(self, ctx: Context):
|
|
114
|
-
# TODO: It would be helpful to store arg provenance for error messages, especially for a conflict between
|
|
115
|
-
# mutually exclusive params when they were provided via env
|
|
116
117
|
for param in ctx.missing_options_with_env_var():
|
|
117
118
|
for env_var in param.env_vars():
|
|
118
119
|
try:
|
|
@@ -220,7 +221,7 @@ class CommandParser:
|
|
|
220
221
|
self, opt: str, param: BaseOption, value: OptStr, combo: bool = False, joined: Bool = False
|
|
221
222
|
):
|
|
222
223
|
if value is not None:
|
|
223
|
-
param.action.add_value(value,
|
|
224
|
+
param.action.add_value(value, combo=combo, joined=joined)
|
|
224
225
|
elif param.action.accepts_consts and not param.action.accepts_values:
|
|
225
226
|
param.action.add_const(opt=opt, combo=combo)
|
|
226
227
|
elif not self.consume_values(param) and param.action.accepts_consts:
|
{cli_command_parser-2024.11.2 → cli_command_parser-2025.5.10}/lib/cli_command_parser/testing.py
RENAMED
|
@@ -94,9 +94,6 @@ class ParserTest(TestCase):
|
|
|
94
94
|
def assert_dict_equal(self, d1, d2, msg: str = None):
|
|
95
95
|
self.assertIsInstance(d1, dict, 'First argument is not a dictionary')
|
|
96
96
|
self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')
|
|
97
|
-
self._assert_dict_equal(d1, d2, msg)
|
|
98
|
-
|
|
99
|
-
def _assert_dict_equal(self, d1, d2, msg: str = None):
|
|
100
97
|
if d1 != d2:
|
|
101
98
|
self.fail(self._formatMessage(msg, f'{d1} != {d2}\n{format_dict_diff(d1, d2)}'))
|
|
102
99
|
|
|
@@ -105,7 +102,9 @@ class ParserTest(TestCase):
|
|
|
105
102
|
|
|
106
103
|
def assert_parse_results(self, cmd_cls: CommandCls, argv: Argv, expected: Expected, msg: str = None) -> Command:
|
|
107
104
|
cmd = cmd_cls.parse(argv)
|
|
108
|
-
|
|
105
|
+
parsed = cmd.ctx.get_parsed(cmd, exclude=EXCLUDE_ACTIONS)
|
|
106
|
+
if expected != parsed:
|
|
107
|
+
self.fail(msg or f'Expected results ({expected}) != {parsed=}\n{format_dict_diff(expected, parsed)}')
|
|
109
108
|
return cmd
|
|
110
109
|
|
|
111
110
|
def assert_parse_results_cases(self, cmd_cls: CommandCls, cases: Iterable[Case], msg: str = None):
|