cli-command-parser 2024.12.15__tar.gz → 2025.6.14__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.12.15/lib/cli_command_parser.egg-info → cli_command_parser-2025.6.14}/PKG-INFO +134 -130
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/__init__.py +25 -25
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/__version__.py +2 -2
- cli_command_parser-2025.6.14/lib/cli_command_parser/conversion/__init__.py +12 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/conversion/cli.py +1 -1
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/documentation.py +17 -1
- cli_command_parser-2025.6.14/lib/cli_command_parser/error_handling/__init__.py +38 -0
- cli_command_parser-2024.12.15/lib/cli_command_parser/error_handling.py → cli_command_parser-2025.6.14/lib/cli_command_parser/error_handling/base.py +15 -68
- cli_command_parser-2025.6.14/lib/cli_command_parser/error_handling/other.py +20 -0
- cli_command_parser-2025.6.14/lib/cli_command_parser/error_handling/windows.py +55 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/formatting/params.py +3 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/inputs/__init__.py +8 -8
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/inputs/choices.py +8 -2
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/inputs/exceptions.py +1 -1
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/inputs/time.py +77 -72
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/inputs/utils.py +2 -2
- cli_command_parser-2025.6.14/lib/cli_command_parser/parameters/__init__.py +6 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/parameters/actions.py +2 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/parameters/options.py +9 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/parameters/positionals.py +3 -3
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/testing.py +13 -14
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14/lib/cli_command_parser.egg-info}/PKG-INFO +134 -130
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser.egg-info/SOURCES.txt +4 -1
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/readme.rst +4 -1
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/setup.cfg +50 -50
- cli_command_parser-2024.12.15/lib/cli_command_parser/conversion/__init__.py +0 -3
- cli_command_parser-2024.12.15/lib/cli_command_parser/parameters/__init__.py +0 -6
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/LICENSE +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/MANIFEST.in +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/entry_points.txt +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/__main__.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/annotations.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/command_parameters.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/commands.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/compat.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/config.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/context.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/conversion/__main__.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/conversion/argparse_ast.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/conversion/argparse_utils.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/conversion/command_builder.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/conversion/utils.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/conversion/visitor.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/core.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/exceptions.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/formatting/__init__.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/formatting/commands.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/formatting/restructured_text.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/formatting/utils.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/inputs/base.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/inputs/files.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/inputs/numeric.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/inputs/patterns.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/metadata.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/nargs.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/parameters/base.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/parameters/choice_map.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/parameters/groups.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/parameters/option_strings.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/parameters/pass_thru.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/parse_tree.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/parser.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/typing.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/utils.py +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser.egg-info/dependency_links.txt +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser.egg-info/entry_points.txt +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser.egg-info/requires.txt +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser.egg-info/top_level.txt +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/pyproject.toml +0 -0
- {cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/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.6.14
|
|
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.12.15 → cli_command_parser-2025.6.14}/lib/cli_command_parser/__init__.py
RENAMED
|
@@ -4,52 +4,52 @@ Command Parser
|
|
|
4
4
|
:author: Doug Skrypa
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from .commands import AsyncCommand, Command, main
|
|
7
8
|
from .config import (
|
|
9
|
+
AllowLeadingDash,
|
|
10
|
+
AmbiguousComboMode,
|
|
8
11
|
CommandConfig,
|
|
9
|
-
ShowDefaults,
|
|
10
12
|
OptionNameMode,
|
|
13
|
+
ShowDefaults,
|
|
11
14
|
SubcommandAliasHelpMode,
|
|
12
|
-
AmbiguousComboMode,
|
|
13
|
-
AllowLeadingDash,
|
|
14
15
|
)
|
|
15
|
-
from .
|
|
16
|
-
from .
|
|
16
|
+
from .context import Context, ctx, get_context, get_current_context, get_parsed, get_raw_arg
|
|
17
|
+
from .error_handling import ErrorHandler, error_handler, extended_error_handler, no_exit_handler
|
|
17
18
|
from .exceptions import (
|
|
18
|
-
|
|
19
|
-
CommandDefinitionError,
|
|
20
|
-
ParameterDefinitionError,
|
|
21
|
-
UsageError,
|
|
22
|
-
ParamUsageError,
|
|
19
|
+
AmbiguousParseTree,
|
|
23
20
|
BadArgument,
|
|
21
|
+
CommandDefinitionError,
|
|
22
|
+
CommandParserException,
|
|
24
23
|
InvalidChoice,
|
|
25
24
|
MissingArgument,
|
|
26
|
-
|
|
25
|
+
NoActiveContext,
|
|
27
26
|
NoSuchOption,
|
|
28
|
-
ParserExit,
|
|
29
27
|
ParamConflict,
|
|
28
|
+
ParameterDefinitionError,
|
|
30
29
|
ParamsMissing,
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
ParamUsageError,
|
|
31
|
+
ParserExit,
|
|
32
|
+
TooManyArguments,
|
|
33
|
+
UsageError,
|
|
33
34
|
)
|
|
34
|
-
from .error_handling import ErrorHandler, error_handler, no_exit_handler, extended_error_handler
|
|
35
35
|
from .formatting.commands import get_formatter
|
|
36
36
|
from .nargs import REMAINDER
|
|
37
37
|
from .parameters import (
|
|
38
|
+
Action,
|
|
39
|
+
ActionFlag,
|
|
40
|
+
BaseOption,
|
|
41
|
+
BasePositional,
|
|
42
|
+
Counter,
|
|
43
|
+
Flag,
|
|
44
|
+
Option,
|
|
38
45
|
Parameter,
|
|
46
|
+
ParamGroup,
|
|
39
47
|
PassThru,
|
|
40
|
-
BasePositional,
|
|
41
48
|
Positional,
|
|
42
49
|
SubCommand,
|
|
43
|
-
|
|
44
|
-
BaseOption,
|
|
45
|
-
Option,
|
|
46
|
-
Flag,
|
|
47
|
-
Counter,
|
|
48
|
-
ActionFlag,
|
|
50
|
+
TriFlag,
|
|
49
51
|
action_flag,
|
|
50
|
-
before_main,
|
|
51
52
|
after_main,
|
|
52
|
-
|
|
53
|
-
TriFlag,
|
|
53
|
+
before_main,
|
|
54
54
|
)
|
|
55
55
|
from .typing import Param, ParamOrGroup
|
{cli_command_parser-2024.12.15 → cli_command_parser-2025.6.14}/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.06.14'
|
|
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'
|
|
@@ -4,7 +4,7 @@ import logging
|
|
|
4
4
|
from functools import cached_property
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
-
from cli_command_parser import Command, Counter,
|
|
7
|
+
from cli_command_parser import Command, Counter, Flag, ParamGroup, Positional, SubCommand, main
|
|
8
8
|
from cli_command_parser.inputs import Path as IPath
|
|
9
9
|
|
|
10
10
|
log = logging.getLogger(__name__)
|
|
@@ -132,15 +132,17 @@ def top_level_commands(commands: Commands) -> Commands:
|
|
|
132
132
|
def import_module(path: PathLike):
|
|
133
133
|
"""Import the module / package from the given path"""
|
|
134
134
|
path = Path(path)
|
|
135
|
-
name = path
|
|
135
|
+
name = _module_name(path)
|
|
136
136
|
if path.is_dir():
|
|
137
137
|
path /= '__init__.py'
|
|
138
|
+
|
|
138
139
|
spec = spec_from_file_location(name, path)
|
|
139
140
|
try:
|
|
140
141
|
module = module_from_spec(spec)
|
|
141
142
|
except AttributeError as e:
|
|
142
143
|
path_str = path.as_posix()
|
|
143
144
|
raise ImportError(f'Invalid path={path_str!r} - are you sure it is a Python module?', path=path_str) from e
|
|
145
|
+
|
|
144
146
|
sys.modules[spec.name] = module # This is required for the program metadata introspection
|
|
145
147
|
try:
|
|
146
148
|
spec.loader.exec_module(module)
|
|
@@ -150,6 +152,20 @@ def import_module(path: PathLike):
|
|
|
150
152
|
return module
|
|
151
153
|
|
|
152
154
|
|
|
155
|
+
def _module_name(path: Path) -> str:
|
|
156
|
+
if path.name == '__init__.py':
|
|
157
|
+
path = path.parent
|
|
158
|
+
|
|
159
|
+
parts = [path.stem]
|
|
160
|
+
while (path := path.parent).name: # / has no name
|
|
161
|
+
if path.joinpath('__init__.py').exists(): # it is a package
|
|
162
|
+
parts.append(path.name)
|
|
163
|
+
else:
|
|
164
|
+
break
|
|
165
|
+
|
|
166
|
+
return '.'.join(parts[::-1])
|
|
167
|
+
|
|
168
|
+
|
|
153
169
|
def _is_command(obj, include_abc: Bool = False) -> bool:
|
|
154
170
|
if not (isinstance(obj, CommandMeta) and obj is not Command):
|
|
155
171
|
return False
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Error handling for expected / unexpected exceptions.
|
|
3
|
+
|
|
4
|
+
The default handler will...
|
|
5
|
+
|
|
6
|
+
- Call ``print()`` after catching a :class:`python:KeyboardInterrupt`, before exiting
|
|
7
|
+
- Exit gracefully after catching a :class:`python:BrokenPipeError` (often caused by piping output to a tool like
|
|
8
|
+
``tail``)
|
|
9
|
+
|
|
10
|
+
.. note::
|
|
11
|
+
Parameters defined in a base Command will be processed in the context of that Command. I.e., if a valid
|
|
12
|
+
subcommand argument was provided, but an Option defined in the parent Command has an invalid value, then the
|
|
13
|
+
exception that is raised about that invalid value will be raised before transferring control to the
|
|
14
|
+
subcommand's error handler.
|
|
15
|
+
|
|
16
|
+
:author: Doug Skrypa
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from platform import system as _system
|
|
20
|
+
|
|
21
|
+
from .base import (
|
|
22
|
+
ErrorHandler,
|
|
23
|
+
Handler,
|
|
24
|
+
HandlerFunc,
|
|
25
|
+
NullErrorHandler,
|
|
26
|
+
error_handler,
|
|
27
|
+
extended_error_handler,
|
|
28
|
+
no_exit_handler,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
if _system().lower() == 'windows':
|
|
32
|
+
from .windows import handle_kb_interrupt
|
|
33
|
+
else:
|
|
34
|
+
from .other import handle_kb_interrupt
|
|
35
|
+
|
|
36
|
+
__all__ = ['ErrorHandler', 'Handler', 'error_handler', 'extended_error_handler', 'no_exit_handler', 'NullErrorHandler']
|
|
37
|
+
|
|
38
|
+
error_handler.register(handle_kb_interrupt, KeyboardInterrupt)
|
|
@@ -1,34 +1,19 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
The default handler will...
|
|
5
|
-
|
|
6
|
-
- Call ``print()`` after catching a :class:`python:KeyboardInterrupt`, before exiting
|
|
7
|
-
- Exit gracefully after catching a :class:`python:BrokenPipeError` (often caused by piping output to a tool like
|
|
8
|
-
``tail``)
|
|
9
|
-
|
|
10
|
-
.. note::
|
|
11
|
-
Parameters defined in a base Command will be processed in the context of that Command. I.e., if a valid
|
|
12
|
-
subcommand argument was provided, but an Option defined in the parent Command has an invalid value, then the
|
|
13
|
-
exception that is raised about that invalid value will be raised before transferring control to the
|
|
14
|
-
subcommand's error handler.
|
|
15
|
-
|
|
16
|
-
:author: Doug Skrypa
|
|
2
|
+
Platform-agnostic error handling framework / handlers.
|
|
17
3
|
"""
|
|
18
4
|
|
|
19
5
|
from __future__ import annotations
|
|
20
6
|
|
|
21
|
-
import platform
|
|
22
7
|
import sys
|
|
23
8
|
from collections import ChainMap
|
|
24
|
-
from typing import Callable, Iterator,
|
|
9
|
+
from typing import Callable, Iterator, Type, TypeVar, Union
|
|
25
10
|
|
|
26
|
-
from
|
|
11
|
+
from ..exceptions import CommandParserException
|
|
27
12
|
|
|
28
13
|
__all__ = ['ErrorHandler', 'error_handler', 'extended_error_handler', 'no_exit_handler', 'NullErrorHandler']
|
|
29
14
|
|
|
30
|
-
|
|
31
|
-
HandlerFunc = Callable[[
|
|
15
|
+
E = TypeVar('E', bound=BaseException)
|
|
16
|
+
HandlerFunc = Callable[[E], Union[bool, int, None]]
|
|
32
17
|
|
|
33
18
|
|
|
34
19
|
class ErrorHandler:
|
|
@@ -42,7 +27,7 @@ class ErrorHandler:
|
|
|
42
27
|
def __repr__(self) -> str:
|
|
43
28
|
return f'<{self.__class__.__name__}[handlers={len(self.exc_handler_map)}]>'
|
|
44
29
|
|
|
45
|
-
def register(self, handler: HandlerFunc, *exceptions: Type[
|
|
30
|
+
def register(self, handler: HandlerFunc, *exceptions: Type[E]):
|
|
46
31
|
for exc in exceptions:
|
|
47
32
|
self.exc_handler_map[exc] = Handler(exc, handler)
|
|
48
33
|
|
|
@@ -61,7 +46,7 @@ class ErrorHandler:
|
|
|
61
46
|
return _handler
|
|
62
47
|
|
|
63
48
|
@classmethod
|
|
64
|
-
def cls_handler(cls, *exceptions: Type[
|
|
49
|
+
def cls_handler(cls, *exceptions: Type[E]):
|
|
65
50
|
def _cls_handler(handler: Union[HandlerFunc, staticmethod]):
|
|
66
51
|
for exc in exceptions:
|
|
67
52
|
cls._exc_handler_map[exc] = Handler(exc, handler)
|
|
@@ -91,6 +76,7 @@ class ErrorHandler:
|
|
|
91
76
|
for handler in self.iter_handlers(exc_type, exc_val):
|
|
92
77
|
result = handler(exc_val)
|
|
93
78
|
if result is True:
|
|
79
|
+
# This explicitly checks for True since 1 == True, but 1 is treated as an intended exit code
|
|
94
80
|
return True
|
|
95
81
|
if result or (isinstance(result, int) and result is not False):
|
|
96
82
|
sys.exit(result)
|
|
@@ -130,62 +116,23 @@ class Handler:
|
|
|
130
116
|
return issubclass(self.exc_cls, other.exc_cls)
|
|
131
117
|
|
|
132
118
|
|
|
133
|
-
|
|
119
|
+
# By default, all error handlers should call :meth:`CommandParserException.exit` for CommandParserExceptions
|
|
120
|
+
ErrorHandler.cls_handler(CommandParserException)(CommandParserException.exit) # noqa
|
|
134
121
|
|
|
135
122
|
#: Default base :class:`ErrorHandler`
|
|
136
123
|
error_handler: ErrorHandler = ErrorHandler()
|
|
137
|
-
error_handler.register(lambda e: True, BrokenPipeError)
|
|
138
124
|
|
|
139
125
|
|
|
140
|
-
@error_handler(
|
|
141
|
-
def
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
causes the next terminal prompt to be printed on the same line as the last (possibly incomplete) line of output.
|
|
145
|
-
"""
|
|
146
|
-
try:
|
|
147
|
-
print(flush=True) # Flush forces any potential closed/broken pipe-related error to be caught/handled here
|
|
148
|
-
except BrokenPipeError:
|
|
149
|
-
pass
|
|
150
|
-
except OSError as e:
|
|
151
|
-
# Handle the closed/broken pipe incorrect errno bug if triggered during the above print
|
|
152
|
-
if not WINDOWS or not handle_win_os_pipe_error(e):
|
|
153
|
-
raise
|
|
154
|
-
return 130
|
|
126
|
+
@error_handler(BrokenPipeError)
|
|
127
|
+
def _handle_broken_pipe(exc: BrokenPipeError):
|
|
128
|
+
# This can't be registered as a lambda function because it would break the ability to pickle the handler
|
|
129
|
+
return True
|
|
155
130
|
|
|
156
131
|
|
|
157
132
|
#: An :class:`ErrorHandler` that does not call :func:`python:sys.exit` for
|
|
158
133
|
#: :class:`CommandParserExceptions<.CommandParserException>`
|
|
159
134
|
no_exit_handler: ErrorHandler = error_handler.copy()
|
|
160
|
-
no_exit_handler(CommandParserException
|
|
135
|
+
no_exit_handler.register(CommandParserException.show, CommandParserException) # noqa
|
|
161
136
|
|
|
162
137
|
#: The default :class:`ErrorHandler` (extends :obj:`error_handler`)
|
|
163
138
|
extended_error_handler: ErrorHandler = error_handler.copy()
|
|
164
|
-
|
|
165
|
-
if WINDOWS:
|
|
166
|
-
import ctypes
|
|
167
|
-
|
|
168
|
-
RtlGetLastNtStatus = ctypes.WinDLL('ntdll').RtlGetLastNtStatus
|
|
169
|
-
RtlGetLastNtStatus.restype = ctypes.c_ulong
|
|
170
|
-
NT_STATUSES = {0xC000_00B1: 'STATUS_PIPE_CLOSING', 0xC000_014B: 'STATUS_PIPE_BROKEN'}
|
|
171
|
-
|
|
172
|
-
@extended_error_handler(OSError)
|
|
173
|
-
def handle_win_os_pipe_error(exc: OSError):
|
|
174
|
-
"""
|
|
175
|
-
This is a workaround for `[Windows] I/O on a broken pipe may raise an EINVAL OSError instead of BrokenPipeError
|
|
176
|
-
<https://github.com/python/cpython/issues/79935>`_, which is a bug in the way that the
|
|
177
|
-
windows error code for a broken pipe is translated into an errno value. It should be translated to
|
|
178
|
-
:data:`~errno.EPIPE`, but it uses :data:`~errno.EINVAL` (22) instead.
|
|
179
|
-
|
|
180
|
-
Prevents the following when piping output to utilities such as ``| head``::\n
|
|
181
|
-
Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>
|
|
182
|
-
OSError: [Errno 22] Invalid argument
|
|
183
|
-
"""
|
|
184
|
-
if exc.errno == 22 and RtlGetLastNtStatus() in NT_STATUSES:
|
|
185
|
-
try:
|
|
186
|
-
sys.stdout.close()
|
|
187
|
-
except OSError:
|
|
188
|
-
pass
|
|
189
|
-
return True
|
|
190
|
-
|
|
191
|
-
return False
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Error handling for expected / unexpected exceptions on non-Windows systems.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
__all__ = ['handle_kb_interrupt']
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def handle_kb_interrupt(exc: KeyboardInterrupt) -> int:
|
|
11
|
+
"""
|
|
12
|
+
Handles :class:`python:KeyboardInterrupt` by calling :func:`python:print` to avoid ending the program in a way that
|
|
13
|
+
causes the next terminal prompt to be printed on the same line as the last (possibly incomplete) line of output.
|
|
14
|
+
"""
|
|
15
|
+
try:
|
|
16
|
+
print(flush=True) # Flush forces any potential closed/broken pipe-related error to be caught/handled here
|
|
17
|
+
except BrokenPipeError:
|
|
18
|
+
pass
|
|
19
|
+
# 130 (= 128 + SIGINT (2)) is used/expected by Bash; see: https://tldp.org/LDP/abs/html/exitcodes.html
|
|
20
|
+
return 130
|