omlish 0.0.0.dev117__py3-none-any.whl → 0.0.0.dev119__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/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/lite/journald.py +163 -0
- omlish/lite/logs.py +6 -2
- omlish/logs/_abc.py +53 -0
- omlish/logs/handlers.py +1 -1
- 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/testing/pytest/plugins/pydevd.py +6 -6
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/RECORD +29 -26
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev117.dist-info → omlish-0.0.0.dev119.dist-info}/top_level.txt +0 -0
omlish/specs/jmespath/scope.py
CHANGED
@@ -1,14 +1,18 @@
|
|
1
|
-
import
|
1
|
+
import typing as ta
|
2
2
|
|
3
3
|
|
4
|
-
|
4
|
+
K = ta.TypeVar('K')
|
5
|
+
V = ta.TypeVar('V')
|
6
|
+
|
7
|
+
|
8
|
+
class ScopedChainDict(ta.Generic[K, V]):
|
5
9
|
"""
|
6
10
|
Dictionary that can delegate lookups to multiple dicts. This provides a basic get/set dict interface that is backed
|
7
11
|
by multiple dicts. Each dict is searched from the top most (most recently pushed) scope dict until a match is
|
8
12
|
found.
|
9
13
|
"""
|
10
14
|
|
11
|
-
def __init__(self, *scopes):
|
15
|
+
def __init__(self, *scopes: ta.Mapping[K, V]) -> None:
|
12
16
|
super().__init__()
|
13
17
|
|
14
18
|
# The scopes are evaluated starting at the top of the stack (the most recently pushed scope via .push_scope()).
|
@@ -16,22 +20,22 @@ class ScopedChainDict:
|
|
16
20
|
# call reversed(self._scopes) whenever we resolve a key, because the end of the list is the top of the stack.
|
17
21
|
# To avoid this, we're using a deque so we can append to the front of the list via .appendleft() in constant
|
18
22
|
# time, and iterate over scopes without having to do so with a reversed() call each time.
|
19
|
-
self._scopes =
|
23
|
+
self._scopes: list[ta.Mapping[K, V]] = list(reversed(scopes))
|
20
24
|
|
21
|
-
def __getitem__(self, key):
|
22
|
-
for scope in self._scopes:
|
25
|
+
def __getitem__(self, key: K) -> V:
|
26
|
+
for scope in reversed(self._scopes):
|
23
27
|
if key in scope:
|
24
28
|
return scope[key]
|
25
29
|
raise KeyError(key)
|
26
30
|
|
27
|
-
def get(self, key, default=None):
|
31
|
+
def get(self, key: K, default: V | None = None) -> V | None:
|
28
32
|
try:
|
29
33
|
return self[key]
|
30
34
|
except KeyError:
|
31
35
|
return default
|
32
36
|
|
33
|
-
def push_scope(self, scope):
|
34
|
-
self._scopes.
|
37
|
+
def push_scope(self, scope: ta.Mapping[K, V]) -> None:
|
38
|
+
self._scopes.append(scope)
|
35
39
|
|
36
|
-
def pop_scope(self):
|
37
|
-
self._scopes.
|
40
|
+
def pop_scope(self) -> None:
|
41
|
+
self._scopes.pop()
|
omlish/specs/jmespath/visitor.py
CHANGED
@@ -2,12 +2,54 @@ import numbers
|
|
2
2
|
import operator
|
3
3
|
import typing as ta
|
4
4
|
|
5
|
-
from
|
6
|
-
from
|
5
|
+
from ... import check
|
6
|
+
from ... import lang
|
7
|
+
from .ast import AndExpression
|
8
|
+
from .ast import Arithmetic
|
9
|
+
from .ast import ArithmeticUnary
|
10
|
+
from .ast import Assign
|
11
|
+
from .ast import Comparator
|
12
|
+
from .ast import CurrentNode
|
13
|
+
from .ast import Expref
|
14
|
+
from .ast import Field
|
15
|
+
from .ast import FilterProjection
|
16
|
+
from .ast import Flatten
|
17
|
+
from .ast import FunctionExpression
|
18
|
+
from .ast import Identity
|
19
|
+
from .ast import Index
|
20
|
+
from .ast import IndexExpression
|
21
|
+
from .ast import KeyValPair
|
22
|
+
from .ast import LetExpression
|
23
|
+
from .ast import Literal
|
24
|
+
from .ast import MultiSelectDict
|
25
|
+
from .ast import MultiSelectList
|
7
26
|
from .ast import Node
|
27
|
+
from .ast import NotExpression
|
28
|
+
from .ast import OrExpression
|
29
|
+
from .ast import Pipe
|
30
|
+
from .ast import Projection
|
31
|
+
from .ast import RootNode
|
32
|
+
from .ast import Slice
|
33
|
+
from .ast import Subexpression
|
34
|
+
from .ast import ValueProjection
|
35
|
+
from .ast import VariableRef
|
36
|
+
from .exceptions import UndefinedVariableError
|
37
|
+
from .functions import DefaultFunctions
|
38
|
+
from .functions import Functions
|
8
39
|
from .scope import ScopedChainDict
|
9
40
|
|
10
41
|
|
42
|
+
if ta.TYPE_CHECKING:
|
43
|
+
import html
|
44
|
+
import json
|
45
|
+
else:
|
46
|
+
html = lang.proxy_import('html')
|
47
|
+
json = lang.proxy_import('json')
|
48
|
+
|
49
|
+
|
50
|
+
##
|
51
|
+
|
52
|
+
|
11
53
|
def _equals(x: ta.Any, y: ta.Any) -> bool:
|
12
54
|
if _is_special_number_case(x, y):
|
13
55
|
return False
|
@@ -58,58 +100,69 @@ def _is_actual_number(x: ta.Any) -> bool:
|
|
58
100
|
return isinstance(x, numbers.Number)
|
59
101
|
|
60
102
|
|
103
|
+
def node_type(n: Node) -> str:
|
104
|
+
return lang.snake_case(type(n).__name__)
|
105
|
+
|
106
|
+
|
107
|
+
##
|
108
|
+
|
109
|
+
|
110
|
+
class Visitor:
|
111
|
+
def __init__(self) -> None:
|
112
|
+
super().__init__()
|
113
|
+
|
114
|
+
self._method_cache: dict[str, ta.Callable] = {}
|
115
|
+
|
116
|
+
def visit(self, node: Node, *args: ta.Any, **kwargs: ta.Any) -> ta.Any:
|
117
|
+
nty = node_type(node)
|
118
|
+
method = self._method_cache.get(nty)
|
119
|
+
if method is None:
|
120
|
+
method = check.not_none(getattr(self, f'visit_{nty}', self.default_visit))
|
121
|
+
self._method_cache[nty] = method
|
122
|
+
return method(node, *args, **kwargs)
|
123
|
+
|
124
|
+
def default_visit(self, node, *args, **kwargs):
|
125
|
+
raise NotImplementedError('default_visit')
|
126
|
+
|
127
|
+
|
128
|
+
##
|
129
|
+
|
130
|
+
|
61
131
|
class Options:
|
62
132
|
"""Options to control how a Jmespath function is evaluated."""
|
63
133
|
|
64
134
|
def __init__(
|
65
135
|
self,
|
66
136
|
dict_cls: type | None = None,
|
67
|
-
custom_functions=None,
|
137
|
+
custom_functions: Functions | None = None,
|
68
138
|
enable_legacy_literals: bool = False,
|
69
139
|
) -> None:
|
70
140
|
super().__init__()
|
71
141
|
|
72
|
-
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
142
|
+
# The class to use when creating a dict. The interpreter may create dictionaries during the evaluation of a
|
143
|
+
# Jmespath expression. For example, a multi-select hash will create a dictionary. By default we use a dict()
|
144
|
+
# type. You can set this value to change what dict type is used. The most common reason you would change this is
|
145
|
+
# if you want to set a collections.OrderedDict so that you can have predictable key ordering.
|
76
146
|
self.dict_cls = dict_cls
|
77
147
|
self.custom_functions = custom_functions
|
78
148
|
|
79
|
-
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
149
|
+
# The flag to enable pre-JEP-12 literal compatibility.
|
150
|
+
# JEP-12 deprecates `foo` -> "foo" syntax.
|
151
|
+
# Valid expressions MUST use: `"foo"` -> "foo"
|
152
|
+
# Setting this flag to `True` enables support for legacy syntax.
|
83
153
|
self.enable_legacy_literals = enable_legacy_literals
|
84
154
|
|
85
155
|
|
86
156
|
class _Expression:
|
87
|
-
def __init__(self, expression, interpreter):
|
157
|
+
def __init__(self, expression: Node, interpreter: 'TreeInterpreter') -> None:
|
88
158
|
super().__init__()
|
89
159
|
self.expression = expression
|
90
160
|
self.interpreter = interpreter
|
91
161
|
|
92
|
-
def visit(self, node, *args, **kwargs):
|
162
|
+
def visit(self, node: Node, *args: ta.Any, **kwargs: ta.Any) -> ta.Any:
|
93
163
|
return self.interpreter.visit(node, *args, **kwargs)
|
94
164
|
|
95
165
|
|
96
|
-
class Visitor:
|
97
|
-
def __init__(self):
|
98
|
-
super().__init__()
|
99
|
-
self._method_cache = {}
|
100
|
-
|
101
|
-
def visit(self, node, *args, **kwargs):
|
102
|
-
node_type = node['type']
|
103
|
-
method = self._method_cache.get(node_type)
|
104
|
-
if method is None:
|
105
|
-
method = getattr(self, f'visit_{node["type"]}', self.default_visit)
|
106
|
-
self._method_cache[node_type] = method
|
107
|
-
return method(node, *args, **kwargs) # type: ignore
|
108
|
-
|
109
|
-
def default_visit(self, node, *args, **kwargs):
|
110
|
-
raise NotImplementedError('default_visit')
|
111
|
-
|
112
|
-
|
113
166
|
class TreeInterpreter(Visitor):
|
114
167
|
COMPARATOR_FUNC: ta.Mapping[str, ta.Callable] = {
|
115
168
|
'eq': _equals,
|
@@ -120,7 +173,10 @@ class TreeInterpreter(Visitor):
|
|
120
173
|
'gte': operator.ge,
|
121
174
|
}
|
122
175
|
|
123
|
-
_EQUALITY_OPS: ta.
|
176
|
+
_EQUALITY_OPS: ta.AbstractSet[str] = {
|
177
|
+
'eq',
|
178
|
+
'ne',
|
179
|
+
}
|
124
180
|
|
125
181
|
_ARITHMETIC_UNARY_FUNC: ta.Mapping[str, ta.Callable] = {
|
126
182
|
'minus': operator.neg,
|
@@ -136,115 +192,113 @@ class TreeInterpreter(Visitor):
|
|
136
192
|
'plus': operator.add,
|
137
193
|
}
|
138
194
|
|
139
|
-
|
140
|
-
|
141
|
-
def __init__(self, options=None):
|
195
|
+
def __init__(self, options: Options | None = None) -> None:
|
142
196
|
super().__init__()
|
143
197
|
|
144
|
-
self._dict_cls =
|
198
|
+
self._dict_cls: type = dict
|
145
199
|
|
146
200
|
if options is None:
|
147
201
|
options = Options()
|
148
202
|
self._options = options
|
149
203
|
|
150
204
|
if options.dict_cls is not None:
|
151
|
-
self._dict_cls =
|
205
|
+
self._dict_cls = options.dict_cls
|
152
206
|
|
153
207
|
if options.custom_functions is not None:
|
154
|
-
self._functions =
|
208
|
+
self._functions: Functions = options.custom_functions
|
155
209
|
else:
|
156
|
-
self._functions =
|
210
|
+
self._functions = DefaultFunctions()
|
157
211
|
|
158
|
-
self._root = None
|
159
|
-
self._scope = ScopedChainDict()
|
212
|
+
self._root: Node | None = None
|
213
|
+
self._scope: ScopedChainDict = ScopedChainDict()
|
160
214
|
|
161
|
-
def default_visit(self, node, *args, **kwargs):
|
162
|
-
raise NotImplementedError(node
|
215
|
+
def default_visit(self, node: Node, *args: ta.Any, **kwargs: ta.Any) -> ta.NoReturn:
|
216
|
+
raise NotImplementedError(node_type(node))
|
163
217
|
|
164
|
-
def evaluate(self, ast, root: Node) -> ta.Any:
|
218
|
+
def evaluate(self, ast: Node, root: Node) -> ta.Any:
|
165
219
|
self._root = root
|
166
220
|
return self.visit(ast, root)
|
167
221
|
|
168
|
-
def visit_subexpression(self, node, value):
|
222
|
+
def visit_subexpression(self, node: Subexpression, value: ta.Any) -> ta.Any:
|
169
223
|
result = value
|
170
|
-
for child in node
|
224
|
+
for child in node.children_nodes:
|
171
225
|
result = self.visit(child, result)
|
172
226
|
if result is None:
|
173
227
|
return None
|
174
228
|
return result
|
175
229
|
|
176
|
-
def visit_field(self, node, value):
|
230
|
+
def visit_field(self, node: Field, value: ta.Any) -> ta.Any:
|
177
231
|
try:
|
178
|
-
return value.get(node
|
232
|
+
return value.get(node.name)
|
179
233
|
except AttributeError:
|
180
234
|
return None
|
181
235
|
|
182
|
-
def visit_comparator(self, node, value):
|
236
|
+
def visit_comparator(self, node: Comparator, value: ta.Any) -> ta.Any:
|
183
237
|
# Common case: comparator is == or !=
|
184
|
-
comparator_func = self.COMPARATOR_FUNC[node
|
185
|
-
if node
|
238
|
+
comparator_func = self.COMPARATOR_FUNC[node.name]
|
239
|
+
if node.name in self._EQUALITY_OPS:
|
186
240
|
return comparator_func(
|
187
|
-
self.visit(node
|
188
|
-
self.visit(node
|
241
|
+
self.visit(node.first, value),
|
242
|
+
self.visit(node.second, value),
|
189
243
|
)
|
190
244
|
|
191
245
|
else:
|
192
246
|
# Ordering operators are only valid for numbers. Evaluating any other type with a comparison operator will
|
193
247
|
# yield a None value.
|
194
|
-
left = self.visit(node
|
195
|
-
right = self.visit(node
|
248
|
+
left = self.visit(node.first, value)
|
249
|
+
right = self.visit(node.second, value)
|
196
250
|
# num_types = (int, float)
|
197
251
|
if not (_is_comparable(left) and _is_comparable(right)):
|
198
252
|
return None
|
199
253
|
return comparator_func(left, right)
|
200
254
|
|
201
|
-
def visit_arithmetic_unary(self, node, value):
|
202
|
-
operation = self._ARITHMETIC_UNARY_FUNC[node
|
255
|
+
def visit_arithmetic_unary(self, node: ArithmeticUnary, value: ta.Any) -> ta.Any:
|
256
|
+
operation = self._ARITHMETIC_UNARY_FUNC[node.operator]
|
203
257
|
return operation(
|
204
|
-
self.visit(node
|
258
|
+
self.visit(node.expression, value),
|
205
259
|
)
|
206
260
|
|
207
|
-
def visit_arithmetic(self, node, value):
|
208
|
-
operation = self._ARITHMETIC_FUNC[node
|
261
|
+
def visit_arithmetic(self, node: Arithmetic, value: ta.Any) -> ta.Any:
|
262
|
+
operation = self._ARITHMETIC_FUNC[node.operator]
|
209
263
|
return operation(
|
210
|
-
self.visit(node
|
211
|
-
self.visit(node
|
264
|
+
self.visit(node.left, value),
|
265
|
+
self.visit(node.right, value),
|
212
266
|
)
|
213
267
|
|
214
|
-
def
|
268
|
+
def visit_current_node(self, node: CurrentNode, value: ta.Any) -> ta.Any:
|
215
269
|
return value
|
216
270
|
|
217
|
-
def
|
271
|
+
def visit_root_node(self, node: RootNode, value: ta.Any) -> ta.Any:
|
218
272
|
return self._root
|
219
273
|
|
220
|
-
def visit_expref(self, node, value):
|
221
|
-
return _Expression(node
|
274
|
+
def visit_expref(self, node: Expref, value: ta.Any) -> ta.Any:
|
275
|
+
return _Expression(node.expression, self)
|
222
276
|
|
223
|
-
def visit_function_expression(self, node, value):
|
277
|
+
def visit_function_expression(self, node: FunctionExpression, value: ta.Any) -> ta.Any:
|
224
278
|
resolved_args = []
|
225
|
-
for child in node
|
279
|
+
for child in node.args:
|
226
280
|
current = self.visit(child, value)
|
227
281
|
resolved_args.append(current)
|
228
282
|
|
229
|
-
return self._functions.call_function(node
|
283
|
+
return self._functions.call_function(node.name, resolved_args)
|
230
284
|
|
231
|
-
def visit_filter_projection(self, node, value):
|
232
|
-
base = self.visit(node
|
285
|
+
def visit_filter_projection(self, node: FilterProjection, value: ta.Any) -> ta.Any:
|
286
|
+
base = self.visit(node.left, value)
|
233
287
|
if not isinstance(base, list):
|
234
288
|
return None
|
235
289
|
|
236
|
-
comparator_node = node
|
290
|
+
comparator_node = node.comparator
|
237
291
|
collected = []
|
238
292
|
for element in base:
|
239
293
|
if self._is_true(self.visit(comparator_node, element)):
|
240
|
-
current = self.visit(node
|
294
|
+
current = self.visit(node.right, element)
|
241
295
|
if current is not None:
|
242
296
|
collected.append(current)
|
243
297
|
|
244
298
|
return collected
|
245
299
|
|
246
|
-
def visit_flatten(self, node, value):
|
247
|
-
base = self.visit(node
|
300
|
+
def visit_flatten(self, node: Flatten, value: ta.Any) -> ta.Any:
|
301
|
+
base = self.visit(node.node, value)
|
248
302
|
if not isinstance(base, list):
|
249
303
|
# Can't flatten the object if it's not a list.
|
250
304
|
return None
|
@@ -258,77 +312,74 @@ class TreeInterpreter(Visitor):
|
|
258
312
|
|
259
313
|
return merged_list
|
260
314
|
|
261
|
-
def visit_identity(self, node, value):
|
315
|
+
def visit_identity(self, node: Identity, value: ta.Any) -> ta.Any:
|
262
316
|
return value
|
263
317
|
|
264
|
-
def visit_index(self, node, value):
|
318
|
+
def visit_index(self, node: Index, value: ta.Any) -> ta.Any:
|
265
319
|
# Even though we can index strings, we don't want to support that.
|
266
320
|
if not isinstance(value, list):
|
267
321
|
return None
|
268
322
|
|
269
323
|
try:
|
270
|
-
return value[node
|
324
|
+
return value[node.index]
|
271
325
|
except IndexError:
|
272
326
|
return None
|
273
327
|
|
274
|
-
def visit_index_expression(self, node, value):
|
328
|
+
def visit_index_expression(self, node: IndexExpression, value: ta.Any) -> ta.Any:
|
275
329
|
result = value
|
276
|
-
for child in node
|
330
|
+
for child in node.nodes:
|
277
331
|
result = self.visit(child, result)
|
278
332
|
|
279
333
|
return result
|
280
334
|
|
281
|
-
def visit_slice(self, node, value):
|
335
|
+
def visit_slice(self, node: Slice, value: ta.Any) -> ta.Any:
|
282
336
|
if isinstance(value, str):
|
283
|
-
|
284
|
-
end = node['children'][1]
|
285
|
-
step = node['children'][2]
|
286
|
-
return value[start:end:step]
|
337
|
+
return value[node.start:node.end:node.step]
|
287
338
|
|
288
339
|
if not isinstance(value, list):
|
289
340
|
return None
|
290
341
|
|
291
|
-
s = slice(
|
342
|
+
s = slice(node.start, node.end, node.step)
|
292
343
|
return value[s]
|
293
344
|
|
294
|
-
def visit_key_val_pair(self, node, value):
|
295
|
-
return self.visit(node
|
345
|
+
def visit_key_val_pair(self, node: KeyValPair, value: ta.Any) -> ta.Any:
|
346
|
+
return self.visit(node.node, value)
|
296
347
|
|
297
|
-
def visit_literal(self, node, value):
|
298
|
-
return node
|
348
|
+
def visit_literal(self, node: Literal, value: ta.Any):
|
349
|
+
return node.literal_value
|
299
350
|
|
300
|
-
def visit_multi_select_dict(self, node, value):
|
351
|
+
def visit_multi_select_dict(self, node: MultiSelectDict, value: ta.Any) -> ta.Any:
|
301
352
|
collected = self._dict_cls()
|
302
|
-
for child in node
|
303
|
-
collected[child
|
353
|
+
for child in node.nodes:
|
354
|
+
collected[child.key_name] = self.visit(child, value)
|
304
355
|
|
305
356
|
return collected
|
306
357
|
|
307
|
-
def visit_multi_select_list(self, node, value):
|
358
|
+
def visit_multi_select_list(self, node: MultiSelectList, value: ta.Any) -> ta.Any:
|
308
359
|
collected = []
|
309
|
-
for child in node
|
360
|
+
for child in node.nodes:
|
310
361
|
collected.append(self.visit(child, value))
|
311
362
|
|
312
363
|
return collected
|
313
364
|
|
314
|
-
def visit_or_expression(self, node, value):
|
315
|
-
matched = self.visit(node
|
365
|
+
def visit_or_expression(self, node: OrExpression, value: ta.Any) -> ta.Any:
|
366
|
+
matched = self.visit(node.left, value)
|
316
367
|
|
317
368
|
if self._is_false(matched):
|
318
|
-
matched = self.visit(node
|
369
|
+
matched = self.visit(node.right, value)
|
319
370
|
|
320
371
|
return matched
|
321
372
|
|
322
|
-
def visit_and_expression(self, node, value):
|
323
|
-
matched = self.visit(node
|
373
|
+
def visit_and_expression(self, node: AndExpression, value: ta.Any) -> ta.Any:
|
374
|
+
matched = self.visit(node.left, value)
|
324
375
|
|
325
376
|
if self._is_false(matched):
|
326
377
|
return matched
|
327
378
|
|
328
|
-
return self.visit(node
|
379
|
+
return self.visit(node.right, value)
|
329
380
|
|
330
|
-
def visit_not_expression(self, node, value):
|
331
|
-
original_result = self.visit(node
|
381
|
+
def visit_not_expression(self, node: NotExpression, value: ta.Any) -> ta.Any:
|
382
|
+
original_result = self.visit(node.expr, value)
|
332
383
|
|
333
384
|
if _is_actual_number(original_result) and original_result == 0:
|
334
385
|
# Special case for 0, !0 should be false, not true. 0 is not a special cased integer in jmespath.
|
@@ -336,59 +387,59 @@ class TreeInterpreter(Visitor):
|
|
336
387
|
|
337
388
|
return not original_result
|
338
389
|
|
339
|
-
def visit_pipe(self, node, value):
|
390
|
+
def visit_pipe(self, node: Pipe, value: ta.Any) -> ta.Any:
|
340
391
|
result = value
|
341
|
-
for child in node
|
392
|
+
for child in [node.left, node.right]:
|
342
393
|
result = self.visit(child, result)
|
343
394
|
return result
|
344
395
|
|
345
|
-
def visit_projection(self, node, value):
|
346
|
-
base = self.visit(node
|
396
|
+
def visit_projection(self, node: Projection, value: ta.Any) -> ta.Any:
|
397
|
+
base = self.visit(node.left, value)
|
347
398
|
|
348
399
|
allow_string = False
|
349
|
-
first_child = node
|
350
|
-
if first_child
|
351
|
-
nested_children = first_child
|
352
|
-
if len(nested_children) > 1 and nested_children[1]
|
400
|
+
first_child = node.left
|
401
|
+
if isinstance(first_child, IndexExpression):
|
402
|
+
nested_children = first_child.nodes
|
403
|
+
if len(nested_children) > 1 and isinstance(nested_children[1], Slice):
|
353
404
|
allow_string = True
|
354
405
|
|
355
406
|
if isinstance(base, str) and allow_string:
|
356
407
|
# projections are really sub-expressions in disguise evaluate the rhs when lhs is a sliced string
|
357
|
-
return self.visit(node
|
408
|
+
return self.visit(node.right, base)
|
358
409
|
|
359
410
|
if not isinstance(base, list):
|
360
411
|
return None
|
412
|
+
|
361
413
|
collected = []
|
362
414
|
for element in base:
|
363
|
-
current = self.visit(node
|
415
|
+
current = self.visit(node.right, element)
|
364
416
|
if current is not None:
|
365
417
|
collected.append(current)
|
366
418
|
|
367
419
|
return collected
|
368
420
|
|
369
|
-
def visit_let_expression(self, node, value):
|
370
|
-
*bindings, expr = node['children']
|
421
|
+
def visit_let_expression(self, node: LetExpression, value: ta.Any) -> ta.Any:
|
371
422
|
scope = {}
|
372
|
-
for assign in bindings:
|
423
|
+
for assign in node.bindings:
|
373
424
|
scope.update(self.visit(assign, value))
|
374
425
|
self._scope.push_scope(scope)
|
375
|
-
result = self.visit(expr, value)
|
426
|
+
result = self.visit(node.expr, value)
|
376
427
|
self._scope.pop_scope()
|
377
428
|
return result
|
378
429
|
|
379
|
-
def visit_assign(self, node, value):
|
380
|
-
name = node
|
381
|
-
value = self.visit(node
|
430
|
+
def visit_assign(self, node: Assign, value: ta.Any) -> ta.Any:
|
431
|
+
name = node.name
|
432
|
+
value = self.visit(node.expr, value)
|
382
433
|
return {name: value}
|
383
434
|
|
384
|
-
def visit_variable_ref(self, node, value):
|
435
|
+
def visit_variable_ref(self, node: VariableRef, value: ta.Any) -> ta.Any:
|
385
436
|
try:
|
386
|
-
return self._scope[node
|
437
|
+
return self._scope[node.name]
|
387
438
|
except KeyError:
|
388
|
-
raise
|
439
|
+
raise UndefinedVariableError(node.name) # noqa
|
389
440
|
|
390
|
-
def visit_value_projection(self, node, value):
|
391
|
-
base = self.visit(node
|
441
|
+
def visit_value_projection(self, node: ValueProjection, value: ta.Any) -> ta.Any:
|
442
|
+
base = self.visit(node.left, value)
|
392
443
|
try:
|
393
444
|
base = base.values()
|
394
445
|
except AttributeError:
|
@@ -396,39 +447,62 @@ class TreeInterpreter(Visitor):
|
|
396
447
|
|
397
448
|
collected = []
|
398
449
|
for element in base:
|
399
|
-
current = self.visit(node
|
450
|
+
current = self.visit(node.right, element)
|
400
451
|
if current is not None:
|
401
452
|
collected.append(current)
|
402
453
|
|
403
454
|
return collected
|
404
455
|
|
405
|
-
def _is_false(self, value):
|
456
|
+
def _is_false(self, value: ta.Any) -> bool:
|
406
457
|
# This looks weird, but we're explicitly using equality checks because the truth/false values are different
|
407
458
|
# between python and jmespath.
|
408
|
-
return (
|
459
|
+
return (
|
460
|
+
value == '' or # noqa
|
461
|
+
value == [] or
|
462
|
+
value == {} or
|
463
|
+
value is None or
|
464
|
+
value is False
|
465
|
+
)
|
409
466
|
|
410
|
-
def _is_true(self, value):
|
467
|
+
def _is_true(self, value: ta.Any) -> bool:
|
411
468
|
return not self._is_false(value)
|
412
469
|
|
413
470
|
|
414
|
-
class GraphvizVisitor
|
415
|
-
def __init__(self):
|
471
|
+
class GraphvizVisitor:
|
472
|
+
def __init__(self) -> None:
|
416
473
|
super().__init__()
|
417
|
-
self._lines = []
|
474
|
+
self._lines: list[str] = []
|
418
475
|
self._count = 1
|
419
476
|
|
420
|
-
def visit(self, node
|
477
|
+
def visit(self, node: Node) -> str:
|
421
478
|
self._lines.append('digraph AST {')
|
422
|
-
current = f
|
479
|
+
current = f'{node_type(node)}{self._count}'
|
423
480
|
self._count += 1
|
424
481
|
self._visit(node, current)
|
425
482
|
self._lines.append('}')
|
426
483
|
return '\n'.join(self._lines)
|
427
484
|
|
428
|
-
def
|
429
|
-
|
430
|
-
|
431
|
-
|
485
|
+
def _node_value(self, node: Node) -> lang.Maybe[ta.Any]:
|
486
|
+
if isinstance(node, (ArithmeticUnary, Arithmetic)):
|
487
|
+
return lang.just(node.operator)
|
488
|
+
elif isinstance(node, (Assign, Comparator, FunctionExpression, Field, VariableRef)):
|
489
|
+
return lang.just(node.name)
|
490
|
+
elif isinstance(node, Index):
|
491
|
+
return lang.just(node.index)
|
492
|
+
elif isinstance(node, KeyValPair):
|
493
|
+
return lang.just(node.key_name)
|
494
|
+
elif isinstance(node, Literal):
|
495
|
+
return lang.just(node.literal_value)
|
496
|
+
else:
|
497
|
+
return lang.empty()
|
498
|
+
|
499
|
+
def _visit(self, node: Node, current: str) -> None:
|
500
|
+
self._lines.append(
|
501
|
+
f'{current} '
|
502
|
+
f'[label="{node_type(node)}({self._node_value(node).map(json.dumps).map(html.escape).or_else("")})"]',
|
503
|
+
)
|
504
|
+
for child in node.children:
|
505
|
+
child_name = f'{node_type(child)}{self._count}'
|
432
506
|
self._count += 1
|
433
507
|
self._lines.append(f' {current} -> {child_name}')
|
434
508
|
self._visit(child, child_name)
|
@@ -16,9 +16,9 @@ class PydevdPlugin:
|
|
16
16
|
# if (dbg := opd.get_global_debugger()) is not None:
|
17
17
|
# dbg.set_unit_tests_debugging_mode()
|
18
18
|
|
19
|
-
def pytest_exception_interact(self, node, call, report):
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
# def pytest_exception_interact(self, node, call, report):
|
20
|
+
# if opd.get_setup() is not None:
|
21
|
+
# if not node.session.config.option.no_pydevd:
|
22
|
+
# opd.debug_unhandled_exception(call.excinfo._excinfo) # noqa
|
23
|
+
#
|
24
|
+
# return report
|