cli-command-parser 2024.9.7__tar.gz → 2024.12.15__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.9.7/lib/cli_command_parser.egg-info → cli_command_parser-2024.12.15}/PKG-INFO +6 -18
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/__version__.py +1 -1
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/annotations.py +1 -10
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/compat.py +0 -10
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/conversion/utils.py +2 -19
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/documentation.py +1 -1
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/exceptions.py +8 -8
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/formatting/commands.py +11 -6
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/formatting/params.py +17 -10
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/formatting/restructured_text.py +94 -55
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/inputs/base.py +3 -3
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/inputs/numeric.py +1 -1
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/metadata.py +69 -4
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/parameters/actions.py +21 -21
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/parameters/base.py +16 -8
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/parameters/choice_map.py +2 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/parameters/groups.py +7 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/parameters/options.py +9 -6
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/parser.py +4 -3
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15/lib/cli_command_parser.egg-info}/PKG-INFO +6 -18
- cli_command_parser-2024.12.15/lib/cli_command_parser.egg-info/requires.txt +3 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/readme.rst +3 -13
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/requirements-dev.txt +1 -1
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/setup.cfg +2 -4
- cli_command_parser-2024.9.7/lib/cli_command_parser.egg-info/requires.txt +0 -8
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/LICENSE +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/MANIFEST.in +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/entry_points.txt +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/__init__.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/__main__.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/command_parameters.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/commands.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/config.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/context.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/conversion/__init__.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/conversion/__main__.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/conversion/argparse_ast.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/conversion/argparse_utils.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/conversion/cli.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/conversion/command_builder.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/conversion/visitor.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/core.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/error_handling.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/formatting/__init__.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/formatting/utils.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/inputs/__init__.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/inputs/choices.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/inputs/exceptions.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/inputs/files.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/inputs/patterns.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/inputs/time.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/inputs/utils.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/nargs.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/parameters/__init__.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/parameters/option_strings.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/parameters/pass_thru.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/parameters/positionals.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/parse_tree.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/testing.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/typing.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/utils.py +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser.egg-info/SOURCES.txt +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser.egg-info/dependency_links.txt +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser.egg-info/entry_points.txt +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser.egg-info/top_level.txt +0 -0
- {cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cli_command_parser
|
|
3
|
-
Version: 2024.
|
|
3
|
+
Version: 2024.12.15
|
|
4
4
|
Summary: CLI Command Parser
|
|
5
5
|
Home-page: https://github.com/dskrypa/cli_command_parser
|
|
6
6
|
Author: Doug Skrypa
|
|
@@ -16,27 +16,25 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
17
|
Classifier: Programming Language :: Python
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.9
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
24
|
Classifier: Topic :: Software Development :: User Interfaces
|
|
25
25
|
Classifier: Topic :: Text Processing
|
|
26
|
-
Requires-Python: >=3.
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
27
|
Description-Content-Type: text/x-rst
|
|
28
28
|
License-File: LICENSE
|
|
29
29
|
Provides-Extra: wcwidth
|
|
30
30
|
Requires-Dist: wcwidth; extra == "wcwidth"
|
|
31
|
-
Provides-Extra: conversion
|
|
32
|
-
Requires-Dist: astunparse; python_version < "3.9" and extra == "conversion"
|
|
33
31
|
|
|
34
32
|
CLI Command Parser
|
|
35
33
|
##################
|
|
36
34
|
|
|
37
35
|
|downloads| |py_version| |coverage_badge| |build_status| |Ruff|
|
|
38
36
|
|
|
39
|
-
.. |py_version| image:: https://img.shields.io/badge/python-3.
|
|
37
|
+
.. |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
|
|
40
38
|
:target: https://pypi.org/project/cli-command-parser/
|
|
41
39
|
|
|
42
40
|
.. |coverage_badge| image:: https://codecov.io/gh/dskrypa/cli_command_parser/branch/main/graph/badge.svg
|
|
@@ -118,18 +116,8 @@ with optional dependencies::
|
|
|
118
116
|
Python Version Compatibility
|
|
119
117
|
============================
|
|
120
118
|
|
|
121
|
-
Python versions 3.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
When using Python 3.8, some additional packages that backport functionality that was added in later Python versions
|
|
125
|
-
are required for compatibility.
|
|
126
|
-
|
|
127
|
-
To use the argparse to cli-command-parser conversion script with Python 3.8, there is a dependency on
|
|
128
|
-
`astunparse <https://astunparse.readthedocs.io>`__. If you are using Python 3.9 or above, then ``astunparse`` is not
|
|
129
|
-
necessary because the relevant code was added to the stdlib ``ast`` module. If you're unsure, you can install
|
|
130
|
-
cli-command-parser with the following command to automatically handle whether that extra dependency is needed or not::
|
|
131
|
-
|
|
132
|
-
$ pip install -U cli-command-parser[conversion]
|
|
119
|
+
Python versions 3.9 and above are currently supported. The last release of CLI Command Parser that supported 3.8 was
|
|
120
|
+
2024-09-07. Support for Python 3.8 `officially ended on 2024-10-07 <https://devguide.python.org/versions/>`__.
|
|
133
121
|
|
|
134
122
|
|
|
135
123
|
Links
|
{cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/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__ = '2024.
|
|
4
|
+
__version__ = '2024.12.15'
|
|
5
5
|
__author__ = 'Doug Skrypa'
|
|
6
6
|
__author_email__ = 'dskrypa@gmail.com'
|
|
7
7
|
__license__ = 'Apache 2.0'
|
{cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/annotations.py
RENAMED
|
@@ -7,7 +7,7 @@ Utilities for extracting types from annotations.
|
|
|
7
7
|
from collections.abc import Collection, Iterable
|
|
8
8
|
from functools import lru_cache
|
|
9
9
|
from inspect import isclass
|
|
10
|
-
from typing import
|
|
10
|
+
from typing import Optional, Union, get_args, get_origin, get_type_hints as _get_type_hints
|
|
11
11
|
|
|
12
12
|
try:
|
|
13
13
|
from types import NoneType
|
|
@@ -42,15 +42,6 @@ def get_annotation_value_type(annotation, from_union: bool = True, from_collecti
|
|
|
42
42
|
return None
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def get_args(annotation) -> tuple:
|
|
46
|
-
"""
|
|
47
|
-
Wrapper around :func:`python:typing.get_args` for 3.7~8 compatibility, to make it behave more like it does in 3.9+
|
|
48
|
-
"""
|
|
49
|
-
if getattr(annotation, '_special', False): # 3.7-3.8 generic collection alias with no content types
|
|
50
|
-
return ()
|
|
51
|
-
return _get_args(annotation)
|
|
52
|
-
|
|
53
|
-
|
|
54
45
|
def _type_from_union(annotation) -> Optional[type]:
|
|
55
46
|
args = get_args(annotation)
|
|
56
47
|
# Note: Unions of a single argument return the argument; i.e., Union[T] returns T, so the len can never be 1
|
{cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/lib/cli_command_parser/compat.py
RENAMED
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Compatibility / Patch module - used to back-port features to Python 3.7 and to avoid breaking changes in Enum/Flag in
|
|
3
|
-
3.11.
|
|
4
|
-
|
|
5
|
-
Contains stdlib CPython functions / classes from Python 3.8 and 3.10.
|
|
6
|
-
|
|
7
2
|
The :class:`WCTextWrapper` in this module extends the stdlib :class:`python:textwrap.TextWrapper` to support wide
|
|
8
3
|
characters.
|
|
9
4
|
"""
|
|
@@ -16,8 +11,6 @@ from .utils import wcswidth
|
|
|
16
11
|
|
|
17
12
|
__all__ = ['WCTextWrapper']
|
|
18
13
|
|
|
19
|
-
# region textwrap
|
|
20
|
-
|
|
21
14
|
|
|
22
15
|
class WCTextWrapper(TextWrapper):
|
|
23
16
|
"""
|
|
@@ -119,6 +112,3 @@ class WCTextWrapper(TextWrapper):
|
|
|
119
112
|
break
|
|
120
113
|
|
|
121
114
|
return lines
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
# endregion
|
|
@@ -1,24 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from ast import AST,
|
|
4
|
-
|
|
5
|
-
try:
|
|
6
|
-
from ast import unparse
|
|
7
|
-
except ImportError: # added in 3.9
|
|
8
|
-
try:
|
|
9
|
-
from astunparse import unparse as _unparse
|
|
10
|
-
except ImportError as e:
|
|
11
|
-
raise RuntimeError(
|
|
12
|
-
'Missing required dependency: astunparse (only required in Python 3.8 and below'
|
|
13
|
-
' - upgrade to 3.9 or above to avoid this dependency)'
|
|
14
|
-
)
|
|
15
|
-
else:
|
|
16
|
-
|
|
17
|
-
def unparse(node):
|
|
18
|
-
return ''.join(_unparse(node).splitlines())
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
from typing import Union, Iterator, List as _List
|
|
3
|
+
from ast import AST, Attribute, Call, Dict, List, Name, Set, Tuple, expr, unparse
|
|
4
|
+
from typing import Iterator, List as _List, Union
|
|
22
5
|
|
|
23
6
|
__all__ = ['get_name_repr', 'iter_module_parents', 'collection_contents']
|
|
24
7
|
|
|
@@ -372,6 +372,6 @@ class RstWriter:
|
|
|
372
372
|
path = target_dir.joinpath(name + self.ext)
|
|
373
373
|
log.debug(f'{prefix} {path.as_posix()}')
|
|
374
374
|
if not self.dry_run:
|
|
375
|
-
# Path.write_text on 3.
|
|
375
|
+
# Path.write_text on 3.9 does not support `newline`
|
|
376
376
|
with path.open('w', encoding=self.encoding, newline=self.newline) as f:
|
|
377
377
|
f.write(content)
|
{cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/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):
|
|
@@ -12,9 +12,10 @@ from typing import TYPE_CHECKING, Callable, Iterable, Iterator, Optional, Type,
|
|
|
12
12
|
|
|
13
13
|
from ..context import NoActiveContext, ctx
|
|
14
14
|
from ..core import get_metadata, get_params
|
|
15
|
+
from ..parameters.choice_map import ChoiceMap
|
|
15
16
|
from ..parameters.groups import ParamGroup
|
|
16
17
|
from ..utils import _NotSet, camel_to_snake_case
|
|
17
|
-
from .restructured_text import
|
|
18
|
+
from .restructured_text import spaced_rst_header
|
|
18
19
|
from .utils import PartWrapper
|
|
19
20
|
|
|
20
21
|
if TYPE_CHECKING:
|
|
@@ -47,6 +48,7 @@ class CommandHelpFormatter:
|
|
|
47
48
|
for group in groups:
|
|
48
49
|
if group.group: # prevent duplicates
|
|
49
50
|
continue
|
|
51
|
+
|
|
50
52
|
if group.contains_positional:
|
|
51
53
|
self.pos_group.add(group)
|
|
52
54
|
else:
|
|
@@ -162,14 +164,17 @@ class CommandHelpFormatter:
|
|
|
162
164
|
yield description
|
|
163
165
|
yield ''
|
|
164
166
|
|
|
165
|
-
|
|
166
|
-
# subcommand sections
|
|
167
|
-
for group in self.groups:
|
|
167
|
+
if self.pos_group.show_in_help:
|
|
168
168
|
# TODO: Nested subcommands' local choices should not repeat the `subcommands` positional arguments section
|
|
169
169
|
# that includes the nested subcommand choice being documented
|
|
170
|
+
if len(members := self.pos_group.members) == 1 and isinstance(members[0], ChoiceMap):
|
|
171
|
+
yield from members[0].formatter.rst_table().iter_build() # noqa
|
|
172
|
+
else:
|
|
173
|
+
yield from self.pos_group.formatter.rst_table().iter_build()
|
|
174
|
+
|
|
175
|
+
for group in self.groups[1:]:
|
|
170
176
|
if group.show_in_help:
|
|
171
|
-
|
|
172
|
-
yield from table.iter_build()
|
|
177
|
+
yield from group.formatter.rst_table().iter_build()
|
|
173
178
|
|
|
174
179
|
if include_epilog and (epilog := self._meta.format_epilog(config.extended_epilog, allow_sys_argv)):
|
|
175
180
|
yield epilog
|
|
@@ -16,7 +16,7 @@ from ..core import get_config
|
|
|
16
16
|
from ..parameters import ParamGroup, PassThru, TriFlag
|
|
17
17
|
from ..parameters.base import BaseOption, BasePositional
|
|
18
18
|
from ..parameters.choice_map import Choice, ChoiceMap
|
|
19
|
-
from .restructured_text import RstTable
|
|
19
|
+
from .restructured_text import Cell, Row, RstTable
|
|
20
20
|
from .utils import _should_add_default, format_help_entry
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
@@ -223,6 +223,8 @@ class TriFlagHelpFormatter(OptionHelpFormatter, param_cls=TriFlag):
|
|
|
223
223
|
|
|
224
224
|
|
|
225
225
|
class ChoiceMapHelpFormatter(ParamHelpFormatter, param_cls=ChoiceMap):
|
|
226
|
+
"""Formatter for :class:`SubCommand` and :class:`Action` parameters (and any other params that extend ChoiceMap)"""
|
|
227
|
+
|
|
226
228
|
param: ChoiceMap
|
|
227
229
|
|
|
228
230
|
@cached_property
|
|
@@ -269,6 +271,7 @@ class ChoiceMapHelpFormatter(ParamHelpFormatter, param_cls=ChoiceMap):
|
|
|
269
271
|
|
|
270
272
|
def _format_rst_rows(self) -> Iterator[tuple[str, OptStr]]:
|
|
271
273
|
mode = ctx.config.cmd_alias_mode or SubcommandAliasHelpMode.ALIAS
|
|
274
|
+
# TODO: The subcommand names should link to their respective subcommand sections
|
|
272
275
|
for choice_group in self.choice_groups:
|
|
273
276
|
for choice, usage, description in choice_group.prepare(mode):
|
|
274
277
|
yield f'``{usage}``', description
|
|
@@ -492,15 +495,19 @@ class GroupHelpFormatter(ParamHelpFormatter, param_cls=ParamGroup): # noqa # p
|
|
|
492
495
|
table = RstTable(self.format_description())
|
|
493
496
|
# TODO: non-nested when config.show_group_tree is False; maybe separate options for rst vs help
|
|
494
497
|
for member in self.param.members:
|
|
495
|
-
if member.show_in_help:
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
498
|
+
if not member.show_in_help:
|
|
499
|
+
continue
|
|
500
|
+
|
|
501
|
+
formatter = member.formatter
|
|
502
|
+
try:
|
|
503
|
+
sub_table: RstTable = formatter.rst_table() # noqa
|
|
504
|
+
except AttributeError:
|
|
505
|
+
table.add_rows(formatter.rst_rows())
|
|
506
|
+
else:
|
|
507
|
+
table._add_row(Row([Cell(str(sub_table), ext_right=True), Cell()]))
|
|
508
|
+
# If a config option to switch to the old way is added later, the old approach:
|
|
509
|
+
# sub_table.show_title = False
|
|
510
|
+
# table.add_row(sub_table.title, str(sub_table))
|
|
504
511
|
|
|
505
512
|
return table
|
|
506
513
|
|
|
@@ -6,11 +6,8 @@ Utilities for formatting data using RST markup
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from itertools import starmap
|
|
10
9
|
from typing import TYPE_CHECKING, Any, Iterable, Iterator, Mapping, Sequence, TypeVar, Union
|
|
11
10
|
|
|
12
|
-
from .utils import line_iter
|
|
13
|
-
|
|
14
11
|
if TYPE_CHECKING:
|
|
15
12
|
from ..typing import Bool, OptStr, Strings
|
|
16
13
|
|
|
@@ -119,7 +116,7 @@ class RstTable:
|
|
|
119
116
|
body of this table.
|
|
120
117
|
"""
|
|
121
118
|
|
|
122
|
-
__slots__ = ('title', 'subtitle', 'show_title', 'use_table_directive', '
|
|
119
|
+
__slots__ = ('title', 'subtitle', 'show_title', 'use_table_directive', '_rows', '_widths', '_updated')
|
|
123
120
|
|
|
124
121
|
def __init__(
|
|
125
122
|
self,
|
|
@@ -134,8 +131,9 @@ class RstTable:
|
|
|
134
131
|
self.subtitle = subtitle
|
|
135
132
|
self.show_title = show_title
|
|
136
133
|
self.use_table_directive = use_table_directive
|
|
137
|
-
self.
|
|
138
|
-
self.
|
|
134
|
+
self._rows = []
|
|
135
|
+
self._widths = ()
|
|
136
|
+
self._updated = False
|
|
139
137
|
if headers:
|
|
140
138
|
self.add_row(*headers, header=True)
|
|
141
139
|
|
|
@@ -163,6 +161,13 @@ class RstTable:
|
|
|
163
161
|
table.add_kv_rows(data)
|
|
164
162
|
return table
|
|
165
163
|
|
|
164
|
+
@property
|
|
165
|
+
def widths(self) -> tuple[int, ...]:
|
|
166
|
+
if self._updated:
|
|
167
|
+
self._widths = tuple(max(col) for col in zip(*(row.widths() for row in self._rows)))
|
|
168
|
+
self._updated = False
|
|
169
|
+
return self._widths
|
|
170
|
+
|
|
166
171
|
def add_dict_rows(self, rows: RowMaps, columns: Sequence[T] = None, add_header: Bool = False):
|
|
167
172
|
"""Add a row for each dict in the given sequence of rows, where the keys represent the columns."""
|
|
168
173
|
if not columns:
|
|
@@ -180,8 +185,11 @@ class RstTable:
|
|
|
180
185
|
self.add_rows(data.items())
|
|
181
186
|
|
|
182
187
|
def add_rows(self, rows: Iterable[Iterable[OptStr]]):
|
|
183
|
-
for
|
|
184
|
-
|
|
188
|
+
self._add_rows(Row([Cell(c or '') for c in columns]) for columns in rows)
|
|
189
|
+
|
|
190
|
+
def _add_rows(self, rows: Iterable[Row]):
|
|
191
|
+
self._rows.extend(rows)
|
|
192
|
+
self._updated = True
|
|
185
193
|
|
|
186
194
|
def add_row(self, *columns: OptStr, index: int = None, header: bool = False):
|
|
187
195
|
"""
|
|
@@ -192,34 +200,18 @@ class RstTable:
|
|
|
192
200
|
the list of rows.
|
|
193
201
|
:param header: If True, this row will be treated as a header row. Does not affect insertion order.
|
|
194
202
|
"""
|
|
195
|
-
|
|
196
|
-
if self.widths:
|
|
197
|
-
self.widths = tuple(starmap(max, zip(self.widths, widths)))
|
|
198
|
-
else:
|
|
199
|
-
self.widths = tuple(widths)
|
|
203
|
+
self._add_row(Row([Cell(c or '') for c in columns], header), index)
|
|
200
204
|
|
|
201
|
-
|
|
205
|
+
def _add_row(self, row: Row, index: int = None):
|
|
202
206
|
if index is None:
|
|
203
|
-
self.
|
|
207
|
+
self._rows.append(row)
|
|
204
208
|
else:
|
|
205
|
-
self.
|
|
206
|
-
|
|
207
|
-
def bar(self, char: str = '-') -> str:
|
|
208
|
-
"""
|
|
209
|
-
:param char: The character to use for the bar. Defaults to ``-`` (for normal rows). Use ``=`` below a header
|
|
210
|
-
row. See :du_rst:`Grid Tables<grid-tables>` for more info.
|
|
211
|
-
:return: The formatted bar string
|
|
212
|
-
"""
|
|
213
|
-
pre = ' ' if self.use_table_directive else ''
|
|
214
|
-
return '+'.join([pre, *(char * (w + 2) for w in self.widths), ''])
|
|
215
|
-
|
|
216
|
-
def _get_row_format(self) -> str:
|
|
217
|
-
pre = ' ' if self.use_table_directive else ''
|
|
218
|
-
return '|'.join([pre, *(f' {{:<{w}s}} ' for w in self.widths), ''])
|
|
209
|
+
self._rows.insert(index, row)
|
|
210
|
+
self._updated = True
|
|
219
211
|
|
|
220
212
|
def __repr__(self) -> str:
|
|
221
213
|
return (
|
|
222
|
-
f'<RstTable[use_table_directive={self.use_table_directive}, rows={len(self.
|
|
214
|
+
f'<RstTable[use_table_directive={self.use_table_directive}, rows={len(self._rows)},'
|
|
223
215
|
f' title={self.title!r}, widths={self.widths}]>'
|
|
224
216
|
)
|
|
225
217
|
|
|
@@ -230,38 +222,85 @@ class RstTable:
|
|
|
230
222
|
yield ''
|
|
231
223
|
|
|
232
224
|
if self.use_table_directive:
|
|
233
|
-
options
|
|
234
|
-
yield from _rst_directive('table', options=options, check=True)
|
|
225
|
+
yield from _rst_directive('table', options={'subtitle': self.subtitle, 'widths': 'auto'}, check=True)
|
|
235
226
|
yield ''
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
for header, any_new_line, row in self.rows:
|
|
241
|
-
if any_new_line:
|
|
242
|
-
for line in line_iter(*row):
|
|
243
|
-
yield format_row(*line)
|
|
244
|
-
else:
|
|
245
|
-
yield format_row(*row)
|
|
246
|
-
|
|
247
|
-
yield header_bar if header else bar
|
|
227
|
+
for line in self._iter_render():
|
|
228
|
+
yield ' ' + line
|
|
229
|
+
else:
|
|
230
|
+
yield from self._iter_render()
|
|
248
231
|
|
|
249
232
|
yield ''
|
|
250
233
|
|
|
234
|
+
def _iter_render(self) -> Iterator[str]:
|
|
235
|
+
col_widths = self.widths
|
|
236
|
+
yield self._rows[0].render_upper_bar(col_widths)
|
|
237
|
+
for row in self._rows:
|
|
238
|
+
yield from row.render_lines(col_widths)
|
|
239
|
+
|
|
251
240
|
def __str__(self) -> str:
|
|
252
241
|
return '\n'.join(self.iter_build())
|
|
253
242
|
|
|
254
243
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
244
|
+
class Cell:
|
|
245
|
+
__slots__ = ('text', 'lines', 'width', 'height', 'brd_bottom', 'brd_right')
|
|
246
|
+
|
|
247
|
+
def __init__(self, text: str = '', *, ext_right: bool = False, ext_below: bool = False):
|
|
248
|
+
self.text = text
|
|
249
|
+
if text:
|
|
250
|
+
self.lines = text.splitlines()
|
|
251
|
+
self.width = max(map(len, self.lines))
|
|
252
|
+
self.height = len(self.lines)
|
|
264
253
|
else:
|
|
265
|
-
|
|
254
|
+
self.lines = ['']
|
|
255
|
+
self.width = 0
|
|
256
|
+
self.height = 1
|
|
257
|
+
|
|
258
|
+
self.brd_bottom = not ext_below
|
|
259
|
+
self.brd_right = not ext_right
|
|
260
|
+
|
|
261
|
+
def render_upper_bar(self, width: int) -> str:
|
|
262
|
+
return ('-' * (width + 2)) + ('+' if self.brd_bottom else '|')
|
|
263
|
+
|
|
264
|
+
def render_lower_bar(self, width: int, char: str = '-') -> str:
|
|
265
|
+
if not self.brd_bottom:
|
|
266
|
+
char = ' '
|
|
267
|
+
return (char * (width + 2)) + ('+' if self.brd_bottom or self.brd_right else ' ')
|
|
268
|
+
|
|
269
|
+
def render_lines(self, width: int, max_lines: int) -> Iterator[str]:
|
|
270
|
+
format_line = f' {{:<{width}s}} {"|" if self.brd_right else " "}'.format
|
|
271
|
+
for line in self.lines:
|
|
272
|
+
yield format_line(line)
|
|
273
|
+
|
|
274
|
+
for _ in range(self.height, max_lines):
|
|
275
|
+
yield format_line('')
|
|
276
|
+
|
|
277
|
+
def __repr__(self) -> str:
|
|
278
|
+
return f'<{self.__class__.__name__}[{self.text!r}, width={self.width}, height={self.height}]>'
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class Row:
|
|
282
|
+
__slots__ = ('cells', 'header')
|
|
283
|
+
|
|
284
|
+
def __init__(self, cells: list[Cell], header: bool = False):
|
|
285
|
+
self.cells = cells
|
|
286
|
+
self.header = header
|
|
287
|
+
|
|
288
|
+
def widths(self) -> Iterator[int]:
|
|
289
|
+
for cell in self.cells:
|
|
290
|
+
yield cell.width
|
|
291
|
+
|
|
292
|
+
def render_upper_bar(self, widths: Sequence[int]) -> str:
|
|
293
|
+
return '+' + ''.join(cell.render_upper_bar(w) for cell, w in zip(self.cells, widths))
|
|
294
|
+
|
|
295
|
+
def render_lower_bar(self, widths: Sequence[int]) -> str:
|
|
296
|
+
char = '=' if self.header else '-'
|
|
297
|
+
first = '+' if self.cells[0].brd_bottom else '|'
|
|
298
|
+
return first + ''.join(cell.render_lower_bar(w, char) for cell, w in zip(self.cells, widths))
|
|
299
|
+
|
|
300
|
+
def render_lines(self, widths: Sequence[int]) -> Iterator[str]:
|
|
301
|
+
max_lines = max(cell.height for cell in self.cells)
|
|
302
|
+
renderers = [cell.render_lines(w, max_lines) for cell, w in zip(self.cells, widths)]
|
|
303
|
+
for cell_strs in zip(*renderers):
|
|
304
|
+
yield '|' + ''.join(cell_strs)
|
|
266
305
|
|
|
267
|
-
|
|
306
|
+
yield self.render_lower_bar(widths)
|
{cli_command_parser-2024.9.7 → cli_command_parser-2024.12.15}/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
|
|
@@ -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
|