omlish 0.0.0.dev104__py3-none-any.whl → 0.0.0.dev105__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/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()
|