paramspecli 0.2.1__py3-none-any.whl
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.
- paramspecli/__init__.py +27 -0
- paramspecli/apstub.py +84 -0
- paramspecli/args.py +110 -0
- paramspecli/cli.py +956 -0
- paramspecli/conv.py +58 -0
- paramspecli/doc.py +368 -0
- paramspecli/fake.py +127 -0
- paramspecli/flags.py +226 -0
- paramspecli/md.py +75 -0
- paramspecli/opts.py +324 -0
- paramspecli/py.typed +0 -0
- paramspecli/util.py +50 -0
- paramspecli-0.2.1.dist-info/METADATA +88 -0
- paramspecli-0.2.1.dist-info/RECORD +16 -0
- paramspecli-0.2.1.dist-info/WHEEL +4 -0
- paramspecli-0.2.1.dist-info/licenses/LICENSE +21 -0
paramspecli/flags.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
from argparse import BooleanOptionalAction
|
|
2
|
+
from typing import Any, Literal, overload
|
|
3
|
+
|
|
4
|
+
from .fake import Option, RepeatedOption
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# common use - set: True, unset: False
|
|
9
|
+
@overload
|
|
10
|
+
def flag( # type: ignore[overload-overlap]
|
|
11
|
+
*names: str,
|
|
12
|
+
value: bool = True,
|
|
13
|
+
help: str | Literal[False] | None = None,
|
|
14
|
+
show_default: bool | str | None = None,
|
|
15
|
+
) -> Option[bool, bool]: ...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## value: T
|
|
19
|
+
# even less common use - set: T, unset: None
|
|
20
|
+
@overload
|
|
21
|
+
def flag[T](
|
|
22
|
+
*names: str,
|
|
23
|
+
value: T,
|
|
24
|
+
help: str | Literal[False] | None = None,
|
|
25
|
+
show_default: bool | str | None = None,
|
|
26
|
+
) -> Option[T, None]: ...
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## value: T, default: D
|
|
30
|
+
# even even less common use - set: T, unset: D
|
|
31
|
+
@overload
|
|
32
|
+
def flag[T, D](
|
|
33
|
+
*names: str,
|
|
34
|
+
value: T,
|
|
35
|
+
default: D,
|
|
36
|
+
help: str | Literal[False] | None = None,
|
|
37
|
+
show_default: bool | str | None = None,
|
|
38
|
+
) -> Option[T, D]: ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## default: D
|
|
42
|
+
# for completeness. shouldn't be really used
|
|
43
|
+
@overload
|
|
44
|
+
def flag[D](
|
|
45
|
+
*names: str,
|
|
46
|
+
default: D,
|
|
47
|
+
value: bool = True,
|
|
48
|
+
help: str | Literal[False] | None = None,
|
|
49
|
+
show_default: bool | str | None = None,
|
|
50
|
+
) -> Option[bool, D]: ...
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# NOTE: using singleton ellipsis here as the sentinel
|
|
54
|
+
def flag(
|
|
55
|
+
*names: str,
|
|
56
|
+
value: Any = True,
|
|
57
|
+
default: Any = ...,
|
|
58
|
+
help: str | bool | None = None,
|
|
59
|
+
show_default: bool | str | None = None,
|
|
60
|
+
) -> Option[Any, Any]:
|
|
61
|
+
"""flag
|
|
62
|
+
|
|
63
|
+
In basic configuration it's boolean:
|
|
64
|
+
```
|
|
65
|
+
flag() -> True if --foo else False
|
|
66
|
+
```
|
|
67
|
+
Pass `value=False` to invert:
|
|
68
|
+
```
|
|
69
|
+
flag(value=False) -> False if --foo else True
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Pass non-bool `value` to disable the boolean mode:
|
|
73
|
+
```
|
|
74
|
+
flag(value=123) -> 123 if --foo else None
|
|
75
|
+
```
|
|
76
|
+
Pass `default` to change default:
|
|
77
|
+
```
|
|
78
|
+
flag(value=123, default=456) -> 123 if --foo else 456
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
if default is ...:
|
|
84
|
+
soft_show_default = False
|
|
85
|
+
if value is True:
|
|
86
|
+
default = False
|
|
87
|
+
elif value is False:
|
|
88
|
+
default = True
|
|
89
|
+
else:
|
|
90
|
+
default = None
|
|
91
|
+
else:
|
|
92
|
+
soft_show_default = default is not None
|
|
93
|
+
|
|
94
|
+
return Option(
|
|
95
|
+
names,
|
|
96
|
+
help=help,
|
|
97
|
+
action="store_const",
|
|
98
|
+
const=value,
|
|
99
|
+
default=default,
|
|
100
|
+
#
|
|
101
|
+
hard_show_default=show_default,
|
|
102
|
+
soft_show_default=soft_show_default,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
## -> bool
|
|
107
|
+
@overload
|
|
108
|
+
def switch(
|
|
109
|
+
*names: str,
|
|
110
|
+
default: bool = False,
|
|
111
|
+
help: str | Literal[False] | None = None,
|
|
112
|
+
show_default: bool | str | None = None,
|
|
113
|
+
) -> Option[bool, bool]: ...
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
## default: D
|
|
117
|
+
@overload
|
|
118
|
+
def switch[D](
|
|
119
|
+
*names: str,
|
|
120
|
+
default: D,
|
|
121
|
+
help: str | Literal[False] | None = None,
|
|
122
|
+
show_default: bool | str | None = None,
|
|
123
|
+
) -> Option[bool, D]: ...
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def switch(
|
|
127
|
+
*names: str,
|
|
128
|
+
default: Any = False,
|
|
129
|
+
help: str | bool | None = None,
|
|
130
|
+
show_default: bool | str | None = None,
|
|
131
|
+
) -> Option[bool, Any]:
|
|
132
|
+
"""On/Off switch with complimentary flags: `--foo/--no-foo`. Default is `False`"""
|
|
133
|
+
longs = [name for name in names if name.startswith("--")]
|
|
134
|
+
if not longs:
|
|
135
|
+
raise ValueError("at least one name starting with '--' is required")
|
|
136
|
+
|
|
137
|
+
soft_show_default: bool | str
|
|
138
|
+
|
|
139
|
+
if default is True or default is False:
|
|
140
|
+
soft_show_default = longs[0] if default else f"--no-{longs[0][2:]}"
|
|
141
|
+
else:
|
|
142
|
+
soft_show_default = True
|
|
143
|
+
|
|
144
|
+
return Option(
|
|
145
|
+
names,
|
|
146
|
+
help=help,
|
|
147
|
+
action=BooleanOptionalAction,
|
|
148
|
+
default=default,
|
|
149
|
+
#
|
|
150
|
+
hard_show_default=show_default,
|
|
151
|
+
soft_show_default=soft_show_default,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
## -> int
|
|
156
|
+
@overload
|
|
157
|
+
def count(
|
|
158
|
+
*names: str,
|
|
159
|
+
default: int = 0,
|
|
160
|
+
help: str | Literal[False] | None = None,
|
|
161
|
+
show_default: bool | str | None = None,
|
|
162
|
+
) -> Option[int, int]: ...
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
## default: None
|
|
166
|
+
@overload
|
|
167
|
+
def count(
|
|
168
|
+
*names: str,
|
|
169
|
+
default: None,
|
|
170
|
+
help: str | Literal[False] | None = None,
|
|
171
|
+
show_default: bool | str | None = None,
|
|
172
|
+
) -> Option[int, None]: ...
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def count(
|
|
176
|
+
*names: str,
|
|
177
|
+
default: int | None = 0,
|
|
178
|
+
help: str | bool | None = None,
|
|
179
|
+
show_default: bool | str | None = None,
|
|
180
|
+
) -> Option[int, Any]:
|
|
181
|
+
"""Counter: `-vvv`. Default is `0`"""
|
|
182
|
+
return Option(
|
|
183
|
+
names,
|
|
184
|
+
help=help,
|
|
185
|
+
action="count",
|
|
186
|
+
default=default,
|
|
187
|
+
#
|
|
188
|
+
hard_show_default=show_default,
|
|
189
|
+
soft_show_default=False,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
##
|
|
194
|
+
@overload
|
|
195
|
+
def repeated_flag(
|
|
196
|
+
*names: str,
|
|
197
|
+
value: bool = True,
|
|
198
|
+
help: str | Literal[False] | None = None,
|
|
199
|
+
) -> RepeatedOption[bool]: ...
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# value: T
|
|
203
|
+
@overload
|
|
204
|
+
def repeated_flag[T](
|
|
205
|
+
*names: str,
|
|
206
|
+
value: T,
|
|
207
|
+
help: str | Literal[False] | None = None,
|
|
208
|
+
) -> RepeatedOption[T]: ...
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def repeated_flag(
|
|
212
|
+
*names: str,
|
|
213
|
+
value: Any = True,
|
|
214
|
+
help: str | bool | None = None,
|
|
215
|
+
) -> RepeatedOption[Any]:
|
|
216
|
+
"""Flag which could be present multiple times on a command line.
|
|
217
|
+
Each appearance adds `value` to the list.
|
|
218
|
+
"""
|
|
219
|
+
return RepeatedOption(
|
|
220
|
+
names,
|
|
221
|
+
help=help,
|
|
222
|
+
action="append_const",
|
|
223
|
+
default=[],
|
|
224
|
+
const=value,
|
|
225
|
+
soft_show_default=False,
|
|
226
|
+
)
|
paramspecli/md.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Iterable
|
|
3
|
+
|
|
4
|
+
# NOTE: dots are not escaped
|
|
5
|
+
_MD_ESCAPE = re.compile("[" + re.escape(r"\`*_{}[]<>()#+-!") + "]")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Md(str):
|
|
9
|
+
__slots__ = ()
|
|
10
|
+
|
|
11
|
+
def plain(self) -> str:
|
|
12
|
+
return self.replace("\\", "")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Renderer:
|
|
16
|
+
"""Default markdown renderer"""
|
|
17
|
+
|
|
18
|
+
def p(self, text: str) -> str:
|
|
19
|
+
return text + "\n\n"
|
|
20
|
+
|
|
21
|
+
def h(self, level: int, text: str) -> str:
|
|
22
|
+
return f"{'#' * level} {text}\n\n"
|
|
23
|
+
|
|
24
|
+
def br(self) -> str:
|
|
25
|
+
return "<br />\n"
|
|
26
|
+
|
|
27
|
+
def b(self, text: str) -> str:
|
|
28
|
+
return f"**{text}**"
|
|
29
|
+
|
|
30
|
+
def i(self, text: str) -> str:
|
|
31
|
+
return f"*{text}*"
|
|
32
|
+
|
|
33
|
+
def blockquote(self, text: str) -> str:
|
|
34
|
+
return "\n".join([f"> {line}" for line in text.splitlines()]) + "\n\n"
|
|
35
|
+
|
|
36
|
+
def ul(self, lis: Iterable[str]) -> str:
|
|
37
|
+
elts: list[str] = []
|
|
38
|
+
for li in lis:
|
|
39
|
+
lines = li.splitlines()
|
|
40
|
+
if lines:
|
|
41
|
+
elts.append(f"- {lines[0]}")
|
|
42
|
+
elts.extend(f" {line}" for line in lines[1:])
|
|
43
|
+
return "\n".join(elts) + "\n\n"
|
|
44
|
+
|
|
45
|
+
def dl(self, dtdds: Iterable[tuple[str, str]]) -> str:
|
|
46
|
+
return self.ul(self.p(dt) + self.p(dd) for dt, dd in dtdds)
|
|
47
|
+
|
|
48
|
+
def code(self, text: str) -> str:
|
|
49
|
+
return f"`{text}`"
|
|
50
|
+
|
|
51
|
+
def codeblock(self, text: str, lang: str | None = None) -> str:
|
|
52
|
+
return f"```{lang or ''}\n{text}\n```\n\n"
|
|
53
|
+
|
|
54
|
+
def strike(self, text: str) -> str:
|
|
55
|
+
return f"~~~{text}~~~"
|
|
56
|
+
|
|
57
|
+
def hr(self) -> str:
|
|
58
|
+
return "---\n\n"
|
|
59
|
+
|
|
60
|
+
def link(self, url: str, *, text: str | None) -> str:
|
|
61
|
+
if text:
|
|
62
|
+
return f"[{text}]({url})"
|
|
63
|
+
return f"({url})"
|
|
64
|
+
|
|
65
|
+
def e(self, text: str) -> str:
|
|
66
|
+
return _MD_ESCAPE.sub(r"\\\g<0>", text)
|
|
67
|
+
|
|
68
|
+
def body(self, text: str) -> str:
|
|
69
|
+
return text
|
|
70
|
+
|
|
71
|
+
# It's easier to post-process markdown than account for extra newlines while generating
|
|
72
|
+
def postprocess(self, text: str) -> str:
|
|
73
|
+
out = "\n".join(line.rstrip() for line in text.splitlines())
|
|
74
|
+
out = re.sub(r"\n{3,}", "\n\n", out)
|
|
75
|
+
return out
|
paramspecli/opts.py
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
from types import EllipsisType
|
|
2
|
+
from typing import Any, Iterable, Literal, overload
|
|
3
|
+
|
|
4
|
+
from .apstub import TypeConverter
|
|
5
|
+
from .fake import Option, RepeatedOption
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
@overload
|
|
10
|
+
def option(
|
|
11
|
+
*names: str,
|
|
12
|
+
help: str | Literal[False] | None = None,
|
|
13
|
+
choices: Iterable[str] | None = None,
|
|
14
|
+
metavar: str | None = None,
|
|
15
|
+
show_default: bool | str | None = None,
|
|
16
|
+
) -> Option[str, None]: ...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## default: D
|
|
20
|
+
@overload
|
|
21
|
+
def option[D](
|
|
22
|
+
*names: str,
|
|
23
|
+
default: D,
|
|
24
|
+
help: str | Literal[False] | None = None,
|
|
25
|
+
choices: Iterable[str] | None = None,
|
|
26
|
+
metavar: str | None = None,
|
|
27
|
+
show_default: bool | str | None = None,
|
|
28
|
+
) -> Option[str, D]: ...
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## nargs
|
|
32
|
+
@overload
|
|
33
|
+
def option(
|
|
34
|
+
*names: str,
|
|
35
|
+
nargs: int | Literal["+", "*"],
|
|
36
|
+
help: str | Literal[False] | None = None,
|
|
37
|
+
choices: Iterable[str] | None = None,
|
|
38
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
39
|
+
show_default: bool | str | None = None,
|
|
40
|
+
) -> Option[list[str], None]: ...
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## optional
|
|
44
|
+
@overload
|
|
45
|
+
def option(
|
|
46
|
+
*names: str,
|
|
47
|
+
nargs: Literal["?"],
|
|
48
|
+
help: str | Literal[False] | None = None,
|
|
49
|
+
choices: Iterable[str] | None = None,
|
|
50
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
51
|
+
show_default: bool | str | None = None,
|
|
52
|
+
) -> Option[str | EllipsisType, None]: ...
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## nargs, default: D
|
|
56
|
+
@overload
|
|
57
|
+
def option[D](
|
|
58
|
+
*names: str,
|
|
59
|
+
nargs: int | Literal["+", "*"],
|
|
60
|
+
default: D,
|
|
61
|
+
help: str | Literal[False] | None = None,
|
|
62
|
+
choices: Iterable[str] | None = None,
|
|
63
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
64
|
+
show_default: bool | str | None = None,
|
|
65
|
+
) -> Option[list[str], D]: ...
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
## optional, default: D
|
|
69
|
+
@overload
|
|
70
|
+
def option[D](
|
|
71
|
+
*names: str,
|
|
72
|
+
nargs: Literal["?"],
|
|
73
|
+
default: D,
|
|
74
|
+
help: str | Literal[False] | None = None,
|
|
75
|
+
choices: Iterable[str] | None = None,
|
|
76
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
77
|
+
show_default: bool | str | None = None,
|
|
78
|
+
) -> Option[str | EllipsisType, D]: ...
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
## type: T
|
|
82
|
+
@overload
|
|
83
|
+
def option[T](
|
|
84
|
+
*names: str,
|
|
85
|
+
type: TypeConverter[T],
|
|
86
|
+
help: str | Literal[False] | None = None,
|
|
87
|
+
choices: Iterable[T] | None = None,
|
|
88
|
+
metavar: str | None = None,
|
|
89
|
+
show_default: bool | str | None = None,
|
|
90
|
+
) -> Option[T, None]: ...
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
## type: T, default: str | D
|
|
94
|
+
# default is parsed if str, otherwise left as is
|
|
95
|
+
@overload
|
|
96
|
+
def option[T, D](
|
|
97
|
+
*names: str,
|
|
98
|
+
type: TypeConverter[T],
|
|
99
|
+
default: str | D,
|
|
100
|
+
help: str | Literal[False] | None = None,
|
|
101
|
+
choices: Iterable[T] | None = None,
|
|
102
|
+
metavar: str | None = None,
|
|
103
|
+
show_default: bool | str | None = None,
|
|
104
|
+
) -> Option[T, T | D]: ...
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
## type: T, nargs
|
|
108
|
+
@overload
|
|
109
|
+
def option[T](
|
|
110
|
+
*names: str,
|
|
111
|
+
type: TypeConverter[T],
|
|
112
|
+
nargs: int | Literal["+", "*"],
|
|
113
|
+
help: str | Literal[False] | None = None,
|
|
114
|
+
choices: Iterable[T] | None = None,
|
|
115
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
116
|
+
show_default: bool | str | None = None,
|
|
117
|
+
) -> Option[list[T], None]: ...
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
## type: T, optional
|
|
121
|
+
@overload
|
|
122
|
+
def option[T](
|
|
123
|
+
*names: str,
|
|
124
|
+
type: TypeConverter[T],
|
|
125
|
+
nargs: Literal["?"],
|
|
126
|
+
help: str | Literal[False] | None = None,
|
|
127
|
+
choices: Iterable[T] | None = None,
|
|
128
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
129
|
+
show_default: bool | str | None = None,
|
|
130
|
+
) -> Option[T | EllipsisType, None]: ...
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
## type: T, default: str, nargs
|
|
134
|
+
# yes, this one is strange. default is parsed by T, but list is not formed
|
|
135
|
+
@overload
|
|
136
|
+
def option[T](
|
|
137
|
+
*names: str,
|
|
138
|
+
type: TypeConverter[T],
|
|
139
|
+
nargs: int | Literal["+", "*"],
|
|
140
|
+
default: str,
|
|
141
|
+
help: str | Literal[False] | None = None,
|
|
142
|
+
choices: Iterable[T] | None = None,
|
|
143
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
144
|
+
show_default: bool | str | None = None,
|
|
145
|
+
) -> Option[list[T], T]: ...
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
## type: T, default: str, optional
|
|
149
|
+
@overload
|
|
150
|
+
def option[T](
|
|
151
|
+
*names: str,
|
|
152
|
+
type: TypeConverter[T],
|
|
153
|
+
nargs: Literal["?"],
|
|
154
|
+
default: str,
|
|
155
|
+
help: str | Literal[False] | None = None,
|
|
156
|
+
choices: Iterable[T] | None = None,
|
|
157
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
158
|
+
show_default: bool | str | None = None,
|
|
159
|
+
) -> Option[T | EllipsisType, T]: ...
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
## type: T, default: D, nargs
|
|
163
|
+
@overload
|
|
164
|
+
def option[T, D](
|
|
165
|
+
*names: str,
|
|
166
|
+
type: TypeConverter[T],
|
|
167
|
+
nargs: int | Literal["+", "*"],
|
|
168
|
+
default: D,
|
|
169
|
+
help: str | Literal[False] | None = None,
|
|
170
|
+
choices: Iterable[T] | None = None,
|
|
171
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
172
|
+
show_default: bool | str | None = None,
|
|
173
|
+
) -> Option[list[T], D]: ...
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
## type: T, default: D, optional
|
|
177
|
+
@overload
|
|
178
|
+
def option[T, D](
|
|
179
|
+
*names: str,
|
|
180
|
+
type: TypeConverter[T],
|
|
181
|
+
nargs: Literal["?"],
|
|
182
|
+
default: D,
|
|
183
|
+
help: str | Literal[False] | None = None,
|
|
184
|
+
choices: Iterable[T] | None = None,
|
|
185
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
186
|
+
show_default: bool | str | None = None,
|
|
187
|
+
) -> Option[T | EllipsisType, D]: ...
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def option(
|
|
191
|
+
*names: str,
|
|
192
|
+
type: TypeConverter[Any] | None = None,
|
|
193
|
+
default: Any = None,
|
|
194
|
+
nargs: int | Literal["+", "*", "?"] | None = None,
|
|
195
|
+
help: str | bool | None = None,
|
|
196
|
+
choices: Iterable[Any] | None = None,
|
|
197
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
198
|
+
show_default: bool | str | None = None,
|
|
199
|
+
) -> Option[Any, Any]:
|
|
200
|
+
"""Just an option"""
|
|
201
|
+
return Option(
|
|
202
|
+
names,
|
|
203
|
+
help=help,
|
|
204
|
+
type=type,
|
|
205
|
+
nargs=nargs,
|
|
206
|
+
default=default,
|
|
207
|
+
const=... if nargs == "?" else None,
|
|
208
|
+
choices=choices,
|
|
209
|
+
metavar=metavar if metavar is not None else names[0].lstrip("-").upper(),
|
|
210
|
+
action="store",
|
|
211
|
+
#
|
|
212
|
+
hard_show_default=show_default,
|
|
213
|
+
soft_show_default=default is not None,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
##
|
|
218
|
+
@overload
|
|
219
|
+
def repeated_option(
|
|
220
|
+
*names: str,
|
|
221
|
+
help: str | Literal[False] | None = None,
|
|
222
|
+
choices: Iterable[str] | None = None,
|
|
223
|
+
metavar: str | None = None,
|
|
224
|
+
) -> RepeatedOption[str]: ...
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
## nargs
|
|
228
|
+
@overload
|
|
229
|
+
def repeated_option(
|
|
230
|
+
*names: str,
|
|
231
|
+
nargs: int | Literal["+", "*"],
|
|
232
|
+
flatten: Literal[False] = False,
|
|
233
|
+
help: str | Literal[False] | None = None,
|
|
234
|
+
choices: Iterable[str] | None = None,
|
|
235
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
236
|
+
) -> RepeatedOption[list[str]]: ...
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
## nargs, flatten
|
|
240
|
+
@overload
|
|
241
|
+
def repeated_option(
|
|
242
|
+
*names: str,
|
|
243
|
+
nargs: int | Literal["+", "*"],
|
|
244
|
+
flatten: Literal[True],
|
|
245
|
+
help: str | Literal[False] | None = None,
|
|
246
|
+
choices: Iterable[str] | None = None,
|
|
247
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
248
|
+
) -> RepeatedOption[str]: ...
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
## type: T
|
|
252
|
+
@overload
|
|
253
|
+
def repeated_option[T](
|
|
254
|
+
*names: str,
|
|
255
|
+
type: TypeConverter[T],
|
|
256
|
+
flatten: Literal[False] = False,
|
|
257
|
+
help: str | Literal[False] | None = None,
|
|
258
|
+
choices: Iterable[T] | None = None,
|
|
259
|
+
metavar: str | None = None,
|
|
260
|
+
) -> RepeatedOption[T]: ...
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
## type: T, flatten
|
|
264
|
+
# gotcha: type converter should return iterable so it could extend running list
|
|
265
|
+
@overload
|
|
266
|
+
def repeated_option[T](
|
|
267
|
+
*names: str,
|
|
268
|
+
type: TypeConverter[Iterable[T]],
|
|
269
|
+
flatten: Literal[True],
|
|
270
|
+
help: str | Literal[False] | None = None,
|
|
271
|
+
choices: Iterable[T] | None = None,
|
|
272
|
+
metavar: str | None = None,
|
|
273
|
+
) -> RepeatedOption[T]: ...
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
## type: T, nargs
|
|
277
|
+
@overload
|
|
278
|
+
def repeated_option[T](
|
|
279
|
+
*names: str,
|
|
280
|
+
type: TypeConverter[T],
|
|
281
|
+
nargs: int | Literal["+", "*"],
|
|
282
|
+
flatten: Literal[False] = False,
|
|
283
|
+
help: str | Literal[False] | None = None,
|
|
284
|
+
choices: Iterable[T] | None = None,
|
|
285
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
286
|
+
) -> RepeatedOption[list[T]]: ...
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
## type: T, nargs, flatten
|
|
290
|
+
@overload
|
|
291
|
+
def repeated_option[T](
|
|
292
|
+
*names: str,
|
|
293
|
+
type: TypeConverter[T],
|
|
294
|
+
nargs: int | Literal["+", "*"],
|
|
295
|
+
flatten: Literal[True],
|
|
296
|
+
help: str | Literal[False] | None = None,
|
|
297
|
+
choices: Iterable[T] | None = None,
|
|
298
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
299
|
+
) -> RepeatedOption[T]: ...
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def repeated_option(
|
|
303
|
+
*names: str,
|
|
304
|
+
type: TypeConverter[Any] | None = None,
|
|
305
|
+
nargs: int | Literal["+", "*"] | None = None,
|
|
306
|
+
flatten: bool = False,
|
|
307
|
+
help: str | bool | None = None,
|
|
308
|
+
choices: Iterable[Any] | None = None,
|
|
309
|
+
metavar: str | tuple[str, ...] | None = None,
|
|
310
|
+
) -> RepeatedOption[Any]:
|
|
311
|
+
"""Option which could present multiple times on a command line.
|
|
312
|
+
Result is collected into the list"""
|
|
313
|
+
return RepeatedOption(
|
|
314
|
+
names,
|
|
315
|
+
help=help,
|
|
316
|
+
type=type,
|
|
317
|
+
nargs=nargs,
|
|
318
|
+
choices=choices,
|
|
319
|
+
metavar=metavar if metavar is not None else names[0].lstrip("-").upper(),
|
|
320
|
+
action="extend" if flatten else "append",
|
|
321
|
+
default=[],
|
|
322
|
+
#
|
|
323
|
+
soft_show_default=False,
|
|
324
|
+
)
|
paramspecli/py.typed
ADDED
|
File without changes
|
paramspecli/util.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from argparse import ArgumentTypeError
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import Callable, NoReturn, TextIO
|
|
5
|
+
|
|
6
|
+
from .apstub import TypeConverter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def echo(*strings: str, nl: bool = True, stream: TextIO | None = None) -> None:
|
|
10
|
+
"""Should be used instead of the print() to make intention clear"""
|
|
11
|
+
out = stream or sys.stdout
|
|
12
|
+
for s in strings:
|
|
13
|
+
out.write(s)
|
|
14
|
+
if nl:
|
|
15
|
+
out.write("\n")
|
|
16
|
+
out.flush()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def exit(status: int = 0, message: str | tuple[str, ...] | None = None) -> NoReturn:
|
|
20
|
+
"""Print error message and exit"""
|
|
21
|
+
if message:
|
|
22
|
+
if isinstance(message, str):
|
|
23
|
+
message = (message,)
|
|
24
|
+
echo(*message, stream=sys.stderr)
|
|
25
|
+
sys.exit(status)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def catch_all[T](f: TypeConverter[T]) -> TypeConverter[T]:
|
|
29
|
+
"""Catch type converter exceptions and present them as the cli errors"""
|
|
30
|
+
|
|
31
|
+
@wraps(f)
|
|
32
|
+
def catcher(s: str) -> T:
|
|
33
|
+
try:
|
|
34
|
+
return f(s)
|
|
35
|
+
except ArgumentTypeError as e:
|
|
36
|
+
raise e
|
|
37
|
+
except Exception as e:
|
|
38
|
+
raise ArgumentTypeError(str(e)) from e
|
|
39
|
+
|
|
40
|
+
return catcher
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def resolve_later[**P](resolve: Callable[[], Callable[P, None]]) -> Callable[P, None]:
|
|
44
|
+
"""Allows the handler to be resolved at the call time"""
|
|
45
|
+
|
|
46
|
+
@wraps(resolve)
|
|
47
|
+
def wrap(*args: P.args, **kwargs: P.kwargs) -> None:
|
|
48
|
+
resolve()(*args, **kwargs)
|
|
49
|
+
|
|
50
|
+
return wrap
|