PythonIota 1.2.0__py3-none-any.whl → 1.7.0__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.
- pythoniota/__init__.py +7 -2
- pythoniota/cli/__init__.py +27 -0
- pythoniota/cli/_argument.py +70 -0
- pythoniota/cli/_errors.py +9 -0
- pythoniota/cli/_formatter.py +93 -0
- pythoniota/cli/_namespace.py +17 -0
- pythoniota/cli/_parser.py +321 -0
- pythoniota/enum.py +60 -7
- pythoniota/recipes.py +137 -0
- pythoniota/regex/__init__.py +66 -0
- pythoniota/regex/_compiler.py +177 -0
- pythoniota/regex/_parser.py +463 -0
- pythoniota/regex/_pattern.py +210 -0
- pythoniota/regex/_vm.py +123 -0
- pythoniota/sequence.py +170 -19
- pythoniota-1.7.0.dist-info/METADATA +368 -0
- pythoniota-1.7.0.dist-info/RECORD +22 -0
- {pythoniota-1.2.0.dist-info → pythoniota-1.7.0.dist-info}/WHEEL +1 -1
- pythoniota-1.2.0.dist-info/METADATA +0 -16
- pythoniota-1.2.0.dist-info/RECORD +0 -11
- {pythoniota-1.2.0.dist-info → pythoniota-1.7.0.dist-info}/top_level.txt +0 -0
pythoniota/__init__.py
CHANGED
|
@@ -3,12 +3,14 @@ from pythoniota._bitflag import BitFlag
|
|
|
3
3
|
from pythoniota._safe_eval import safe_eval
|
|
4
4
|
from pythoniota.enum import (
|
|
5
5
|
IotaEnum, IotaBitFlags, IotaStringEnum,
|
|
6
|
-
FlagScope, iota_enum, _EnumMemberProxy,
|
|
6
|
+
FlagScope, iota_enum, unique, _EnumMemberProxy,
|
|
7
7
|
)
|
|
8
8
|
from pythoniota.sequence import IotaSequence as iota
|
|
9
9
|
from pythoniota import recipes
|
|
10
|
+
from pythoniota import regex
|
|
11
|
+
from pythoniota import cli
|
|
10
12
|
|
|
11
|
-
__version__ = "1.
|
|
13
|
+
__version__ = "1.7.0"
|
|
12
14
|
__all__ = [
|
|
13
15
|
"Iota",
|
|
14
16
|
"IotaEnum",
|
|
@@ -18,6 +20,9 @@ __all__ = [
|
|
|
18
20
|
"BitFlag",
|
|
19
21
|
"FlagScope",
|
|
20
22
|
"iota_enum",
|
|
23
|
+
"unique",
|
|
21
24
|
"safe_eval",
|
|
22
25
|
"recipes",
|
|
26
|
+
"regex",
|
|
27
|
+
"cli",
|
|
23
28
|
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""A from-scratch, zero-dependency command-line argument parser.
|
|
2
|
+
|
|
3
|
+
A small, faithful subset of the standard library's ``argparse``:
|
|
4
|
+
|
|
5
|
+
from pythoniota import cli
|
|
6
|
+
|
|
7
|
+
p = cli.ArgumentParser(prog="greet", description="say hello")
|
|
8
|
+
p.add_argument("name")
|
|
9
|
+
p.add_argument("-n", "--times", type=int, default=1)
|
|
10
|
+
p.add_argument("-v", "--verbose", action="store_true")
|
|
11
|
+
args = p.parse_args(["world", "--times", "3", "-v"])
|
|
12
|
+
args.name, args.times, args.verbose # -> ('world', 3, True)
|
|
13
|
+
|
|
14
|
+
Supported: positional and optional arguments, ``nargs`` (``N`` / ``?`` / ``*``
|
|
15
|
+
/ ``+``), ``type`` / ``choices`` / ``default`` / ``required``, actions
|
|
16
|
+
``store`` / ``store_true`` / ``store_false`` / ``store_const`` / ``append`` /
|
|
17
|
+
``count``, ``--opt=value`` and clustered short flags (``-abc``), the ``--``
|
|
18
|
+
end-of-options marker, an auto-generated ``-h/--help``, and sub-commands via
|
|
19
|
+
``add_subparsers``. Bad user input prints usage and raises ``SystemExit``.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from ._argument import Argument
|
|
23
|
+
from ._errors import ArgumentError
|
|
24
|
+
from ._namespace import Namespace
|
|
25
|
+
from ._parser import ArgumentParser
|
|
26
|
+
|
|
27
|
+
__all__ = ["ArgumentParser", "Namespace", "Argument", "ArgumentError"]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""A single argument specification (the result of one ``add_argument`` call)."""
|
|
2
|
+
|
|
3
|
+
INF = float("inf")
|
|
4
|
+
|
|
5
|
+
# Actions that consume no value from the command line.
|
|
6
|
+
_ZERO_ACTIONS = ("store_true", "store_false", "store_const", "count", "help")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Argument:
|
|
10
|
+
def __init__(self, option_strings, dest, action, nargs, const, default,
|
|
11
|
+
type, choices, required, metavar, help):
|
|
12
|
+
self.option_strings = option_strings # [] for positionals
|
|
13
|
+
self.dest = dest
|
|
14
|
+
self.action = action
|
|
15
|
+
self.nargs = nargs
|
|
16
|
+
self.const = const
|
|
17
|
+
self.default = default
|
|
18
|
+
self.type = type
|
|
19
|
+
self.choices = choices
|
|
20
|
+
self.required = required
|
|
21
|
+
self.metavar = metavar
|
|
22
|
+
self.help = help
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def is_positional(self):
|
|
26
|
+
return not self.option_strings
|
|
27
|
+
|
|
28
|
+
def takes_value(self):
|
|
29
|
+
return self.action not in _ZERO_ACTIONS
|
|
30
|
+
|
|
31
|
+
def get_default(self):
|
|
32
|
+
if self.action == "store_true":
|
|
33
|
+
return False
|
|
34
|
+
if self.action == "store_false":
|
|
35
|
+
return True
|
|
36
|
+
return self.default
|
|
37
|
+
|
|
38
|
+
def count_range(self):
|
|
39
|
+
"""(min, max) number of command-line tokens this argument consumes."""
|
|
40
|
+
if not self.takes_value():
|
|
41
|
+
return (0, 0)
|
|
42
|
+
n = self.nargs
|
|
43
|
+
if n is None:
|
|
44
|
+
return (1, 1)
|
|
45
|
+
if n == "?":
|
|
46
|
+
return (0, 1)
|
|
47
|
+
if n == "*":
|
|
48
|
+
return (0, INF)
|
|
49
|
+
if n == "+":
|
|
50
|
+
return (1, INF)
|
|
51
|
+
return (n, n) # an int
|
|
52
|
+
|
|
53
|
+
def display_name(self):
|
|
54
|
+
return "/".join(self.option_strings) if self.option_strings else self.metavar
|
|
55
|
+
|
|
56
|
+
def convert_one(self, raw, parser):
|
|
57
|
+
if self.type is not None:
|
|
58
|
+
try:
|
|
59
|
+
value = self.type(raw)
|
|
60
|
+
except (ValueError, TypeError):
|
|
61
|
+
tname = getattr(self.type, "__name__", str(self.type))
|
|
62
|
+
parser.error(f"argument {self.display_name()}: "
|
|
63
|
+
f"invalid {tname} value: {raw!r}")
|
|
64
|
+
else:
|
|
65
|
+
value = raw
|
|
66
|
+
if self.choices is not None and value not in self.choices:
|
|
67
|
+
choices = ", ".join(repr(c) for c in self.choices)
|
|
68
|
+
parser.error(f"argument {self.display_name()}: "
|
|
69
|
+
f"invalid choice: {value!r} (choose from {choices})")
|
|
70
|
+
return value
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Exceptions raised while *building* a parser (bad add_argument calls).
|
|
2
|
+
|
|
3
|
+
Parse-time problems (bad user input) go through ``ArgumentParser.error`` and
|
|
4
|
+
raise ``SystemExit`` instead, matching the standard library.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ArgumentError(Exception):
|
|
9
|
+
"""A malformed argument specification."""
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Usage/help text generation. Layout is clean and readable; it deliberately
|
|
2
|
+
does not try to reproduce the standard library's column math byte-for-byte."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _value_pattern(arg):
|
|
6
|
+
"""The ``METAVAR`` portion of an argument's usage, honoring nargs."""
|
|
7
|
+
if not arg.takes_value():
|
|
8
|
+
return ""
|
|
9
|
+
m, n = arg.metavar, arg.nargs
|
|
10
|
+
if n is None:
|
|
11
|
+
return m
|
|
12
|
+
if n == "?":
|
|
13
|
+
return f"[{m}]"
|
|
14
|
+
if n == "*":
|
|
15
|
+
return f"[{m} ...]"
|
|
16
|
+
if n == "+":
|
|
17
|
+
return f"{m} [{m} ...]"
|
|
18
|
+
return " ".join([m] * n) # an int
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _optional_usage(arg):
|
|
22
|
+
flag = arg.option_strings[0]
|
|
23
|
+
value = _value_pattern(arg)
|
|
24
|
+
piece = f"{flag} {value}" if value else flag
|
|
25
|
+
return piece if arg.required else f"[{piece}]"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _positional_usage(arg):
|
|
29
|
+
m = arg.metavar
|
|
30
|
+
n = arg.nargs
|
|
31
|
+
if n == "?":
|
|
32
|
+
return f"[{m}]"
|
|
33
|
+
if n == "*":
|
|
34
|
+
return f"[{m} ...]"
|
|
35
|
+
if n == "+":
|
|
36
|
+
return f"{m} [{m} ...]"
|
|
37
|
+
if isinstance(n, int):
|
|
38
|
+
return " ".join([m] * n)
|
|
39
|
+
return m
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def format_usage(parser):
|
|
43
|
+
parts = [f"usage: {parser.prog}"]
|
|
44
|
+
for arg in parser._arguments:
|
|
45
|
+
if arg.option_strings:
|
|
46
|
+
parts.append(_optional_usage(arg))
|
|
47
|
+
for arg in parser._arguments:
|
|
48
|
+
if not arg.option_strings:
|
|
49
|
+
parts.append(_positional_usage(arg))
|
|
50
|
+
if parser._subparsers is not None:
|
|
51
|
+
names = ",".join(parser._subparsers.choices)
|
|
52
|
+
parts.append(f"{{{names}}} ...")
|
|
53
|
+
return " ".join(parts) + "\n"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _row(invocation, help):
|
|
57
|
+
if not help:
|
|
58
|
+
return f" {invocation}\n"
|
|
59
|
+
if len(invocation) <= 20:
|
|
60
|
+
return f" {invocation:<20} {help}\n"
|
|
61
|
+
return f" {invocation}\n{'':<24}{help}\n"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def format_help(parser):
|
|
65
|
+
out = [format_usage(parser)]
|
|
66
|
+
if parser.description:
|
|
67
|
+
out.append("\n" + parser.description + "\n")
|
|
68
|
+
|
|
69
|
+
positionals = [a for a in parser._arguments if not a.option_strings]
|
|
70
|
+
optionals = [a for a in parser._arguments if a.option_strings]
|
|
71
|
+
|
|
72
|
+
if positionals or parser._subparsers is not None:
|
|
73
|
+
out.append("\npositional arguments:\n")
|
|
74
|
+
for arg in positionals:
|
|
75
|
+
out.append(_row(arg.metavar, arg.help))
|
|
76
|
+
if parser._subparsers is not None:
|
|
77
|
+
sp = parser._subparsers
|
|
78
|
+
out.append(_row("{" + ",".join(sp.choices) + "}", sp.help))
|
|
79
|
+
for name in sp.choices:
|
|
80
|
+
out.append(_row(" " + name, sp._help.get(name)))
|
|
81
|
+
|
|
82
|
+
if optionals:
|
|
83
|
+
out.append("\noptions:\n")
|
|
84
|
+
for arg in optionals:
|
|
85
|
+
invocation = ", ".join(arg.option_strings)
|
|
86
|
+
value = _value_pattern(arg)
|
|
87
|
+
if value:
|
|
88
|
+
invocation += " " + value
|
|
89
|
+
out.append(_row(invocation, arg.help))
|
|
90
|
+
|
|
91
|
+
if parser.epilog:
|
|
92
|
+
out.append("\n" + parser.epilog + "\n")
|
|
93
|
+
return "".join(out)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""The object ``parse_args`` fills in — attribute access over parsed values."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Namespace:
|
|
5
|
+
def __init__(self, **kwargs):
|
|
6
|
+
for key, value in kwargs.items():
|
|
7
|
+
setattr(self, key, value)
|
|
8
|
+
|
|
9
|
+
def __eq__(self, other):
|
|
10
|
+
return isinstance(other, Namespace) and vars(self) == vars(other)
|
|
11
|
+
|
|
12
|
+
def __contains__(self, key):
|
|
13
|
+
return key in self.__dict__
|
|
14
|
+
|
|
15
|
+
def __repr__(self):
|
|
16
|
+
items = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
|
|
17
|
+
return f"Namespace({items})"
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""A from-scratch, ``argparse``-style command-line parser.
|
|
2
|
+
|
|
3
|
+
The parser turns ``add_argument`` calls into :class:`Argument` specs, then does
|
|
4
|
+
a single left-to-right scan of ``argv``: options are consumed as they appear,
|
|
5
|
+
leftover tokens are matched against the positional specs by their (min, max)
|
|
6
|
+
counts. Errors print a usage line and raise ``SystemExit(2)`` — same contract
|
|
7
|
+
as the standard library, so it drops into scripts and tests unchanged.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
from ._argument import Argument, INF
|
|
14
|
+
from ._errors import ArgumentError
|
|
15
|
+
from ._namespace import Namespace
|
|
16
|
+
from . import _formatter
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _SubParsers:
|
|
20
|
+
"""The object returned by ``add_subparsers``; holds the command table."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, parent, dest, required, help, metavar):
|
|
23
|
+
self.parent = parent
|
|
24
|
+
self.dest = dest
|
|
25
|
+
self.required = required
|
|
26
|
+
self.help = help
|
|
27
|
+
self.metavar = metavar
|
|
28
|
+
self.choices = {} # name -> ArgumentParser
|
|
29
|
+
self._help = {} # name -> help string
|
|
30
|
+
|
|
31
|
+
def add_parser(self, name, help=None, **kwargs):
|
|
32
|
+
kwargs.setdefault("prog", f"{self.parent.prog} {name}")
|
|
33
|
+
parser = ArgumentParser(**kwargs)
|
|
34
|
+
self.choices[name] = parser
|
|
35
|
+
self._help[name] = help
|
|
36
|
+
return parser
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ArgumentParser:
|
|
40
|
+
def __init__(self, prog=None, description=None, epilog=None,
|
|
41
|
+
add_help=True, prefix_chars="-"):
|
|
42
|
+
self.prog = prog or os.path.basename(sys.argv[0] or "prog")
|
|
43
|
+
self.description = description
|
|
44
|
+
self.epilog = epilog
|
|
45
|
+
self.prefix = prefix_chars[0]
|
|
46
|
+
self._arguments = []
|
|
47
|
+
self._optionmap = {} # option string -> Argument
|
|
48
|
+
self._subparsers = None
|
|
49
|
+
self._seen = set()
|
|
50
|
+
if add_help:
|
|
51
|
+
self.add_argument(self.prefix + "h", self.prefix * 2 + "help",
|
|
52
|
+
action="help", help="show this help message and exit")
|
|
53
|
+
|
|
54
|
+
# -- building ----------------------------------------------------------- #
|
|
55
|
+
def add_argument(self, *names, **kwargs):
|
|
56
|
+
action = kwargs.get("action", "store")
|
|
57
|
+
if names and names[0].startswith(self.prefix):
|
|
58
|
+
option_strings = list(names)
|
|
59
|
+
dest = kwargs.get("dest")
|
|
60
|
+
if dest is None:
|
|
61
|
+
longs = [n for n in names if n.startswith(self.prefix * 2)]
|
|
62
|
+
base = (longs[0] if longs else names[0]).lstrip(self.prefix)
|
|
63
|
+
dest = base.replace("-", "_")
|
|
64
|
+
required = kwargs.get("required", False)
|
|
65
|
+
else:
|
|
66
|
+
if len(names) != 1:
|
|
67
|
+
raise ArgumentError("positional arguments take a single name")
|
|
68
|
+
option_strings = []
|
|
69
|
+
dest = kwargs.get("dest", names[0])
|
|
70
|
+
required = kwargs.get("nargs") not in ("?", "*")
|
|
71
|
+
|
|
72
|
+
const = kwargs.get("const")
|
|
73
|
+
if action == "store_const" and const is None:
|
|
74
|
+
raise ArgumentError("action 'store_const' requires 'const='")
|
|
75
|
+
|
|
76
|
+
metavar = kwargs.get("metavar")
|
|
77
|
+
if metavar is None:
|
|
78
|
+
metavar = dest.upper() if option_strings else dest
|
|
79
|
+
|
|
80
|
+
arg = Argument(
|
|
81
|
+
option_strings=option_strings,
|
|
82
|
+
dest=dest,
|
|
83
|
+
action=action,
|
|
84
|
+
nargs=kwargs.get("nargs"),
|
|
85
|
+
const=const,
|
|
86
|
+
default=kwargs.get("default"),
|
|
87
|
+
type=kwargs.get("type"),
|
|
88
|
+
choices=kwargs.get("choices"),
|
|
89
|
+
required=required,
|
|
90
|
+
metavar=metavar,
|
|
91
|
+
help=kwargs.get("help"),
|
|
92
|
+
)
|
|
93
|
+
self._arguments.append(arg)
|
|
94
|
+
for opt in option_strings:
|
|
95
|
+
self._optionmap[opt] = arg
|
|
96
|
+
return arg
|
|
97
|
+
|
|
98
|
+
def add_subparsers(self, dest=None, required=False, help=None, metavar=None):
|
|
99
|
+
if self._subparsers is not None:
|
|
100
|
+
raise ArgumentError("cannot have multiple subparser sets")
|
|
101
|
+
self._subparsers = _SubParsers(self, dest, required, help, metavar)
|
|
102
|
+
return self._subparsers
|
|
103
|
+
|
|
104
|
+
# -- parsing ------------------------------------------------------------ #
|
|
105
|
+
def parse_args(self, argv=None, namespace=None):
|
|
106
|
+
ns, extras = self.parse_known_args(argv, namespace)
|
|
107
|
+
if extras:
|
|
108
|
+
self.error("unrecognized arguments: " + " ".join(extras))
|
|
109
|
+
return ns
|
|
110
|
+
|
|
111
|
+
def parse_known_args(self, argv=None, namespace=None):
|
|
112
|
+
if argv is None:
|
|
113
|
+
argv = sys.argv[1:]
|
|
114
|
+
argv = list(argv)
|
|
115
|
+
if namespace is None:
|
|
116
|
+
namespace = Namespace()
|
|
117
|
+
self._seen = set()
|
|
118
|
+
|
|
119
|
+
for arg in self._arguments:
|
|
120
|
+
if arg.action != "help" and not hasattr(namespace, arg.dest):
|
|
121
|
+
setattr(namespace, arg.dest, arg.get_default())
|
|
122
|
+
|
|
123
|
+
extras = [] # (original_index, token)
|
|
124
|
+
if self._subparsers is not None:
|
|
125
|
+
return self._parse_with_subparsers(argv, namespace, extras)
|
|
126
|
+
|
|
127
|
+
positionals_raw = [] # (original_index, token)
|
|
128
|
+
i, after_ddash = 0, False
|
|
129
|
+
while i < len(argv):
|
|
130
|
+
tok = argv[i]
|
|
131
|
+
if not after_ddash and tok == self.prefix * 2:
|
|
132
|
+
after_ddash = True
|
|
133
|
+
i += 1
|
|
134
|
+
elif not after_ddash and self._is_option(tok):
|
|
135
|
+
i = self._consume_optional(argv, i, namespace, extras)
|
|
136
|
+
else:
|
|
137
|
+
positionals_raw.append((i, tok))
|
|
138
|
+
i += 1
|
|
139
|
+
|
|
140
|
+
self._match_positionals(positionals_raw, namespace, extras)
|
|
141
|
+
self._check_required(namespace)
|
|
142
|
+
return namespace, self._order_extras(extras)
|
|
143
|
+
|
|
144
|
+
# -- option scanning ---------------------------------------------------- #
|
|
145
|
+
def _is_option(self, tok):
|
|
146
|
+
if not tok.startswith(self.prefix) or tok == self.prefix:
|
|
147
|
+
return False
|
|
148
|
+
if tok.startswith(self.prefix * 2):
|
|
149
|
+
return True
|
|
150
|
+
return not tok[1].isdigit() # '-5' is a value, '-x' an option
|
|
151
|
+
|
|
152
|
+
def _consume_optional(self, argv, i, namespace, extras):
|
|
153
|
+
tok = argv[i]
|
|
154
|
+
if tok.startswith(self.prefix * 2):
|
|
155
|
+
name, eq, val = tok.partition("=")
|
|
156
|
+
arg = self._optionmap.get(name)
|
|
157
|
+
if arg is None:
|
|
158
|
+
extras.append((i, tok))
|
|
159
|
+
return i + 1
|
|
160
|
+
return self._apply_option(arg, name, val if eq else None,
|
|
161
|
+
argv, i + 1, namespace)
|
|
162
|
+
|
|
163
|
+
# short cluster, e.g. -v, -vvv, -abc, -o value, -ovalue
|
|
164
|
+
j = 1
|
|
165
|
+
while j < len(tok):
|
|
166
|
+
oname = self.prefix + tok[j]
|
|
167
|
+
arg = self._optionmap.get(oname)
|
|
168
|
+
if arg is None:
|
|
169
|
+
extras.append((i, tok))
|
|
170
|
+
return i + 1
|
|
171
|
+
if not arg.takes_value():
|
|
172
|
+
self._store_zero(arg, oname, namespace)
|
|
173
|
+
j += 1
|
|
174
|
+
continue
|
|
175
|
+
rest = tok[j + 1:]
|
|
176
|
+
return self._apply_option(arg, oname, rest or None,
|
|
177
|
+
argv, i + 1, namespace)
|
|
178
|
+
return i + 1
|
|
179
|
+
|
|
180
|
+
def _apply_option(self, arg, name, explicit, argv, i, namespace):
|
|
181
|
+
if not arg.takes_value():
|
|
182
|
+
if explicit is not None:
|
|
183
|
+
self.error(f"argument {name}: ignored explicit argument {explicit!r}")
|
|
184
|
+
self._store_zero(arg, name, namespace)
|
|
185
|
+
return i
|
|
186
|
+
|
|
187
|
+
lo, hi = arg.count_range()
|
|
188
|
+
values = []
|
|
189
|
+
if explicit is not None:
|
|
190
|
+
values.append(explicit)
|
|
191
|
+
while len(values) < hi and i < len(argv):
|
|
192
|
+
nxt = argv[i]
|
|
193
|
+
if nxt == self.prefix * 2 or self._is_option(nxt):
|
|
194
|
+
break
|
|
195
|
+
values.append(nxt)
|
|
196
|
+
i += 1
|
|
197
|
+
if len(values) < lo:
|
|
198
|
+
expected = f"at least {lo}" if hi == INF else str(lo)
|
|
199
|
+
self.error(f"argument {name}: expected {expected} argument(s)")
|
|
200
|
+
self._store_values(arg, values, namespace)
|
|
201
|
+
return i
|
|
202
|
+
|
|
203
|
+
# -- storing ------------------------------------------------------------ #
|
|
204
|
+
def _store_zero(self, arg, name, namespace):
|
|
205
|
+
if arg.action == "help":
|
|
206
|
+
self.print_help()
|
|
207
|
+
self.exit(0)
|
|
208
|
+
elif arg.action == "store_true":
|
|
209
|
+
setattr(namespace, arg.dest, True)
|
|
210
|
+
elif arg.action == "store_false":
|
|
211
|
+
setattr(namespace, arg.dest, False)
|
|
212
|
+
elif arg.action == "store_const":
|
|
213
|
+
setattr(namespace, arg.dest, arg.const)
|
|
214
|
+
elif arg.action == "count":
|
|
215
|
+
current = getattr(namespace, arg.dest, None) or 0
|
|
216
|
+
setattr(namespace, arg.dest, current + 1)
|
|
217
|
+
self._seen.add(arg.dest)
|
|
218
|
+
|
|
219
|
+
def _store_values(self, arg, raw_values, namespace):
|
|
220
|
+
converted = [arg.convert_one(v, self) for v in raw_values]
|
|
221
|
+
if arg.nargs is None:
|
|
222
|
+
value = converted[0]
|
|
223
|
+
elif arg.nargs == "?":
|
|
224
|
+
if converted:
|
|
225
|
+
value = converted[0]
|
|
226
|
+
else:
|
|
227
|
+
value = arg.const if arg.option_strings else arg.default
|
|
228
|
+
else: # int, '*', '+'
|
|
229
|
+
value = converted
|
|
230
|
+
|
|
231
|
+
if arg.action == "append":
|
|
232
|
+
current = getattr(namespace, arg.dest, None)
|
|
233
|
+
lst = list(current) if current else []
|
|
234
|
+
lst.append(value)
|
|
235
|
+
setattr(namespace, arg.dest, lst)
|
|
236
|
+
else:
|
|
237
|
+
setattr(namespace, arg.dest, value)
|
|
238
|
+
self._seen.add(arg.dest)
|
|
239
|
+
|
|
240
|
+
def _match_positionals(self, pairs, namespace, extras):
|
|
241
|
+
positionals = [a for a in self._arguments if a.is_positional]
|
|
242
|
+
toks = [t for _, t in pairs]
|
|
243
|
+
idx, n = 0, len(toks)
|
|
244
|
+
for k, arg in enumerate(positionals):
|
|
245
|
+
lo, hi = arg.count_range()
|
|
246
|
+
remaining_min = sum(p.count_range()[0] for p in positionals[k + 1:])
|
|
247
|
+
avail = n - idx - remaining_min
|
|
248
|
+
if avail < lo:
|
|
249
|
+
self.error("the following arguments are required: " + arg.metavar)
|
|
250
|
+
take = int(min(hi, avail))
|
|
251
|
+
chunk = toks[idx:idx + take]
|
|
252
|
+
idx += take
|
|
253
|
+
self._store_values(arg, chunk, namespace)
|
|
254
|
+
if idx < n:
|
|
255
|
+
extras.extend(pairs[idx:])
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
def _order_extras(extras):
|
|
259
|
+
return [tok for _, tok in sorted(extras, key=lambda pair: pair[0])]
|
|
260
|
+
|
|
261
|
+
def _check_required(self, namespace):
|
|
262
|
+
missing = [a.option_strings[0] for a in self._arguments
|
|
263
|
+
if a.option_strings and a.required and a.dest not in self._seen]
|
|
264
|
+
if missing:
|
|
265
|
+
self.error("the following arguments are required: " + ", ".join(missing))
|
|
266
|
+
|
|
267
|
+
# -- subcommands -------------------------------------------------------- #
|
|
268
|
+
def _parse_with_subparsers(self, argv, namespace, extras):
|
|
269
|
+
sp = self._subparsers
|
|
270
|
+
i, after_ddash = 0, False
|
|
271
|
+
while i < len(argv):
|
|
272
|
+
tok = argv[i]
|
|
273
|
+
if not after_ddash and tok == self.prefix * 2:
|
|
274
|
+
after_ddash = True
|
|
275
|
+
i += 1
|
|
276
|
+
elif not after_ddash and self._is_option(tok):
|
|
277
|
+
i = self._consume_optional(argv, i, namespace, extras)
|
|
278
|
+
else:
|
|
279
|
+
break
|
|
280
|
+
self._check_required(namespace)
|
|
281
|
+
|
|
282
|
+
if i >= len(argv):
|
|
283
|
+
if sp.required:
|
|
284
|
+
self.error("the following arguments are required: "
|
|
285
|
+
+ (sp.metavar or sp.dest or "command"))
|
|
286
|
+
return namespace, self._order_extras(extras)
|
|
287
|
+
|
|
288
|
+
command = argv[i]
|
|
289
|
+
if command not in sp.choices:
|
|
290
|
+
choices = ", ".join(repr(c) for c in sp.choices)
|
|
291
|
+
self.error(f"argument command: invalid choice: {command!r} "
|
|
292
|
+
f"(choose from {choices})")
|
|
293
|
+
if sp.dest:
|
|
294
|
+
setattr(namespace, sp.dest, command)
|
|
295
|
+
_, child_extras = sp.choices[command].parse_known_args(argv[i + 1:], namespace)
|
|
296
|
+
return namespace, self._order_extras(extras) + child_extras
|
|
297
|
+
|
|
298
|
+
# -- help / errors ------------------------------------------------------ #
|
|
299
|
+
def format_usage(self):
|
|
300
|
+
return _formatter.format_usage(self)
|
|
301
|
+
|
|
302
|
+
def format_help(self):
|
|
303
|
+
return _formatter.format_help(self)
|
|
304
|
+
|
|
305
|
+
def print_usage(self, file=None):
|
|
306
|
+
(file or sys.stdout).write(self.format_usage())
|
|
307
|
+
|
|
308
|
+
def print_help(self, file=None):
|
|
309
|
+
(file or sys.stdout).write(self.format_help())
|
|
310
|
+
|
|
311
|
+
def error(self, message):
|
|
312
|
+
self.print_usage(sys.stderr)
|
|
313
|
+
self.exit(2, f"{self.prog}: error: {message}\n")
|
|
314
|
+
|
|
315
|
+
def exit(self, status=0, message=None):
|
|
316
|
+
if message:
|
|
317
|
+
sys.stderr.write(message)
|
|
318
|
+
raise SystemExit(status)
|
|
319
|
+
|
|
320
|
+
def __repr__(self):
|
|
321
|
+
return f"ArgumentParser(prog={self.prog!r})"
|
pythoniota/enum.py
CHANGED
|
@@ -113,20 +113,32 @@ class IotaNamespace(dict): # type: ignore[type-arg]
|
|
|
113
113
|
def __init__(self) -> None:
|
|
114
114
|
super().__init__()
|
|
115
115
|
self._counter = 0
|
|
116
|
+
self._step = 1
|
|
116
117
|
self._member_names: list[str] = []
|
|
117
118
|
|
|
119
|
+
def _skip(self, n: int = 1) -> None:
|
|
120
|
+
"""Advance the iota counter by `n` steps without defining a member (Go's blank `_`)."""
|
|
121
|
+
self._counter += n * self._step
|
|
122
|
+
return None
|
|
123
|
+
|
|
118
124
|
def __getitem__(self, key: str) -> Any:
|
|
119
125
|
if key == "iota":
|
|
120
126
|
val = _IotaCounter(self._counter)
|
|
121
|
-
self._counter +=
|
|
127
|
+
self._counter += self._step
|
|
122
128
|
return val
|
|
129
|
+
if key == "skip":
|
|
130
|
+
return self._skip
|
|
123
131
|
return super().__getitem__(key)
|
|
124
132
|
|
|
125
133
|
def __setitem__(self, key: str, value: Any) -> None:
|
|
134
|
+
if key == "_iota_start_":
|
|
135
|
+
self._counter = int(value)
|
|
136
|
+
elif key == "_iota_step_":
|
|
137
|
+
self._step = int(value)
|
|
126
138
|
super().__setitem__(key, value)
|
|
127
139
|
if (
|
|
128
140
|
not key.startswith("_")
|
|
129
|
-
and key
|
|
141
|
+
and key not in ("iota", "skip")
|
|
130
142
|
and isinstance(value, (int, float, str, _IotaCounter))
|
|
131
143
|
):
|
|
132
144
|
if key not in self._member_names:
|
|
@@ -182,7 +194,7 @@ class IotaEnumMeta(type):
|
|
|
182
194
|
members[mname] = flag
|
|
183
195
|
type.__setattr__(cls, mname, flag)
|
|
184
196
|
elif is_ordered:
|
|
185
|
-
for order, (mname, mval) in
|
|
197
|
+
for order, (mname, mval) in enumerate(members.items()):
|
|
186
198
|
proxy = _EnumMemberProxy(mname, mval, cls, order)
|
|
187
199
|
members[mname] = proxy
|
|
188
200
|
type.__setattr__(cls, mname, proxy)
|
|
@@ -388,6 +400,14 @@ class IotaEnum(metaclass=IotaEnumMeta):
|
|
|
388
400
|
return alias
|
|
389
401
|
return None
|
|
390
402
|
|
|
403
|
+
@classmethod
|
|
404
|
+
def has(cls, name: str) -> bool:
|
|
405
|
+
return name in cls._members_ # type: ignore[attr-defined]
|
|
406
|
+
|
|
407
|
+
@classmethod
|
|
408
|
+
def get(cls, name: str, default: Any = None) -> Any:
|
|
409
|
+
return cls._members_.get(name, default) # type: ignore[attr-defined]
|
|
410
|
+
|
|
391
411
|
@classmethod
|
|
392
412
|
def alias(cls, alias_name: str, target_name: str) -> None:
|
|
393
413
|
if target_name not in cls._members_: # type: ignore[attr-defined]
|
|
@@ -397,13 +417,21 @@ class IotaEnum(metaclass=IotaEnumMeta):
|
|
|
397
417
|
|
|
398
418
|
@classmethod
|
|
399
419
|
def to_dict(cls) -> dict[str, Any]:
|
|
400
|
-
|
|
420
|
+
d = {}
|
|
421
|
+
for k, v in cls._members_.items(): # type: ignore[attr-defined]
|
|
422
|
+
d[k] = v._value if isinstance(v, _EnumMemberProxy) else v
|
|
423
|
+
return d
|
|
401
424
|
|
|
402
425
|
@classmethod
|
|
403
426
|
def to_json(cls, **kwargs: Any) -> str:
|
|
404
427
|
d = {}
|
|
405
428
|
for k, v in cls._members_.items(): # type: ignore[attr-defined]
|
|
406
|
-
|
|
429
|
+
if isinstance(v, _EnumMemberProxy):
|
|
430
|
+
d[k] = v._value
|
|
431
|
+
elif isinstance(v, BitFlag):
|
|
432
|
+
d[k] = int(v)
|
|
433
|
+
else:
|
|
434
|
+
d[k] = v
|
|
407
435
|
return json.dumps(d, **kwargs)
|
|
408
436
|
|
|
409
437
|
@classmethod
|
|
@@ -443,6 +471,14 @@ class IotaBitFlags(metaclass=IotaEnumMeta):
|
|
|
443
471
|
return name
|
|
444
472
|
return None
|
|
445
473
|
|
|
474
|
+
@classmethod
|
|
475
|
+
def has(cls, name: str) -> bool:
|
|
476
|
+
return name in cls._members_ # type: ignore[attr-defined]
|
|
477
|
+
|
|
478
|
+
@classmethod
|
|
479
|
+
def get(cls, name: str, default: Any = None) -> Any:
|
|
480
|
+
return cls._members_.get(name, default) # type: ignore[attr-defined]
|
|
481
|
+
|
|
446
482
|
@classmethod
|
|
447
483
|
def to_dict(cls) -> dict[str, int]:
|
|
448
484
|
return {k: int(v) for k, v in cls._members_.items()} # type: ignore[attr-defined]
|
|
@@ -536,5 +572,22 @@ def iota_enum(cls: type | None = None, *, ordered: bool = False) -> Any:
|
|
|
536
572
|
return _wrap
|
|
537
573
|
|
|
538
574
|
|
|
539
|
-
|
|
540
|
-
|
|
575
|
+
def _member_value(val: Any) -> Any:
|
|
576
|
+
if isinstance(val, _EnumMemberProxy):
|
|
577
|
+
return val._value
|
|
578
|
+
if isinstance(val, BitFlag):
|
|
579
|
+
return int(val)
|
|
580
|
+
return val
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def unique(cls: type) -> type:
|
|
584
|
+
"""Class decorator that rejects an enum with duplicate member values."""
|
|
585
|
+
seen: dict[Any, str] = {}
|
|
586
|
+
for name, val in cls._members_.items(): # type: ignore[attr-defined]
|
|
587
|
+
v = _member_value(val)
|
|
588
|
+
if v in seen:
|
|
589
|
+
raise ValueError(
|
|
590
|
+
f"duplicate value {v!r} in {cls.__name__}: {seen[v]} and {name}"
|
|
591
|
+
)
|
|
592
|
+
seen[v] = name
|
|
593
|
+
return cls
|