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.
@@ -1,14 +1,18 @@
1
- import collections
1
+ import typing as ta
2
2
 
3
3
 
4
- class ScopedChainDict:
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 = collections.deque(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.appendleft(scope)
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.popleft()
40
+ def pop_scope(self) -> None:
41
+ self._scopes.pop()
@@ -2,12 +2,54 @@ import numbers
2
2
  import operator
3
3
  import typing as ta
4
4
 
5
- from . import exceptions
6
- from . import functions
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
- #: The class to use when creating a dict. The interpreter may create dictionaries during the evaluation of a
73
- # Jmespath expression. For example, a multi-select hash will create a dictionary. By default we use a dict()
74
- # type. You can set this value to change what dict type is used. The most common reason you would change this
75
- # is if you want to set a collections.OrderedDict so that you can have predictable key ordering.
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
- #: The flag to enable pre-JEP-12 literal compatibility.
80
- # JEP-12 deprecates `foo` -> "foo" syntax.
81
- # Valid expressions MUST use: `"foo"` -> "foo"
82
- # Setting this flag to `True` enables support for legacy syntax.
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.Sequence[str] = ['eq', 'ne']
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
- MAP_TYPE = dict
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 = self.MAP_TYPE
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 = self._options.dict_cls
205
+ self._dict_cls = options.dict_cls
152
206
 
153
207
  if options.custom_functions is not None:
154
- self._functions = self._options.custom_functions
208
+ self._functions: Functions = options.custom_functions
155
209
  else:
156
- self._functions = functions.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['type'])
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['children']:
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['value'])
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['value']]
185
- if node['value'] in self._EQUALITY_OPS:
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['children'][0], value),
188
- self.visit(node['children'][1], value),
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['children'][0], value)
195
- right = self.visit(node['children'][1], value)
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['value']]
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['children'][0], value),
258
+ self.visit(node.expression, value),
205
259
  )
206
260
 
207
- def visit_arithmetic(self, node, value):
208
- operation = self._ARITHMETIC_FUNC[node['value']]
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['children'][0], value),
211
- self.visit(node['children'][1], value),
264
+ self.visit(node.left, value),
265
+ self.visit(node.right, value),
212
266
  )
213
267
 
214
- def visit_current(self, node, value):
268
+ def visit_current_node(self, node: CurrentNode, value: ta.Any) -> ta.Any:
215
269
  return value
216
270
 
217
- def visit_root(self, node, value):
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['children'][0], self)
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['children']:
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['value'], resolved_args)
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['children'][0], value)
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['children'][2]
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['children'][1], element)
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['children'][0], value)
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['value']]
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['children']:
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
- start = node['children'][0]
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(*node['children'])
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['children'][0], value)
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['value']
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['children']:
303
- collected[child['value']] = self.visit(child, value)
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['children']:
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['children'][0], value)
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['children'][1], value)
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['children'][0], value)
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['children'][1], value)
379
+ return self.visit(node.right, value)
329
380
 
330
- def visit_not_expression(self, node, value):
331
- original_result = self.visit(node['children'][0], value)
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['children']:
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['children'][0], value)
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['children'][0]
350
- if first_child['type'] == 'index_expression':
351
- nested_children = first_child['children']
352
- if len(nested_children) > 1 and nested_children[1]['type'] == 'slice':
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['children'][1], base)
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['children'][1], element)
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['value']
381
- value = self.visit(node['children'][0], value)
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['value']]
437
+ return self._scope[node.name]
387
438
  except KeyError:
388
- raise exceptions.UndefinedVariableError(node['value']) # noqa
439
+ raise UndefinedVariableError(node.name) # noqa
389
440
 
390
- def visit_value_projection(self, node, value):
391
- base = self.visit(node['children'][0], value)
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['children'][1], element)
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 (value == '' or value == [] or value == {} or value is None or value is False) # noqa
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(Visitor):
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, *args, **kwargs):
477
+ def visit(self, node: Node) -> str:
421
478
  self._lines.append('digraph AST {')
422
- current = f"{node['type']}{self._count}"
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 _visit(self, node, current):
429
- self._lines.append('%s [label="%s(%s)"]' % (current, node['type'], node.get('value', ''))) # noqa
430
- for child in node.get('children', []):
431
- child_name = f"{child['type']}{self._count}"
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev117
3
+ Version: 0.0.0.dev118
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause