omlish 0.0.0.dev117__py3-none-any.whl → 0.0.0.dev118__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.
- omlish/__about__.py +2 -2
- omlish/collections/hasheq.py +0 -10
- omlish/fnpairs.py +1 -10
- omlish/formats/json/__init__.py +5 -0
- omlish/formats/json/literals.py +179 -0
- omlish/formats/json/render.py +2 -3
- omlish/formats/json/stream/render.py +1 -1
- omlish/formats/json/types.py +6 -0
- omlish/lang/classes/abstract.py +37 -14
- omlish/lang/imports.py +1 -1
- omlish/lang/maybes.py +10 -12
- omlish/specs/jmespath/ast.py +199 -60
- omlish/specs/jmespath/cli.py +43 -29
- omlish/specs/jmespath/functions.py +397 -274
- omlish/specs/jmespath/lexer.py +2 -2
- omlish/specs/jmespath/parser.py +169 -133
- omlish/specs/jmespath/scope.py +15 -11
- omlish/specs/jmespath/visitor.py +211 -137
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev118.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev118.dist-info}/RECORD +24 -22
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev118.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev118.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev118.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev118.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/collections/hasheq.py
CHANGED
@@ -27,21 +27,11 @@ class HashEq(lang.Abstract, ta.Generic[K]):
|
|
27
27
|
raise NotImplementedError
|
28
28
|
|
29
29
|
|
30
|
-
@lang.unabstract_class([
|
31
|
-
('hash', '_hash'),
|
32
|
-
('eq', '_eq'),
|
33
|
-
])
|
34
30
|
@dc.dataclass(frozen=True)
|
35
31
|
class HashEq_(ta.Generic[K]): # noqa
|
36
32
|
hash: ta.Callable[[K], int]
|
37
33
|
eq: ta.Callable[[K, K], bool]
|
38
34
|
|
39
|
-
def _hash(self, k: K) -> int:
|
40
|
-
return self.hash(k)
|
41
|
-
|
42
|
-
def _eq(self, l: K, r: K) -> bool:
|
43
|
-
return self.eq(l, r)
|
44
|
-
|
45
35
|
|
46
36
|
class hash_eq(HashEq[K], lang.NotInstantiable, lang.Final): # noqa
|
47
37
|
"""Workaround for PEP 695 support."""
|
omlish/fnpairs.py
CHANGED
@@ -94,21 +94,12 @@ class FnPair(ta.Generic[F, T], abc.ABC):
|
|
94
94
|
##
|
95
95
|
|
96
96
|
|
97
|
-
@lang.unabstract_class([
|
98
|
-
('forward', '_forward'),
|
99
|
-
('backward', 'backward'),
|
100
|
-
])
|
97
|
+
@lang.unabstract_class(['forward', 'backward'])
|
101
98
|
@dc.dataclass(frozen=True)
|
102
99
|
class Simple(FnPair[F, T]):
|
103
100
|
forward: ta.Callable[[F], T] # type: ignore
|
104
101
|
backward: ta.Callable[[T], F] # type: ignore
|
105
102
|
|
106
|
-
def _forward(self, f: F) -> T:
|
107
|
-
return self.forward(f)
|
108
|
-
|
109
|
-
def _backward(self, t: T) -> F:
|
110
|
-
return self.backward(t)
|
111
|
-
|
112
103
|
|
113
104
|
of = Simple
|
114
105
|
|
omlish/formats/json/__init__.py
CHANGED
@@ -0,0 +1,179 @@
|
|
1
|
+
# MIT License
|
2
|
+
# ===========
|
3
|
+
#
|
4
|
+
# Copyright (c) 2006 Bob Ippolito
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
7
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
8
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
9
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
12
|
+
# Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
15
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
16
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
17
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
18
|
+
# https://github.com/simplejson/simplejson/blob/6932004966ab70ef47250a2b3152acd8c904e6b5/simplejson/scanner.py
|
19
|
+
import json
|
20
|
+
import re
|
21
|
+
import sys
|
22
|
+
import typing as ta
|
23
|
+
|
24
|
+
|
25
|
+
##
|
26
|
+
|
27
|
+
|
28
|
+
_FOUR_DIGIT_HEX_PAT = re.compile(r'^[0-9a-fA-F]{4}$')
|
29
|
+
|
30
|
+
|
31
|
+
def _scan_four_digit_hex(
|
32
|
+
s: str,
|
33
|
+
end: int,
|
34
|
+
):
|
35
|
+
"""Scan a four digit hex number from s[end:end + 4]"""
|
36
|
+
|
37
|
+
msg = 'Invalid \\uXXXX escape sequence'
|
38
|
+
esc = s[end: end + 4]
|
39
|
+
if not _FOUR_DIGIT_HEX_PAT.match(esc):
|
40
|
+
raise json.JSONDecodeError(msg, s, end - 2)
|
41
|
+
|
42
|
+
try:
|
43
|
+
return int(esc, 16), end + 4
|
44
|
+
except ValueError:
|
45
|
+
raise json.JSONDecodeError(msg, s, end - 2) from None
|
46
|
+
|
47
|
+
|
48
|
+
_STRING_CHUNK_PAT = re.compile(r'(.*?)(["\\\x00-\x1f])', re.VERBOSE | re.MULTILINE | re.DOTALL)
|
49
|
+
|
50
|
+
_BACKSLASH_MAP = {
|
51
|
+
'"': '"',
|
52
|
+
'\\': '\\',
|
53
|
+
'/': '/',
|
54
|
+
'b': '\b',
|
55
|
+
'f': '\f',
|
56
|
+
'n': '\n',
|
57
|
+
'r': '\r',
|
58
|
+
't': '\t',
|
59
|
+
}
|
60
|
+
|
61
|
+
|
62
|
+
def parse_string(
|
63
|
+
s: str,
|
64
|
+
idx: int = 0,
|
65
|
+
*,
|
66
|
+
strict: bool = True,
|
67
|
+
) -> tuple[str, int]:
|
68
|
+
"""
|
69
|
+
Scan the string s for a JSON string. Idx is the index of the quote that starts the JSON string. Unescapes all valid
|
70
|
+
JSON string escape sequences and raises ValueError on attempt to decode an invalid string. If strict is False then
|
71
|
+
literal control characters are allowed in the string.
|
72
|
+
|
73
|
+
Returns a tuple of the decoded string and the index of the character in s after the end quote.
|
74
|
+
"""
|
75
|
+
|
76
|
+
if s[idx] != '"':
|
77
|
+
raise json.JSONDecodeError('No opening string quotes at', s, idx)
|
78
|
+
|
79
|
+
chunks: list[str] = []
|
80
|
+
end = idx + 1
|
81
|
+
while True:
|
82
|
+
chunk = _STRING_CHUNK_PAT.match(s, end)
|
83
|
+
if chunk is None:
|
84
|
+
raise json.JSONDecodeError('Unterminated string starting at', s, idx)
|
85
|
+
|
86
|
+
prev_end = end
|
87
|
+
end = chunk.end()
|
88
|
+
content, terminator = chunk.groups()
|
89
|
+
# Content is contains zero or more unescaped string characters
|
90
|
+
if content:
|
91
|
+
chunks.append(content)
|
92
|
+
|
93
|
+
# Terminator is the end of string, a literal control character, or a backslash denoting that an escape sequence
|
94
|
+
# follows
|
95
|
+
if terminator == '"':
|
96
|
+
break
|
97
|
+
elif terminator != '\\':
|
98
|
+
if strict:
|
99
|
+
msg = 'Invalid control character %r at'
|
100
|
+
raise json.JSONDecodeError(msg, s, prev_end)
|
101
|
+
else:
|
102
|
+
chunks.append(terminator)
|
103
|
+
continue
|
104
|
+
|
105
|
+
try:
|
106
|
+
esc = s[end]
|
107
|
+
except IndexError:
|
108
|
+
raise json.JSONDecodeError('Unterminated string starting at', s, idx) from None
|
109
|
+
|
110
|
+
# If not a unicode escape sequence, must be in the lookup table
|
111
|
+
if esc != 'u':
|
112
|
+
try:
|
113
|
+
char = _BACKSLASH_MAP[esc]
|
114
|
+
except KeyError:
|
115
|
+
msg = 'Invalid \\X escape sequence %r'
|
116
|
+
raise json.JSONDecodeError(msg, s, end) from None
|
117
|
+
end += 1
|
118
|
+
|
119
|
+
else:
|
120
|
+
# Unicode escape sequence
|
121
|
+
uni, end = _scan_four_digit_hex(s, end + 1)
|
122
|
+
|
123
|
+
# Check for surrogate pair on UCS-4 systems Note that this will join high/low surrogate pairs but will also
|
124
|
+
# pass unpaired surrogates through
|
125
|
+
if (
|
126
|
+
sys.maxunicode > 65535 and
|
127
|
+
uni & 0xFC00 == 0xD800 and
|
128
|
+
s[end: end + 2] == '\\u'
|
129
|
+
):
|
130
|
+
uni2, end2 = _scan_four_digit_hex(s, end + 2)
|
131
|
+
if uni2 & 0xFC00 == 0xDC00:
|
132
|
+
uni = 0x10000 + (((uni - 0xD800) << 10) | (uni2 - 0xDC00))
|
133
|
+
end = end2
|
134
|
+
|
135
|
+
char = chr(uni)
|
136
|
+
|
137
|
+
# Append the unescaped character
|
138
|
+
chunks.append(char)
|
139
|
+
|
140
|
+
return ''.join(chunks), end
|
141
|
+
|
142
|
+
|
143
|
+
def try_parse_string(
|
144
|
+
s: str,
|
145
|
+
idx: int = 0,
|
146
|
+
*,
|
147
|
+
strict: bool = True,
|
148
|
+
) -> tuple[str, int] | None:
|
149
|
+
if not s or s[idx] != '"':
|
150
|
+
return None
|
151
|
+
|
152
|
+
return parse_string(s, idx, strict=strict)
|
153
|
+
|
154
|
+
|
155
|
+
##
|
156
|
+
|
157
|
+
|
158
|
+
_NUMBER_PAT = re.compile(
|
159
|
+
r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
|
160
|
+
(re.VERBOSE | re.MULTILINE | re.DOTALL),
|
161
|
+
)
|
162
|
+
|
163
|
+
|
164
|
+
def try_parse_number(
|
165
|
+
s: str,
|
166
|
+
idx: int = 0,
|
167
|
+
*,
|
168
|
+
parse_float: ta.Callable[[str], float] = float,
|
169
|
+
parse_int: ta.Callable[[str], int] = int,
|
170
|
+
) -> tuple[int | float, int] | None:
|
171
|
+
if (m := _NUMBER_PAT.match(s, idx)) is None:
|
172
|
+
return None
|
173
|
+
|
174
|
+
integer, frac, exp = m.groups()
|
175
|
+
if frac or exp:
|
176
|
+
res = parse_float(integer + (frac or '') + (exp or ''))
|
177
|
+
else:
|
178
|
+
res = parse_int(integer)
|
179
|
+
return res, m.end()
|
omlish/formats/json/render.py
CHANGED
@@ -6,12 +6,11 @@ import typing as ta
|
|
6
6
|
|
7
7
|
from ... import lang
|
8
8
|
from . import consts
|
9
|
+
from .types import SCALAR_TYPES
|
10
|
+
from .types import Scalar
|
9
11
|
|
10
12
|
|
11
13
|
I = ta.TypeVar('I')
|
12
|
-
Scalar: ta.TypeAlias = bool | int | float | str | None
|
13
|
-
|
14
|
-
SCALAR_TYPES: tuple[type, ...] = (bool, int, float, str, type(None))
|
15
14
|
|
16
15
|
MULTILINE_SEPARATORS = consts.Separators(',', ': ')
|
17
16
|
|
omlish/lang/classes/abstract.py
CHANGED
@@ -7,6 +7,10 @@ T = ta.TypeVar('T')
|
|
7
7
|
|
8
8
|
_DISABLE_CHECKS = False
|
9
9
|
|
10
|
+
_ABSTRACT_METHODS_ATTR = '__abstractmethods__'
|
11
|
+
_IS_ABSTRACT_METHOD_ATTR = '__isabstractmethod__'
|
12
|
+
_FORCE_ABSTRACT_ATTR = '__forceabstract__'
|
13
|
+
|
10
14
|
|
11
15
|
def make_abstract(obj: T) -> T:
|
12
16
|
if callable(obj):
|
@@ -29,13 +33,13 @@ class Abstract(abc.ABC): # noqa
|
|
29
33
|
def __forceabstract__(self):
|
30
34
|
raise TypeError
|
31
35
|
|
32
|
-
setattr(__forceabstract__,
|
36
|
+
setattr(__forceabstract__, _IS_ABSTRACT_METHOD_ATTR, True)
|
33
37
|
|
34
38
|
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
35
39
|
if Abstract in cls.__bases__:
|
36
|
-
cls
|
40
|
+
setattr(cls, _FORCE_ABSTRACT_ATTR, getattr(Abstract, _FORCE_ABSTRACT_ATTR))
|
37
41
|
else:
|
38
|
-
cls
|
42
|
+
setattr(cls, _FORCE_ABSTRACT_ATTR, False)
|
39
43
|
|
40
44
|
super().__init_subclass__(**kwargs)
|
41
45
|
|
@@ -43,7 +47,7 @@ class Abstract(abc.ABC): # noqa
|
|
43
47
|
ams = {a for a, o in cls.__dict__.items() if is_abstract_method(o)}
|
44
48
|
seen = set(cls.__dict__)
|
45
49
|
for b in cls.__bases__:
|
46
|
-
ams.update(set(getattr(b,
|
50
|
+
ams.update(set(getattr(b, _ABSTRACT_METHODS_ATTR, [])) - seen)
|
47
51
|
seen.update(dir(b))
|
48
52
|
if ams:
|
49
53
|
raise TypeError(
|
@@ -53,18 +57,18 @@ class Abstract(abc.ABC): # noqa
|
|
53
57
|
|
54
58
|
|
55
59
|
def is_abstract_method(obj: ta.Any) -> bool:
|
56
|
-
return bool(getattr(obj,
|
60
|
+
return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
|
57
61
|
|
58
62
|
|
59
63
|
def is_abstract_class(obj: ta.Any) -> bool:
|
60
|
-
if bool(getattr(obj,
|
64
|
+
if bool(getattr(obj, _ABSTRACT_METHODS_ATTR, [])):
|
61
65
|
return True
|
62
66
|
if isinstance(obj, type):
|
63
67
|
if Abstract in obj.__bases__:
|
64
68
|
return True
|
65
69
|
if (
|
66
70
|
Abstract in obj.__mro__
|
67
|
-
and getattr(obj.__dict__.get(
|
71
|
+
and getattr(obj.__dict__.get(_FORCE_ABSTRACT_ATTR, None), _IS_ABSTRACT_METHOD_ATTR, False)
|
68
72
|
):
|
69
73
|
return True
|
70
74
|
return False
|
@@ -75,13 +79,32 @@ def is_abstract(obj: ta.Any) -> bool:
|
|
75
79
|
|
76
80
|
|
77
81
|
def unabstract_class(
|
78
|
-
|
79
|
-
) -> ta.Callable[[type[T]], type[T]]:
|
82
|
+
members: ta.Iterable[str | tuple[str, ta.Any]],
|
83
|
+
): # -> ta.Callable[[type[T]], type[T]]:
|
80
84
|
def inner(cls):
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
85
|
+
if isinstance(members, str):
|
86
|
+
raise TypeError(members)
|
87
|
+
|
88
|
+
ams = getattr(cls, _ABSTRACT_METHODS_ATTR)
|
89
|
+
|
90
|
+
names: set[str] = set()
|
91
|
+
for m in members:
|
92
|
+
if isinstance(m, str):
|
93
|
+
if m not in ams:
|
94
|
+
raise NameError(m)
|
95
|
+
getattr(cls, m)
|
96
|
+
names.add(m)
|
97
|
+
|
98
|
+
elif isinstance(m, tuple):
|
99
|
+
name, impl = m
|
100
|
+
if name not in ams:
|
101
|
+
raise NameError(name)
|
102
|
+
if isinstance(impl, str):
|
103
|
+
impl = getattr(cls, impl)
|
104
|
+
setattr(cls, name, impl)
|
105
|
+
names.add(name)
|
106
|
+
|
107
|
+
setattr(cls, _ABSTRACT_METHODS_ATTR, ams - names)
|
86
108
|
return cls
|
109
|
+
|
87
110
|
return inner
|
omlish/lang/imports.py
CHANGED
@@ -312,7 +312,7 @@ class _ProxyInit:
|
|
312
312
|
try:
|
313
313
|
mod = self._mods_by_pkgs[pkg]
|
314
314
|
except KeyError:
|
315
|
-
mod = importlib.import_module(pkg, package=self.
|
315
|
+
mod = importlib.import_module(pkg, package=self._name_package.package)
|
316
316
|
|
317
317
|
val = getattr(mod, attr)
|
318
318
|
|
omlish/lang/maybes.py
CHANGED
@@ -50,15 +50,15 @@ class Maybe(abc.ABC, ta.Generic[T]):
|
|
50
50
|
raise NotImplementedError
|
51
51
|
|
52
52
|
@abc.abstractmethod
|
53
|
-
def or_else(self, other: T) ->
|
53
|
+
def or_else(self, other: T) -> T:
|
54
54
|
raise NotImplementedError
|
55
55
|
|
56
56
|
@abc.abstractmethod
|
57
|
-
def or_else_get(self, supplier: ta.Callable[[], T]) ->
|
57
|
+
def or_else_get(self, supplier: ta.Callable[[], T]) -> T:
|
58
58
|
raise NotImplementedError
|
59
59
|
|
60
60
|
@abc.abstractmethod
|
61
|
-
def or_else_raise(self, exception_supplier: ta.Callable[[], Exception]) ->
|
61
|
+
def or_else_raise(self, exception_supplier: ta.Callable[[], Exception]) -> T:
|
62
62
|
raise NotImplementedError
|
63
63
|
|
64
64
|
|
@@ -98,9 +98,7 @@ class _Maybe(Maybe[T], tuple):
|
|
98
98
|
|
99
99
|
def map(self, mapper: ta.Callable[[T], U]) -> Maybe[U]:
|
100
100
|
if self:
|
101
|
-
|
102
|
-
if value is not None:
|
103
|
-
return just(value)
|
101
|
+
return just(mapper(self[0]))
|
104
102
|
return _empty # noqa
|
105
103
|
|
106
104
|
def flat_map(self, mapper: ta.Callable[[T], Maybe[U]]) -> Maybe[U]:
|
@@ -111,15 +109,15 @@ class _Maybe(Maybe[T], tuple):
|
|
111
109
|
return value
|
112
110
|
return _empty # noqa
|
113
111
|
|
114
|
-
def or_else(self, other: T) ->
|
115
|
-
return self if self else
|
112
|
+
def or_else(self, other: T) -> T:
|
113
|
+
return self.must() if self else other
|
116
114
|
|
117
|
-
def or_else_get(self, supplier: ta.Callable[[], T]) ->
|
118
|
-
return self if self else
|
115
|
+
def or_else_get(self, supplier: ta.Callable[[], T]) -> T:
|
116
|
+
return self.must() if self else supplier()
|
119
117
|
|
120
|
-
def or_else_raise(self, exception_supplier: ta.Callable[[], Exception]) ->
|
118
|
+
def or_else_raise(self, exception_supplier: ta.Callable[[], Exception]) -> T:
|
121
119
|
if self:
|
122
|
-
return self
|
120
|
+
return self.must()
|
123
121
|
raise exception_supplier()
|
124
122
|
|
125
123
|
|