omlish 0.0.0.dev104__py3-none-any.whl → 0.0.0.dev105__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- omlish/__about__.py +2 -2
- omlish/fnpipes.py +20 -2
- omlish/formats/json/__init__.py +2 -0
- omlish/formats/json/cli/cli.py +144 -108
- omlish/formats/json/cli/parsing.py +82 -0
- omlish/formats/json/cli/processing.py +44 -0
- omlish/formats/json/cli/rendering.py +92 -0
- omlish/formats/json/consts.py +11 -1
- omlish/formats/json/render.py +68 -26
- omlish/formats/json/stream/build.py +2 -2
- omlish/formats/json/stream/render.py +32 -29
- omlish/io/trampoline.py +0 -4
- omlish/specs/jmespath/ast.py +35 -30
- omlish/specs/jmespath/exceptions.py +7 -4
- omlish/specs/jmespath/functions.py +1 -0
- omlish/specs/jmespath/lexer.py +31 -20
- omlish/specs/jmespath/parser.py +98 -93
- omlish/specs/jmespath/scope.py +2 -0
- omlish/specs/jmespath/visitor.py +13 -8
- omlish/text/random.py +7 -0
- {omlish-0.0.0.dev104.dist-info → omlish-0.0.0.dev105.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev104.dist-info → omlish-0.0.0.dev105.dist-info}/RECORD +27 -23
- /omlish/{collections/_io_abc.py → io/_abc.py} +0 -0
- {omlish-0.0.0.dev104.dist-info → omlish-0.0.0.dev105.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev104.dist-info → omlish-0.0.0.dev105.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev104.dist-info → omlish-0.0.0.dev105.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev104.dist-info → omlish-0.0.0.dev105.dist-info}/top_level.txt +0 -0
omlish/formats/json/render.py
CHANGED
@@ -5,13 +5,18 @@ import json
|
|
5
5
|
import typing as ta
|
6
6
|
|
7
7
|
from ... import lang
|
8
|
+
from . import consts
|
8
9
|
|
9
10
|
|
10
11
|
I = ta.TypeVar('I')
|
12
|
+
Scalar: ta.TypeAlias = bool | int | float | str | None
|
11
13
|
|
14
|
+
SCALAR_TYPES: tuple[type, ...] = (bool, int, float, str, type(None))
|
12
15
|
|
13
|
-
|
14
|
-
|
16
|
+
MULTILINE_SEPARATORS = consts.Separators(',', ': ')
|
17
|
+
|
18
|
+
|
19
|
+
##
|
15
20
|
|
16
21
|
|
17
22
|
class AbstractJsonRenderer(lang.Abstract, ta.Generic[I]):
|
@@ -21,33 +26,33 @@ class AbstractJsonRenderer(lang.Abstract, ta.Generic[I]):
|
|
21
26
|
|
22
27
|
def __init__(
|
23
28
|
self,
|
24
|
-
out: JsonRendererOut,
|
25
|
-
*,
|
26
29
|
indent: int | str | None = None,
|
27
30
|
separators: tuple[str, str] | None = None,
|
28
31
|
sort_keys: bool = False,
|
29
32
|
style: ta.Callable[[ta.Any, State], tuple[str, str]] | None = None,
|
33
|
+
ensure_ascii: bool = True,
|
30
34
|
) -> None:
|
31
35
|
super().__init__()
|
32
36
|
|
33
|
-
self._out = out
|
34
37
|
self._sort_keys = sort_keys
|
35
38
|
self._style = style
|
39
|
+
self._ensure_ascii = ensure_ascii
|
36
40
|
|
37
41
|
if isinstance(indent, (str, int)):
|
38
42
|
self._indent = (' ' * indent) if isinstance(indent, int) else indent
|
39
43
|
self._endl = '\n'
|
40
44
|
if separators is None:
|
41
|
-
separators =
|
45
|
+
separators = MULTILINE_SEPARATORS
|
42
46
|
elif indent is None:
|
43
47
|
self._indent = self._endl = ''
|
44
48
|
if separators is None:
|
45
|
-
separators =
|
49
|
+
separators = consts.PRETTY_SEPARATORS
|
46
50
|
else:
|
47
51
|
raise TypeError(indent)
|
48
52
|
self._comma, self._colon = separators
|
49
53
|
|
50
54
|
self._level = 0
|
55
|
+
self._indent_cache: dict[int, str] = {}
|
51
56
|
|
52
57
|
_literals: ta.ClassVar[ta.Mapping[ta.Any, str]] = {
|
53
58
|
True: 'true',
|
@@ -55,28 +60,62 @@ class AbstractJsonRenderer(lang.Abstract, ta.Generic[I]):
|
|
55
60
|
None: 'null',
|
56
61
|
}
|
57
62
|
|
58
|
-
def
|
59
|
-
if
|
60
|
-
|
63
|
+
def _get_indent(self) -> str:
|
64
|
+
if not self._indent:
|
65
|
+
return ''
|
61
66
|
|
62
|
-
|
63
|
-
|
64
|
-
self._write(self._endl)
|
65
|
-
if self._level:
|
66
|
-
self._write(self._indent * self._level)
|
67
|
+
if not self._level:
|
68
|
+
return self._endl
|
67
69
|
|
70
|
+
try:
|
71
|
+
return self._indent_cache[self._level]
|
72
|
+
except KeyError:
|
73
|
+
pass
|
74
|
+
|
75
|
+
ret = self._endl + (self._indent * self._level)
|
76
|
+
self._indent_cache[self._level] = ret
|
77
|
+
return ret
|
78
|
+
|
79
|
+
def _format_scalar(self, o: Scalar) -> str:
|
80
|
+
if o is None or isinstance(o, bool):
|
81
|
+
return self._literals[o]
|
82
|
+
|
83
|
+
elif isinstance(o, (str, int, float)):
|
84
|
+
return json.dumps(o, ensure_ascii=self._ensure_ascii)
|
85
|
+
|
86
|
+
else:
|
87
|
+
raise TypeError(o)
|
88
|
+
|
89
|
+
@classmethod
|
68
90
|
@abc.abstractmethod
|
69
|
-
def
|
91
|
+
def render_str(cls, i: I, /, **kwargs: ta.Any) -> str:
|
70
92
|
raise NotImplementedError
|
71
93
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
94
|
+
|
95
|
+
##
|
96
|
+
|
97
|
+
|
98
|
+
class JsonRendererOut(ta.Protocol):
|
99
|
+
def write(self, s: str) -> ta.Any: ...
|
77
100
|
|
78
101
|
|
79
102
|
class JsonRenderer(AbstractJsonRenderer[ta.Any]):
|
103
|
+
def __init__(
|
104
|
+
self,
|
105
|
+
out: JsonRendererOut,
|
106
|
+
**kwargs: ta.Any,
|
107
|
+
) -> None:
|
108
|
+
super().__init__(**kwargs)
|
109
|
+
|
110
|
+
self._out = out
|
111
|
+
|
112
|
+
def _write(self, s: str) -> None:
|
113
|
+
if s:
|
114
|
+
self._out.write(s)
|
115
|
+
|
116
|
+
def _write_indent(self) -> None:
|
117
|
+
self._write(self._get_indent())
|
118
|
+
|
80
119
|
def _render(
|
81
120
|
self,
|
82
121
|
o: ta.Any,
|
@@ -88,11 +127,8 @@ class JsonRenderer(AbstractJsonRenderer[ta.Any]):
|
|
88
127
|
else:
|
89
128
|
post = None
|
90
129
|
|
91
|
-
if
|
92
|
-
self._write(self.
|
93
|
-
|
94
|
-
elif isinstance(o, (str, int, float)):
|
95
|
-
self._write(json.dumps(o))
|
130
|
+
if isinstance(o, SCALAR_TYPES):
|
131
|
+
self._write(self._format_scalar(o)) # type: ignore
|
96
132
|
|
97
133
|
elif isinstance(o, ta.Mapping):
|
98
134
|
self._write('{')
|
@@ -133,3 +169,9 @@ class JsonRenderer(AbstractJsonRenderer[ta.Any]):
|
|
133
169
|
|
134
170
|
def render(self, o: ta.Any) -> None:
|
135
171
|
self._render(o)
|
172
|
+
|
173
|
+
@classmethod
|
174
|
+
def render_str(cls, i: ta.Any, /, **kwargs: ta.Any) -> str:
|
175
|
+
out = io.StringIO()
|
176
|
+
cls(out, **kwargs).render(i)
|
177
|
+
return out.getvalue()
|
@@ -36,7 +36,7 @@ class JsonObjectBuilder:
|
|
36
36
|
if self._stack:
|
37
37
|
raise self.StateError
|
38
38
|
|
39
|
-
def _emit_value(self, v):
|
39
|
+
def _emit_value(self, v: ta.Any) -> ta.Iterable[ta.Any]:
|
40
40
|
if not (stk := self._stack):
|
41
41
|
return (v,)
|
42
42
|
|
@@ -60,7 +60,7 @@ class JsonObjectBuilder:
|
|
60
60
|
else:
|
61
61
|
raise self.StateError
|
62
62
|
|
63
|
-
def __call__(self, e: JsonStreamParserEvent) -> ta.Any:
|
63
|
+
def __call__(self, e: JsonStreamParserEvent) -> ta.Iterable[ta.Any]:
|
64
64
|
stk = self._stack
|
65
65
|
|
66
66
|
#
|
@@ -1,8 +1,8 @@
|
|
1
|
-
import
|
1
|
+
import io
|
2
2
|
import typing as ta
|
3
3
|
|
4
|
+
from ..render import SCALAR_TYPES
|
4
5
|
from ..render import AbstractJsonRenderer
|
5
|
-
from ..render import JsonRendererOut
|
6
6
|
from .parse import BeginArray
|
7
7
|
from .parse import BeginObject
|
8
8
|
from .parse import EndArray
|
@@ -17,7 +17,6 @@ from .parse import Key
|
|
17
17
|
class StreamJsonRenderer(AbstractJsonRenderer[ta.Iterable[JsonStreamParserEvent]]):
|
18
18
|
def __init__(
|
19
19
|
self,
|
20
|
-
out: JsonRendererOut,
|
21
20
|
*,
|
22
21
|
delimiter: str = '',
|
23
22
|
sort_keys: bool = False,
|
@@ -28,7 +27,7 @@ class StreamJsonRenderer(AbstractJsonRenderer[ta.Iterable[JsonStreamParserEvent]
|
|
28
27
|
|
29
28
|
self._delimiter = delimiter
|
30
29
|
|
31
|
-
super().__init__(
|
30
|
+
super().__init__(**kwargs)
|
32
31
|
|
33
32
|
self._stack: list[tuple[ta.Literal['OBJECT', 'ARRAY'], int]] = []
|
34
33
|
self._need_delimit = False
|
@@ -37,41 +36,38 @@ class StreamJsonRenderer(AbstractJsonRenderer[ta.Iterable[JsonStreamParserEvent]
|
|
37
36
|
self,
|
38
37
|
o: ta.Any,
|
39
38
|
state: AbstractJsonRenderer.State = AbstractJsonRenderer.State.VALUE,
|
40
|
-
) -> None:
|
39
|
+
) -> ta.Generator[str, None, None]:
|
41
40
|
if self._style is not None:
|
42
41
|
pre, post = self._style(o, state)
|
43
|
-
|
42
|
+
yield pre
|
44
43
|
else:
|
45
44
|
post = None
|
46
45
|
|
47
|
-
if
|
48
|
-
self.
|
49
|
-
|
50
|
-
elif isinstance(o, (str, int, float)):
|
51
|
-
self._write(json.dumps(o))
|
46
|
+
if isinstance(o, SCALAR_TYPES):
|
47
|
+
yield self._format_scalar(o) # type: ignore
|
52
48
|
|
53
49
|
else:
|
54
50
|
raise TypeError(o)
|
55
51
|
|
56
52
|
if post:
|
57
|
-
|
53
|
+
yield post
|
58
54
|
|
59
|
-
def _render(self, e: JsonStreamParserEvent) -> None:
|
55
|
+
def _render(self, e: JsonStreamParserEvent) -> ta.Generator[str, None, None]:
|
60
56
|
if self._need_delimit:
|
61
|
-
self.
|
57
|
+
yield self._delimiter
|
62
58
|
self._need_delimit = False
|
63
59
|
|
64
60
|
if e != EndArray and self._stack and (tt := self._stack[-1])[0] == 'ARRAY':
|
65
61
|
if tt[1]:
|
66
|
-
self.
|
67
|
-
self.
|
62
|
+
yield self._comma
|
63
|
+
yield self._get_indent()
|
68
64
|
|
69
65
|
self._stack[-1] = ('ARRAY', tt[1] + 1)
|
70
66
|
|
71
67
|
#
|
72
68
|
|
73
69
|
if e is None or isinstance(e, (str, int, float, bool)):
|
74
|
-
self._render_value(e)
|
70
|
+
yield from self._render_value(e)
|
75
71
|
if not self._stack:
|
76
72
|
self._need_delimit = True
|
77
73
|
|
@@ -79,7 +75,7 @@ class StreamJsonRenderer(AbstractJsonRenderer[ta.Iterable[JsonStreamParserEvent]
|
|
79
75
|
|
80
76
|
elif e is BeginObject:
|
81
77
|
self._stack.append(('OBJECT', 0))
|
82
|
-
|
78
|
+
yield '{'
|
83
79
|
self._level += 1
|
84
80
|
|
85
81
|
elif isinstance(e, Key):
|
@@ -87,10 +83,10 @@ class StreamJsonRenderer(AbstractJsonRenderer[ta.Iterable[JsonStreamParserEvent]
|
|
87
83
|
raise Exception
|
88
84
|
|
89
85
|
if tt[1]:
|
90
|
-
self.
|
91
|
-
self.
|
92
|
-
self._render_value(e.key, AbstractJsonRenderer.State.KEY)
|
93
|
-
self.
|
86
|
+
yield self._comma
|
87
|
+
yield self._get_indent()
|
88
|
+
yield from self._render_value(e.key, AbstractJsonRenderer.State.KEY)
|
89
|
+
yield self._colon
|
94
90
|
|
95
91
|
self._stack.append(('OBJECT', tt[1] + 1))
|
96
92
|
|
@@ -100,8 +96,8 @@ class StreamJsonRenderer(AbstractJsonRenderer[ta.Iterable[JsonStreamParserEvent]
|
|
100
96
|
|
101
97
|
self._level -= 1
|
102
98
|
if tt[1]:
|
103
|
-
self.
|
104
|
-
|
99
|
+
yield self._get_indent()
|
100
|
+
yield '}'
|
105
101
|
if not self._stack:
|
106
102
|
self._need_delimit = True
|
107
103
|
|
@@ -109,7 +105,7 @@ class StreamJsonRenderer(AbstractJsonRenderer[ta.Iterable[JsonStreamParserEvent]
|
|
109
105
|
|
110
106
|
elif e is BeginArray:
|
111
107
|
self._stack.append(('ARRAY', 0))
|
112
|
-
|
108
|
+
yield '['
|
113
109
|
self._level += 1
|
114
110
|
|
115
111
|
elif e is EndArray:
|
@@ -118,8 +114,8 @@ class StreamJsonRenderer(AbstractJsonRenderer[ta.Iterable[JsonStreamParserEvent]
|
|
118
114
|
|
119
115
|
self._level -= 1
|
120
116
|
if tt[1]:
|
121
|
-
self.
|
122
|
-
|
117
|
+
yield self._get_indent()
|
118
|
+
yield ']'
|
123
119
|
if not self._stack:
|
124
120
|
self._need_delimit = True
|
125
121
|
|
@@ -128,6 +124,13 @@ class StreamJsonRenderer(AbstractJsonRenderer[ta.Iterable[JsonStreamParserEvent]
|
|
128
124
|
else:
|
129
125
|
raise TypeError(e)
|
130
126
|
|
131
|
-
def render(self, events: ta.Iterable[JsonStreamParserEvent]) -> None:
|
127
|
+
def render(self, events: ta.Iterable[JsonStreamParserEvent]) -> ta.Generator[str, None, None]:
|
132
128
|
for e in events:
|
133
|
-
self._render(e)
|
129
|
+
yield from self._render(e)
|
130
|
+
|
131
|
+
@classmethod
|
132
|
+
def render_str(cls, i: ta.Iterable[JsonStreamParserEvent], /, **kwargs: ta.Any) -> str:
|
133
|
+
out = io.StringIO()
|
134
|
+
for s in cls(**kwargs).render(i):
|
135
|
+
out.write(s)
|
136
|
+
return out.getvalue()
|
omlish/io/trampoline.py
CHANGED
omlish/specs/jmespath/ast.py
CHANGED
@@ -1,114 +1,119 @@
|
|
1
|
-
|
2
|
-
# {'type': <node type>', children: [], 'value': ''}
|
1
|
+
import typing as ta
|
3
2
|
|
4
3
|
|
5
|
-
|
4
|
+
class Node(ta.TypedDict):
|
5
|
+
type: str
|
6
|
+
children: list['Node']
|
7
|
+
value: ta.NotRequired[ta.Any]
|
8
|
+
|
9
|
+
|
10
|
+
def arithmetic_unary(operator, expression) -> Node:
|
6
11
|
return {'type': 'arithmetic_unary', 'children': [expression], 'value': operator}
|
7
12
|
|
8
13
|
|
9
|
-
def arithmetic(operator, left, right):
|
14
|
+
def arithmetic(operator, left, right) -> Node:
|
10
15
|
return {'type': 'arithmetic', 'children': [left, right], 'value': operator}
|
11
16
|
|
12
17
|
|
13
|
-
def assign(name, expr):
|
18
|
+
def assign(name, expr) -> Node:
|
14
19
|
return {'type': 'assign', 'children': [expr], 'value': name}
|
15
20
|
|
16
21
|
|
17
|
-
def comparator(name, first, second):
|
22
|
+
def comparator(name, first, second) -> Node:
|
18
23
|
return {'type': 'comparator', 'children': [first, second], 'value': name}
|
19
24
|
|
20
25
|
|
21
|
-
def current_node():
|
26
|
+
def current_node() -> Node:
|
22
27
|
return {'type': 'current', 'children': []}
|
23
28
|
|
24
29
|
|
25
|
-
def root_node():
|
30
|
+
def root_node() -> Node:
|
26
31
|
return {'type': 'root', 'children': []}
|
27
32
|
|
28
33
|
|
29
|
-
def expref(expression):
|
34
|
+
def expref(expression) -> Node:
|
30
35
|
return {'type': 'expref', 'children': [expression]}
|
31
36
|
|
32
37
|
|
33
|
-
def function_expression(name, args):
|
38
|
+
def function_expression(name, args) -> Node:
|
34
39
|
return {'type': 'function_expression', 'children': args, 'value': name}
|
35
40
|
|
36
41
|
|
37
|
-
def field(name):
|
42
|
+
def field(name) -> Node:
|
38
43
|
return {'type': 'field', 'children': [], 'value': name}
|
39
44
|
|
40
45
|
|
41
|
-
def filter_projection(left, right, comparator):
|
46
|
+
def filter_projection(left, right, comparator) -> Node:
|
42
47
|
return {'type': 'filter_projection', 'children': [left, right, comparator]}
|
43
48
|
|
44
49
|
|
45
|
-
def flatten(node):
|
50
|
+
def flatten(node) -> Node:
|
46
51
|
return {'type': 'flatten', 'children': [node]}
|
47
52
|
|
48
53
|
|
49
|
-
def identity():
|
54
|
+
def identity() -> Node:
|
50
55
|
return {'type': 'identity', 'children': []}
|
51
56
|
|
52
57
|
|
53
|
-
def index(index):
|
58
|
+
def index(index) -> Node:
|
54
59
|
return {'type': 'index', 'value': index, 'children': []}
|
55
60
|
|
56
61
|
|
57
|
-
def index_expression(children):
|
62
|
+
def index_expression(children) -> Node:
|
58
63
|
return {'type': 'index_expression', 'children': children}
|
59
64
|
|
60
65
|
|
61
|
-
def key_val_pair(key_name, node):
|
66
|
+
def key_val_pair(key_name, node) -> Node:
|
62
67
|
return {'type': 'key_val_pair', 'children': [node], 'value': key_name}
|
63
68
|
|
64
69
|
|
65
|
-
def let_expression(bindings, expr):
|
70
|
+
def let_expression(bindings, expr) -> Node:
|
66
71
|
return {'type': 'let_expression', 'children': [*bindings, expr]}
|
67
72
|
|
68
73
|
|
69
|
-
def literal(literal_value):
|
74
|
+
def literal(literal_value) -> Node:
|
70
75
|
return {'type': 'literal', 'value': literal_value, 'children': []}
|
71
76
|
|
72
77
|
|
73
|
-
def multi_select_dict(nodes):
|
78
|
+
def multi_select_dict(nodes) -> Node:
|
74
79
|
return {'type': 'multi_select_dict', 'children': nodes}
|
75
80
|
|
76
81
|
|
77
|
-
def multi_select_list(nodes):
|
82
|
+
def multi_select_list(nodes) -> Node:
|
78
83
|
return {'type': 'multi_select_list', 'children': nodes}
|
79
84
|
|
80
85
|
|
81
|
-
def or_expression(left, right):
|
86
|
+
def or_expression(left, right) -> Node:
|
82
87
|
return {'type': 'or_expression', 'children': [left, right]}
|
83
88
|
|
84
89
|
|
85
|
-
def and_expression(left, right):
|
90
|
+
def and_expression(left, right) -> Node:
|
86
91
|
return {'type': 'and_expression', 'children': [left, right]}
|
87
92
|
|
88
93
|
|
89
|
-
def not_expression(expr):
|
94
|
+
def not_expression(expr) -> Node:
|
90
95
|
return {'type': 'not_expression', 'children': [expr]}
|
91
96
|
|
92
97
|
|
93
|
-
def pipe(left, right):
|
98
|
+
def pipe(left: Node, right: Node) -> Node:
|
94
99
|
return {'type': 'pipe', 'children': [left, right]}
|
95
100
|
|
96
101
|
|
97
|
-
def projection(left, right):
|
102
|
+
def projection(left: Node, right: Node) -> Node:
|
98
103
|
return {'type': 'projection', 'children': [left, right]}
|
99
104
|
|
100
105
|
|
101
|
-
def subexpression(children):
|
106
|
+
def subexpression(children) -> Node:
|
102
107
|
return {'type': 'subexpression', 'children': children}
|
103
108
|
|
104
109
|
|
105
|
-
def slice(start, end, step): # noqa
|
110
|
+
def slice(start, end, step) -> Node: # noqa
|
106
111
|
return {'type': 'slice', 'children': [start, end, step]}
|
107
112
|
|
108
113
|
|
109
|
-
def value_projection(left, right):
|
114
|
+
def value_projection(left: Node, right: Node) -> Node:
|
110
115
|
return {'type': 'value_projection', 'children': [left, right]}
|
111
116
|
|
112
117
|
|
113
|
-
def variable_ref(name):
|
118
|
+
def variable_ref(name: str) -> Node:
|
114
119
|
return {'type': 'variable_ref', 'children': [], 'value': name}
|
@@ -1,3 +1,6 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
|
1
4
|
class JmespathError(ValueError):
|
2
5
|
pass
|
3
6
|
|
@@ -10,15 +13,15 @@ class ParseError(JmespathError):
|
|
10
13
|
lex_position,
|
11
14
|
token_value,
|
12
15
|
token_type,
|
13
|
-
msg=_ERROR_MESSAGE,
|
14
|
-
):
|
16
|
+
msg: str = _ERROR_MESSAGE,
|
17
|
+
) -> None:
|
15
18
|
super().__init__(lex_position, token_value, token_type)
|
16
19
|
self.lex_position = lex_position
|
17
20
|
self.token_value = token_value
|
18
21
|
self.token_type = token_type.upper()
|
19
22
|
self.msg = msg
|
20
23
|
# Whatever catches the ParseError can fill in the full expression
|
21
|
-
self.expression = None
|
24
|
+
self.expression: ta.Any = None
|
22
25
|
|
23
26
|
def __str__(self):
|
24
27
|
# self.lex_position +1 to account for the starting double quote char.
|
@@ -46,7 +49,7 @@ class IncompleteExpressionError(ParseError):
|
|
46
49
|
|
47
50
|
|
48
51
|
class LexerError(ParseError):
|
49
|
-
def __init__(self, lexer_position, lexer_value, message, expression=None):
|
52
|
+
def __init__(self, lexer_position, lexer_value, message, expression: ta.Any | None = None):
|
50
53
|
self.lexer_position = lexer_position
|
51
54
|
self.lexer_value = lexer_value
|
52
55
|
self.message = message
|
omlish/specs/jmespath/lexer.py
CHANGED
@@ -3,8 +3,17 @@ import string
|
|
3
3
|
import typing as ta
|
4
4
|
import warnings
|
5
5
|
|
6
|
+
from ... import check
|
6
7
|
from .exceptions import EmptyExpressionError
|
7
8
|
from .exceptions import LexerError
|
9
|
+
from .visitor import Options
|
10
|
+
|
11
|
+
|
12
|
+
class Token(ta.TypedDict):
|
13
|
+
type: str
|
14
|
+
value: ta.Any
|
15
|
+
start: int
|
16
|
+
end: int
|
8
17
|
|
9
18
|
|
10
19
|
class Lexer:
|
@@ -33,10 +42,13 @@ class Lexer:
|
|
33
42
|
'\u00f7': 'divide',
|
34
43
|
}
|
35
44
|
|
36
|
-
def __init__(self):
|
45
|
+
def __init__(self) -> None:
|
46
|
+
super().__init__()
|
47
|
+
|
37
48
|
self._enable_legacy_literals = False
|
49
|
+
self._current: str | None = None
|
38
50
|
|
39
|
-
def tokenize(self, expression, options=None):
|
51
|
+
def tokenize(self, expression: str, options: Options | None = None) -> ta.Generator[Token, None, None]:
|
40
52
|
if options is not None:
|
41
53
|
self._enable_legacy_literals = options.enable_legacy_literals
|
42
54
|
|
@@ -205,18 +217,18 @@ class Lexer:
|
|
205
217
|
'end': self._length,
|
206
218
|
}
|
207
219
|
|
208
|
-
def _consume_number(self):
|
220
|
+
def _consume_number(self) -> str:
|
209
221
|
start = self._position # noqa
|
210
222
|
|
211
|
-
buff = self._current
|
223
|
+
buff = check.not_none(self._current)
|
212
224
|
while self._next() in self.VALID_NUMBER:
|
213
|
-
buff += self._current
|
225
|
+
buff += check.not_none(self._current)
|
214
226
|
return buff
|
215
227
|
|
216
|
-
def _consume_variable(self):
|
228
|
+
def _consume_variable(self) -> Token:
|
217
229
|
start = self._position
|
218
230
|
|
219
|
-
buff = self._current
|
231
|
+
buff = check.not_none(self._current)
|
220
232
|
self._next()
|
221
233
|
if self._current not in self.START_IDENTIFIER:
|
222
234
|
raise LexerError(
|
@@ -225,9 +237,9 @@ class Lexer:
|
|
225
237
|
message=f'Invalid variable starting character {self._current}',
|
226
238
|
)
|
227
239
|
|
228
|
-
buff += self._current
|
240
|
+
buff += check.not_none(self._current)
|
229
241
|
while self._next() in self.VALID_IDENTIFIER:
|
230
|
-
buff += self._current
|
242
|
+
buff += check.not_none(self._current)
|
231
243
|
|
232
244
|
return {
|
233
245
|
'type': 'variable',
|
@@ -236,21 +248,21 @@ class Lexer:
|
|
236
248
|
'end': start + len(buff),
|
237
249
|
}
|
238
250
|
|
239
|
-
def _peek_may_be_valid_unquoted_identifier(self):
|
251
|
+
def _peek_may_be_valid_unquoted_identifier(self) -> bool:
|
240
252
|
if (self._position == self._length - 1):
|
241
253
|
return False
|
242
254
|
else:
|
243
255
|
nxt = self._chars[self._position + 1]
|
244
256
|
return nxt in self.START_IDENTIFIER
|
245
257
|
|
246
|
-
def _peek_is_next_digit(self):
|
258
|
+
def _peek_is_next_digit(self) -> bool:
|
247
259
|
if (self._position == self._length - 1):
|
248
260
|
return False
|
249
261
|
else:
|
250
262
|
nxt = self._chars[self._position + 1]
|
251
263
|
return nxt in self.VALID_NUMBER
|
252
264
|
|
253
|
-
def _initialize_for_expression(self, expression):
|
265
|
+
def _initialize_for_expression(self, expression: str) -> None:
|
254
266
|
if not expression:
|
255
267
|
raise EmptyExpressionError
|
256
268
|
self._position = 0
|
@@ -259,7 +271,7 @@ class Lexer:
|
|
259
271
|
self._current = self._chars[self._position]
|
260
272
|
self._length = len(self._expression)
|
261
273
|
|
262
|
-
def _next(self):
|
274
|
+
def _next(self) -> str | None:
|
263
275
|
if self._position == self._length - 1:
|
264
276
|
self._current = None
|
265
277
|
else:
|
@@ -267,7 +279,7 @@ class Lexer:
|
|
267
279
|
self._current = self._chars[self._position]
|
268
280
|
return self._current
|
269
281
|
|
270
|
-
def _consume_until(self, delimiter):
|
282
|
+
def _consume_until(self, delimiter: str) -> str:
|
271
283
|
# Consume until the delimiter is reached, allowing for the delimiter to be escaped with "\".
|
272
284
|
start = self._position
|
273
285
|
|
@@ -293,12 +305,11 @@ class Lexer:
|
|
293
305
|
self._next()
|
294
306
|
return buff
|
295
307
|
|
296
|
-
def _consume_literal(self):
|
308
|
+
def _consume_literal(self) -> Token:
|
297
309
|
start = self._position
|
298
310
|
|
299
311
|
token = self._consume_until('`')
|
300
312
|
lexeme = token.replace('\\`', '`')
|
301
|
-
parsed_json = None
|
302
313
|
try:
|
303
314
|
# Assume it is valid JSON and attempt to parse.
|
304
315
|
parsed_json = json.loads(lexeme)
|
@@ -331,7 +342,7 @@ class Lexer:
|
|
331
342
|
'end': token_len,
|
332
343
|
}
|
333
344
|
|
334
|
-
def _consume_quoted_identifier(self):
|
345
|
+
def _consume_quoted_identifier(self) -> Token:
|
335
346
|
start = self._position
|
336
347
|
|
337
348
|
lexeme = '"' + self._consume_until('"') + '"'
|
@@ -352,7 +363,7 @@ class Lexer:
|
|
352
363
|
message=error_message,
|
353
364
|
)
|
354
365
|
|
355
|
-
def _consume_raw_string_literal(self):
|
366
|
+
def _consume_raw_string_literal(self) -> Token:
|
356
367
|
start = self._position
|
357
368
|
|
358
369
|
lexeme = self._consume_until("'") \
|
@@ -367,10 +378,10 @@ class Lexer:
|
|
367
378
|
'end': token_len,
|
368
379
|
}
|
369
380
|
|
370
|
-
def _match_or_else(self, expected, match_type, else_type):
|
381
|
+
def _match_or_else(self, expected: str, match_type: str, else_type: str) -> Token:
|
371
382
|
start = self._position
|
372
383
|
|
373
|
-
current = self._current
|
384
|
+
current = check.not_none(self._current)
|
374
385
|
next_char = self._next()
|
375
386
|
if next_char == expected:
|
376
387
|
self._next()
|