omlish 0.0.0.dev47__py3-none-any.whl → 0.0.0.dev48__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/bootstrap/harness.py +1 -2
- omlish/lang/objects.py +5 -2
- omlish/specs/jmespath/__init__.py +22 -1
- omlish/specs/jmespath/ast.py +24 -0
- omlish/specs/jmespath/cli.py +4 -0
- omlish/specs/jmespath/exceptions.py +24 -0
- omlish/specs/jmespath/functions.py +227 -6
- omlish/specs/jmespath/lexer.py +115 -38
- omlish/specs/jmespath/parser.py +91 -14
- omlish/specs/jmespath/scope.py +35 -0
- omlish/specs/jmespath/visitor.py +93 -8
- omlish/specs/jsonschema/keywords/__init__.py +6 -0
- omlish/specs/jsonschema/keywords/base.py +1 -1
- omlish/specs/jsonschema/keywords/core.py +1 -1
- omlish/specs/jsonschema/keywords/metadata.py +1 -1
- omlish/specs/jsonschema/keywords/parse.py +68 -35
- omlish/specs/jsonschema/keywords/validation.py +1 -1
- {omlish-0.0.0.dev47.dist-info → omlish-0.0.0.dev48.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev47.dist-info → omlish-0.0.0.dev48.dist-info}/RECORD +24 -23
- {omlish-0.0.0.dev47.dist-info → omlish-0.0.0.dev48.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev47.dist-info → omlish-0.0.0.dev48.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev47.dist-info → omlish-0.0.0.dev48.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev47.dist-info → omlish-0.0.0.dev48.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/bootstrap/harness.py
CHANGED
@@ -38,8 +38,7 @@ from .base import SimpleBootstrap
|
|
38
38
|
|
39
39
|
BOOTSTRAP_TYPES_BY_NAME: ta.Mapping[str, ta.Type[Bootstrap]] = { # noqa
|
40
40
|
lang.snake_case(cls.__name__[:-len('Bootstrap')]): cls
|
41
|
-
for cls in lang.deep_subclasses(Bootstrap)
|
42
|
-
if not lang.is_abstract_class(cls)
|
41
|
+
for cls in lang.deep_subclasses(Bootstrap, concrete_only=True)
|
43
42
|
}
|
44
43
|
|
45
44
|
BOOTSTRAP_TYPES_BY_CONFIG_TYPE: ta.Mapping[ta.Type[Bootstrap.Config], ta.Type[Bootstrap]] = {
|
omlish/lang/objects.py
CHANGED
@@ -2,6 +2,8 @@ import types
|
|
2
2
|
import typing as ta
|
3
3
|
import weakref
|
4
4
|
|
5
|
+
from .classes.abstract import is_abstract_class
|
6
|
+
|
5
7
|
|
6
8
|
T = ta.TypeVar('T')
|
7
9
|
|
@@ -85,7 +87,7 @@ def super_meta(
|
|
85
87
|
##
|
86
88
|
|
87
89
|
|
88
|
-
def deep_subclasses(cls: type) -> ta.Iterator[type]:
|
90
|
+
def deep_subclasses(cls: type, *, concrete_only: bool = False) -> ta.Iterator[type]:
|
89
91
|
seen = set()
|
90
92
|
todo = list(reversed(cls.__subclasses__()))
|
91
93
|
while todo:
|
@@ -93,7 +95,8 @@ def deep_subclasses(cls: type) -> ta.Iterator[type]:
|
|
93
95
|
if cur in seen:
|
94
96
|
continue
|
95
97
|
seen.add(cur)
|
96
|
-
|
98
|
+
if not (concrete_only and is_abstract_class(cur)):
|
99
|
+
yield cur
|
97
100
|
todo.extend(reversed(cur.__subclasses__()))
|
98
101
|
|
99
102
|
|
@@ -1,7 +1,28 @@
|
|
1
1
|
"""
|
2
2
|
TODO:
|
3
3
|
- @omlish-lite
|
4
|
-
|
4
|
+
|
5
|
+
Applied:
|
6
|
+
https://github.com/jmespath-community/python-jmespath/compare/bbe7300c60056f52413603cf3e2bcd0b6afeda3d...aef45e9d665de662eee31b06aeb8261e2bc8b90a
|
7
|
+
|
8
|
+
From community:
|
9
|
+
- JEP-11 Lexical Scoping - https://github.com/jmespath-community/jmespath.spec/discussions/24#discussioncomment-3285710
|
10
|
+
- JEP-11a Lexical Scoping deprecates the let function.
|
11
|
+
- JEP-13 Object Manipulation functions - https://github.com/jmespath-community/jmespath.spec/discussions/47#discussioncomment-3308897
|
12
|
+
- JEP-14 String functions - https://github.com/jmespath-community/jmespath.spec/discussions/21#discussioncomment-3869583
|
13
|
+
- JEP-15 String Slices - https://github.com/jmespath-community/jmespath.spec/discussions/26#discussioncomment-3284127
|
14
|
+
- JEP-16 Arithmetic Expressions - https://github.com/jmespath-community/jmespath.spec/discussions/25#discussioncomment-3277652
|
15
|
+
- JEP-17 Root Reference - https://github.com/jmespath-community/jmespath.spec/discussions/18#discussion-3913993
|
16
|
+
- JEP-18 Grouping - https://github.com/jmespath-community/jmespath.spec/discussions/96#discussion-4282156
|
17
|
+
- JEP-19 Evaluation of Pipe Expressions - https://github.com/jmespath-community/jmespath.spec/discussions/113#discussioncomment-4000862
|
18
|
+
- JEP-20 Compact syntax for multi-select-hash - https://github.com/jmespath-community/jmespath.spec/discussions/19
|
19
|
+
|
20
|
+
See:
|
21
|
+
- https://github.com/jmespath-community/jmespath.spec/tree/main
|
22
|
+
- https://github.com/jmespath-community/python-jmespath
|
23
|
+
- https://github.com/jmespath-community/jmespath.spec/discussions?discussions_q=label%3Ajep-candidate
|
24
|
+
- https://github.com/jmespath-community/jmespath.spec/discussions/97
|
25
|
+
""" # noqa
|
5
26
|
from . import exceptions # noqa
|
6
27
|
from . import functions # noqa
|
7
28
|
from . import lexer # noqa
|
omlish/specs/jmespath/ast.py
CHANGED
@@ -2,6 +2,18 @@
|
|
2
2
|
# {'type': <node type>', children: [], 'value': ''}
|
3
3
|
|
4
4
|
|
5
|
+
def arithmetic_unary(operator, expression):
|
6
|
+
return {'type': 'arithmetic_unary', 'children': [expression], 'value': operator}
|
7
|
+
|
8
|
+
|
9
|
+
def arithmetic(operator, left, right):
|
10
|
+
return {'type': 'arithmetic', 'children': [left, right], 'value': operator}
|
11
|
+
|
12
|
+
|
13
|
+
def assign(name, expr):
|
14
|
+
return {'type': 'assign', 'children': [expr], 'value': name}
|
15
|
+
|
16
|
+
|
5
17
|
def comparator(name, first, second):
|
6
18
|
return {'type': 'comparator', 'children': [first, second], 'value': name}
|
7
19
|
|
@@ -10,6 +22,10 @@ def current_node():
|
|
10
22
|
return {'type': 'current', 'children': []}
|
11
23
|
|
12
24
|
|
25
|
+
def root_node():
|
26
|
+
return {'type': 'root', 'children': []}
|
27
|
+
|
28
|
+
|
13
29
|
def expref(expression):
|
14
30
|
return {'type': 'expref', 'children': [expression]}
|
15
31
|
|
@@ -46,6 +62,10 @@ def key_val_pair(key_name, node):
|
|
46
62
|
return {'type': 'key_val_pair', 'children': [node], 'value': key_name}
|
47
63
|
|
48
64
|
|
65
|
+
def let_expression(bindings, expr):
|
66
|
+
return {'type': 'let_expression', 'children': [*bindings, expr]}
|
67
|
+
|
68
|
+
|
49
69
|
def literal(literal_value):
|
50
70
|
return {'type': 'literal', 'value': literal_value, 'children': []}
|
51
71
|
|
@@ -88,3 +108,7 @@ def slice(start, end, step): # noqa
|
|
88
108
|
|
89
109
|
def value_projection(left, right):
|
90
110
|
return {'type': 'value_projection', 'children': [left, right]}
|
111
|
+
|
112
|
+
|
113
|
+
def variable_ref(name):
|
114
|
+
return {'type': 'variable_ref', 'children': [], 'value': name}
|
omlish/specs/jmespath/cli.py
CHANGED
@@ -51,6 +51,10 @@ def _main():
|
|
51
51
|
sys.stderr.write(f'invalid-type: {e}\n')
|
52
52
|
return 1
|
53
53
|
|
54
|
+
except exceptions.JmespathValueError as e:
|
55
|
+
sys.stderr.write(f'invalid-value: {e}\n')
|
56
|
+
return 1
|
57
|
+
|
54
58
|
except exceptions.UnknownFunctionError as e:
|
55
59
|
sys.stderr.write(f'unknown-function: {e}\n')
|
56
60
|
return 1
|
@@ -107,6 +107,24 @@ class JmespathTypeError(JmespathError):
|
|
107
107
|
)
|
108
108
|
|
109
109
|
|
110
|
+
class JmespathValueError(JmespathError):
|
111
|
+
def __init__(
|
112
|
+
self,
|
113
|
+
function_name,
|
114
|
+
current_value,
|
115
|
+
expected_types,
|
116
|
+
):
|
117
|
+
self.function_name = function_name
|
118
|
+
self.current_value = current_value
|
119
|
+
self.expected_types = expected_types
|
120
|
+
|
121
|
+
def __str__(self):
|
122
|
+
return (
|
123
|
+
f'In function {self.function_name}(), invalid value: "{self.current_value}", '
|
124
|
+
f'expected: {self.expected_types}'
|
125
|
+
)
|
126
|
+
|
127
|
+
|
110
128
|
class EmptyExpressionError(JmespathError):
|
111
129
|
def __init__(self):
|
112
130
|
super().__init__('Invalid Jmespath expression: cannot be empty.')
|
@@ -114,3 +132,9 @@ class EmptyExpressionError(JmespathError):
|
|
114
132
|
|
115
133
|
class UnknownFunctionError(JmespathError):
|
116
134
|
pass
|
135
|
+
|
136
|
+
|
137
|
+
class UndefinedVariableError(JmespathError):
|
138
|
+
def __init__(self, varname):
|
139
|
+
self.varname = varname
|
140
|
+
super().__init__(f'Reference to undefined variable: {self.varname}')
|
@@ -83,18 +83,36 @@ class Functions(metaclass=FunctionRegistry):
|
|
83
83
|
return function(self, *resolved_args)
|
84
84
|
|
85
85
|
def _validate_arguments(self, args, signature, function_name):
|
86
|
-
|
86
|
+
|
87
|
+
if len(signature) == 0:
|
88
|
+
return self._type_check(args, signature, function_name)
|
89
|
+
|
90
|
+
required_arguments_count = len([
|
91
|
+
param for param in signature if param and (not param.get('optional') or not param['optional'])
|
92
|
+
])
|
93
|
+
optional_arguments_count = len([
|
94
|
+
param for param in signature if param and param.get('optional') and param['optional']
|
95
|
+
])
|
96
|
+
has_variadic = signature[-1].get('variadic') if signature is not None else False
|
97
|
+
|
98
|
+
if has_variadic:
|
87
99
|
if len(args) < len(signature):
|
88
100
|
raise exceptions.VariadicArityError(len(signature), len(args), function_name)
|
89
101
|
|
90
|
-
elif
|
102
|
+
elif optional_arguments_count > 0:
|
103
|
+
if (
|
104
|
+
len(args) < required_arguments_count or
|
105
|
+
len(args) > (required_arguments_count + optional_arguments_count)
|
106
|
+
):
|
107
|
+
raise exceptions.ArityError(len(signature), len(args), function_name)
|
108
|
+
elif len(args) != required_arguments_count:
|
91
109
|
raise exceptions.ArityError(len(signature), len(args), function_name)
|
92
110
|
|
93
111
|
return self._type_check(args, signature, function_name)
|
94
112
|
|
95
113
|
def _type_check(self, actual, signature, function_name):
|
96
|
-
for i in range(len(signature)):
|
97
|
-
allowed_types = signature[i]
|
114
|
+
for i in range(min(len(signature), len(actual))):
|
115
|
+
allowed_types = self._get_allowed_types_from_signature(signature[i])
|
98
116
|
if allowed_types:
|
99
117
|
self._type_check_single(actual[i], allowed_types, function_name)
|
100
118
|
|
@@ -117,6 +135,12 @@ class Functions(metaclass=FunctionRegistry):
|
|
117
135
|
if allowed_subtypes:
|
118
136
|
self._subtype_check(current, allowed_subtypes, types, function_name)
|
119
137
|
|
138
|
+
def _get_allowed_types_from_signature(self, spec):
|
139
|
+
# signature supports monotype {'type': 'type-name'}## or multiple types {'types': ['type1-name', 'type2-name']}
|
140
|
+
if spec.get('type'):
|
141
|
+
spec.update({'types': [spec.get('type')]})
|
142
|
+
return spec.get('types')
|
143
|
+
|
120
144
|
def _get_allowed_pytypes(self, types):
|
121
145
|
allowed_types: list = []
|
122
146
|
allowed_subtypes: list = []
|
@@ -161,6 +185,14 @@ class Functions(metaclass=FunctionRegistry):
|
|
161
185
|
def _func_abs(self, arg):
|
162
186
|
return abs(arg)
|
163
187
|
|
188
|
+
@signature({'types': ['string']})
|
189
|
+
def _func_lower(self, arg):
|
190
|
+
return arg.lower()
|
191
|
+
|
192
|
+
@signature({'types': ['string']})
|
193
|
+
def _func_upper(self, arg):
|
194
|
+
return arg.upper()
|
195
|
+
|
164
196
|
@signature({'types': ['array-number']})
|
165
197
|
def _func_avg(self, arg):
|
166
198
|
if arg:
|
@@ -280,6 +312,14 @@ class Functions(metaclass=FunctionRegistry):
|
|
280
312
|
def _func_sum(self, arg):
|
281
313
|
return sum(arg)
|
282
314
|
|
315
|
+
@signature({'types': ['object']})
|
316
|
+
def _func_items(self, arg):
|
317
|
+
return [list(t) for t in arg.items()]
|
318
|
+
|
319
|
+
@signature({'types': ['array']})
|
320
|
+
def _func_from_items(self, items):
|
321
|
+
return dict(items)
|
322
|
+
|
283
323
|
@signature({'types': ['object']})
|
284
324
|
def _func_keys(self, arg):
|
285
325
|
# To be consistent with .values() should we also return the indices of a list?
|
@@ -289,6 +329,169 @@ class Functions(metaclass=FunctionRegistry):
|
|
289
329
|
def _func_values(self, arg):
|
290
330
|
return list(arg.values())
|
291
331
|
|
332
|
+
@signature(
|
333
|
+
{'type': 'string'},
|
334
|
+
{'type': 'string'},
|
335
|
+
{'type': 'number', 'optional': True},
|
336
|
+
{'type': 'number', 'optional': True},
|
337
|
+
)
|
338
|
+
def _func_find_first(self, text, search, start=0, end=None):
|
339
|
+
self._ensure_integer('find_first', 'start', start)
|
340
|
+
self._ensure_integer('find_first', 'end', end)
|
341
|
+
return self._find_impl(
|
342
|
+
text,
|
343
|
+
search,
|
344
|
+
lambda t, s: t.find(s),
|
345
|
+
start,
|
346
|
+
end,
|
347
|
+
)
|
348
|
+
|
349
|
+
@signature(
|
350
|
+
{'type': 'string'},
|
351
|
+
{'type': 'string'},
|
352
|
+
{'type': 'number', 'optional': True},
|
353
|
+
{'type': 'number', 'optional': True})
|
354
|
+
def _func_find_last(self, text, search, start=0, end=None):
|
355
|
+
self._ensure_integer('find_last', 'start', start)
|
356
|
+
self._ensure_integer('find_last', 'end', end)
|
357
|
+
return self._find_impl(
|
358
|
+
text,
|
359
|
+
search,
|
360
|
+
lambda t, s: t.rfind(s),
|
361
|
+
start,
|
362
|
+
end,
|
363
|
+
)
|
364
|
+
|
365
|
+
def _find_impl(self, text, search, func, start, end):
|
366
|
+
if len(search) == 0:
|
367
|
+
return None
|
368
|
+
if end is None:
|
369
|
+
end = len(text)
|
370
|
+
|
371
|
+
pos = func(text[start:end], search)
|
372
|
+
if start < 0:
|
373
|
+
start = start + len(text)
|
374
|
+
|
375
|
+
# restrict resulting range to valid indices
|
376
|
+
start = min(max(start, 0), len(text))
|
377
|
+
return start + pos if pos != -1 else None
|
378
|
+
|
379
|
+
@signature(
|
380
|
+
{'type': 'string'},
|
381
|
+
{'type': 'number'},
|
382
|
+
{'type': 'string', 'optional': True},
|
383
|
+
)
|
384
|
+
def _func_pad_left(self, text, width, padding=' '):
|
385
|
+
self._ensure_non_negative_integer('pad_left', 'width', width)
|
386
|
+
return self._pad_impl(lambda: text.rjust(width, padding), padding)
|
387
|
+
|
388
|
+
@signature(
|
389
|
+
{'type': 'string'},
|
390
|
+
{'type': 'number'},
|
391
|
+
{'type': 'string', 'optional': True},
|
392
|
+
)
|
393
|
+
def _func_pad_right(self, text, width, padding=' '):
|
394
|
+
self._ensure_non_negative_integer('pad_right', 'width', width)
|
395
|
+
return self._pad_impl(lambda: text.ljust(width, padding), padding)
|
396
|
+
|
397
|
+
def _pad_impl(self, func, padding):
|
398
|
+
if len(padding) != 1:
|
399
|
+
raise exceptions.JmespathError(
|
400
|
+
f'syntax-error: pad_right() expects $padding to have a single character, but received '
|
401
|
+
f'`{padding}` instead.',
|
402
|
+
)
|
403
|
+
return func()
|
404
|
+
|
405
|
+
@signature(
|
406
|
+
{'type': 'string'},
|
407
|
+
{'type': 'string'},
|
408
|
+
{'type': 'string'},
|
409
|
+
{'type': 'number', 'optional': True},
|
410
|
+
)
|
411
|
+
def _func_replace(self, text, search, replacement, count=None):
|
412
|
+
self._ensure_non_negative_integer(
|
413
|
+
'replace',
|
414
|
+
'count',
|
415
|
+
count,
|
416
|
+
)
|
417
|
+
|
418
|
+
if count is not None:
|
419
|
+
return text.replace(search, replacement, int(count))
|
420
|
+
|
421
|
+
return text.replace(search, replacement)
|
422
|
+
|
423
|
+
@signature(
|
424
|
+
{'type': 'string'},
|
425
|
+
{'type': 'string'},
|
426
|
+
{'type': 'number', 'optional': True},
|
427
|
+
)
|
428
|
+
def _func_split(self, text, search, count=None):
|
429
|
+
self._ensure_non_negative_integer(
|
430
|
+
'split',
|
431
|
+
'count',
|
432
|
+
count,
|
433
|
+
)
|
434
|
+
|
435
|
+
if len(search) == 0:
|
436
|
+
chars = list(text)
|
437
|
+
if count is None:
|
438
|
+
return chars
|
439
|
+
|
440
|
+
head = list(chars[:count])
|
441
|
+
tail = [''.join(chars[count:])]
|
442
|
+
return head + tail
|
443
|
+
|
444
|
+
if count is not None:
|
445
|
+
return text.split(search, count)
|
446
|
+
|
447
|
+
return text.split(search)
|
448
|
+
|
449
|
+
def _ensure_integer(
|
450
|
+
self,
|
451
|
+
func_name,
|
452
|
+
param_name,
|
453
|
+
param_value,
|
454
|
+
):
|
455
|
+
if param_value is not None:
|
456
|
+
if int(param_value) != param_value:
|
457
|
+
raise exceptions.JmespathValueError(
|
458
|
+
func_name,
|
459
|
+
param_value,
|
460
|
+
'integer',
|
461
|
+
)
|
462
|
+
|
463
|
+
def _ensure_non_negative_integer(
|
464
|
+
self,
|
465
|
+
func_name,
|
466
|
+
param_name,
|
467
|
+
param_value,
|
468
|
+
):
|
469
|
+
if param_value is not None:
|
470
|
+
if int(param_value) != param_value or int(param_value) < 0:
|
471
|
+
raise exceptions.JmespathValueError(
|
472
|
+
func_name,
|
473
|
+
param_name,
|
474
|
+
'non-negative integer',
|
475
|
+
)
|
476
|
+
|
477
|
+
@signature({'type': 'string'}, {'type': 'string', 'optional': True})
|
478
|
+
def _func_trim(self, text, chars=None):
|
479
|
+
if chars is None or len(chars) == 0:
|
480
|
+
return text.strip()
|
481
|
+
return text.strip(chars)
|
482
|
+
|
483
|
+
@signature({'type': 'string'}, {'type': 'string', 'optional': True})
|
484
|
+
def _func_trim_left(self, text, chars=None):
|
485
|
+
if chars is None or len(chars) == 0:
|
486
|
+
return text.lstrip()
|
487
|
+
return text.lstrip(chars)
|
488
|
+
|
489
|
+
@signature({'type': 'string'}, {'type': 'string', 'optional': True})
|
490
|
+
def _func_trim_right(self, text, chars=None):
|
491
|
+
if chars is None or len(chars) == 0:
|
492
|
+
return text.rstrip()
|
493
|
+
return text.rstrip(chars)
|
494
|
+
|
292
495
|
@signature({'types': []})
|
293
496
|
def _func_type(self, arg):
|
294
497
|
if isinstance(arg, str):
|
@@ -310,6 +513,7 @@ class Functions(metaclass=FunctionRegistry):
|
|
310
513
|
def _func_sort_by(self, array, expref):
|
311
514
|
if not array:
|
312
515
|
return array
|
516
|
+
|
313
517
|
# sort_by allows for the expref to be either a number of a string, so we have some special logic to handle this.
|
314
518
|
# We evaluate the first array element and verify that it's either a string of a number. We then create a key
|
315
519
|
# function that validates that type, which requires that remaining array elements resolve to the same type as
|
@@ -324,7 +528,6 @@ class Functions(metaclass=FunctionRegistry):
|
|
324
528
|
)
|
325
529
|
|
326
530
|
keyfunc = self._create_key_func(expref, [required_type], 'sort_by')
|
327
|
-
|
328
531
|
return sorted(array, key=keyfunc)
|
329
532
|
|
330
533
|
@signature({'types': ['array']}, {'types': ['expref']})
|
@@ -353,13 +556,31 @@ class Functions(metaclass=FunctionRegistry):
|
|
353
556
|
else:
|
354
557
|
return None
|
355
558
|
|
559
|
+
@signature({'types': ['array'], 'variadic': True})
|
560
|
+
def _func_zip(self, *arguments):
|
561
|
+
return [list(t) for t in zip(*arguments)]
|
562
|
+
|
563
|
+
@signature({'types': ['array']}, {'types': ['expref']})
|
564
|
+
def _func_group_by(self, array, expref):
|
565
|
+
keyfunc = self._create_key_func(expref, ['null', 'string'], 'group_by')
|
566
|
+
if not array:
|
567
|
+
return None
|
568
|
+
|
569
|
+
result = {}
|
570
|
+
keys = list(dict.fromkeys([keyfunc(item) for item in array if keyfunc(item) is not None]))
|
571
|
+
for key in keys:
|
572
|
+
items = [item for item in array if keyfunc(item) == key]
|
573
|
+
result.update({key: items})
|
574
|
+
|
575
|
+
return result
|
576
|
+
|
356
577
|
def _create_key_func(self, expref, allowed_types, function_name):
|
357
578
|
def keyfunc(x):
|
358
579
|
result = expref.visit(expref.expression, x)
|
359
580
|
actual_typename = type(result).__name__
|
360
581
|
|
361
582
|
jmespath_type = self._convert_to_jmespath_type(actual_typename)
|
362
|
-
# allowed_types is in
|
583
|
+
# allowed_types is in terms of jmespath types, not python types.
|
363
584
|
if jmespath_type not in allowed_types:
|
364
585
|
raise exceptions.JmespathTypeError(
|
365
586
|
function_name, result, jmespath_type, allowed_types)
|
omlish/specs/jmespath/lexer.py
CHANGED
@@ -26,9 +26,20 @@ class Lexer:
|
|
26
26
|
')': 'rparen',
|
27
27
|
'{': 'lbrace',
|
28
28
|
'}': 'rbrace',
|
29
|
+
'+': 'plus',
|
30
|
+
'%': 'modulo',
|
31
|
+
'\u2212': 'minus',
|
32
|
+
'\u00d7': 'multiply',
|
33
|
+
'\u00f7': 'divide',
|
29
34
|
}
|
30
35
|
|
31
|
-
def
|
36
|
+
def __init__(self):
|
37
|
+
self._enable_legacy_literals = False
|
38
|
+
|
39
|
+
def tokenize(self, expression, options=None):
|
40
|
+
if options is not None:
|
41
|
+
self._enable_legacy_literals = options.enable_legacy_literals
|
42
|
+
|
32
43
|
self._initialize_for_expression(expression)
|
33
44
|
while self._current is not None:
|
34
45
|
if self._current in self.SIMPLE_TOKENS:
|
@@ -111,24 +122,48 @@ class Lexer:
|
|
111
122
|
}
|
112
123
|
|
113
124
|
elif self._current == '-':
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
buff = self._consume_number()
|
118
|
-
if len(buff) > 1:
|
125
|
+
if not self._peek_is_next_digit():
|
126
|
+
self._next()
|
119
127
|
yield {
|
120
|
-
'type': '
|
121
|
-
'value':
|
122
|
-
'start':
|
123
|
-
'end':
|
128
|
+
'type': 'minus',
|
129
|
+
'value': '-',
|
130
|
+
'start': self._position - 1,
|
131
|
+
'end': self._position,
|
124
132
|
}
|
133
|
+
else:
|
134
|
+
# Negative number.
|
135
|
+
start = self._position
|
136
|
+
buff = self._consume_number()
|
137
|
+
if len(buff) > 1:
|
138
|
+
yield {
|
139
|
+
'type': 'number',
|
140
|
+
'value': int(buff),
|
141
|
+
'start': start,
|
142
|
+
'end': start + len(buff),
|
143
|
+
}
|
144
|
+
else:
|
145
|
+
raise LexerError(
|
146
|
+
lexer_position=start,
|
147
|
+
lexer_value=buff,
|
148
|
+
message=f"Unknown token '{buff}'")
|
125
149
|
|
150
|
+
elif self._current == '/':
|
151
|
+
self._next()
|
152
|
+
if self._current == '/':
|
153
|
+
self._next()
|
154
|
+
yield {
|
155
|
+
'type': 'div',
|
156
|
+
'value': '//',
|
157
|
+
'start': self._position - 1,
|
158
|
+
'end': self._position,
|
159
|
+
}
|
126
160
|
else:
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
161
|
+
yield {
|
162
|
+
'type': 'divide',
|
163
|
+
'value': '/',
|
164
|
+
'start': self._position,
|
165
|
+
'end': self._position + 1,
|
166
|
+
}
|
132
167
|
|
133
168
|
elif self._current == '"':
|
134
169
|
yield self._consume_quoted_identifier()
|
@@ -143,33 +178,24 @@ class Lexer:
|
|
143
178
|
yield self._match_or_else('=', 'ne', 'not')
|
144
179
|
|
145
180
|
elif self._current == '=':
|
146
|
-
|
181
|
+
yield self._match_or_else('=', 'eq', 'assign')
|
182
|
+
|
183
|
+
elif self._current == '$':
|
184
|
+
if self._peek_may_be_valid_unquoted_identifier():
|
185
|
+
yield self._consume_variable()
|
186
|
+
else:
|
147
187
|
yield {
|
148
|
-
'type': '
|
149
|
-
'value':
|
150
|
-
'start': self._position
|
151
|
-
'end': self._position,
|
188
|
+
'type': 'root',
|
189
|
+
'value': self._current,
|
190
|
+
'start': self._position,
|
191
|
+
'end': self._position + 1,
|
152
192
|
}
|
153
193
|
self._next()
|
154
|
-
|
155
|
-
else:
|
156
|
-
if self._current is None:
|
157
|
-
# If we're at the EOF, we never advanced the position so we don't need to rewind it back one
|
158
|
-
# location.
|
159
|
-
position = self._position
|
160
|
-
else:
|
161
|
-
position = self._position - 1
|
162
|
-
raise LexerError(
|
163
|
-
lexer_position=position,
|
164
|
-
lexer_value='=',
|
165
|
-
message="Unknown token '='",
|
166
|
-
)
|
167
|
-
|
168
194
|
else:
|
169
195
|
raise LexerError(
|
170
196
|
lexer_position=self._position,
|
171
197
|
lexer_value=self._current,
|
172
|
-
message=f'Unknown token {self._current}',
|
198
|
+
message=f'Unknown token {self._current})',
|
173
199
|
)
|
174
200
|
|
175
201
|
yield {
|
@@ -187,6 +213,43 @@ class Lexer:
|
|
187
213
|
buff += self._current
|
188
214
|
return buff
|
189
215
|
|
216
|
+
def _consume_variable(self):
|
217
|
+
start = self._position
|
218
|
+
|
219
|
+
buff = self._current
|
220
|
+
self._next()
|
221
|
+
if self._current not in self.START_IDENTIFIER:
|
222
|
+
raise LexerError(
|
223
|
+
lexer_position=start,
|
224
|
+
lexer_value=self._current,
|
225
|
+
message=f'Invalid variable starting character {self._current}',
|
226
|
+
)
|
227
|
+
|
228
|
+
buff += self._current
|
229
|
+
while self._next() in self.VALID_IDENTIFIER:
|
230
|
+
buff += self._current
|
231
|
+
|
232
|
+
return {
|
233
|
+
'type': 'variable',
|
234
|
+
'value': buff,
|
235
|
+
'start': start,
|
236
|
+
'end': start + len(buff),
|
237
|
+
}
|
238
|
+
|
239
|
+
def _peek_may_be_valid_unquoted_identifier(self):
|
240
|
+
if (self._position == self._length - 1):
|
241
|
+
return False
|
242
|
+
else:
|
243
|
+
nxt = self._chars[self._position + 1]
|
244
|
+
return nxt in self.START_IDENTIFIER
|
245
|
+
|
246
|
+
def _peek_is_next_digit(self):
|
247
|
+
if (self._position == self._length - 1):
|
248
|
+
return False
|
249
|
+
else:
|
250
|
+
nxt = self._chars[self._position + 1]
|
251
|
+
return nxt in self.VALID_NUMBER
|
252
|
+
|
190
253
|
def _initialize_for_expression(self, expression):
|
191
254
|
if not expression:
|
192
255
|
raise EmptyExpressionError
|
@@ -233,15 +296,26 @@ class Lexer:
|
|
233
296
|
def _consume_literal(self):
|
234
297
|
start = self._position
|
235
298
|
|
236
|
-
|
299
|
+
token = self._consume_until('`')
|
300
|
+
lexeme = token.replace('\\`', '`')
|
301
|
+
parsed_json = None
|
237
302
|
try:
|
238
303
|
# Assume it is valid JSON and attempt to parse.
|
239
304
|
parsed_json = json.loads(lexeme)
|
240
305
|
except ValueError:
|
306
|
+
error = LexerError(
|
307
|
+
lexer_position=start,
|
308
|
+
lexer_value=self._expression[start:],
|
309
|
+
message=f'Bad token %s `{token}`',
|
310
|
+
)
|
311
|
+
|
312
|
+
if not self._enable_legacy_literals:
|
313
|
+
raise error # noqa
|
314
|
+
|
241
315
|
try:
|
242
316
|
# Invalid JSON values should be converted to quoted JSON strings during the JEP-12 deprecation period.
|
243
317
|
parsed_json = json.loads('"%s"' % lexeme.lstrip()) # noqa
|
244
|
-
warnings.warn('deprecated string literal syntax',
|
318
|
+
warnings.warn('deprecated string literal syntax', DeprecationWarning)
|
245
319
|
except ValueError:
|
246
320
|
raise LexerError( # noqa
|
247
321
|
lexer_position=start,
|
@@ -281,7 +355,10 @@ class Lexer:
|
|
281
355
|
def _consume_raw_string_literal(self):
|
282
356
|
start = self._position
|
283
357
|
|
284
|
-
lexeme = self._consume_until("'")
|
358
|
+
lexeme = self._consume_until("'") \
|
359
|
+
.replace("\\'", "'") \
|
360
|
+
.replace('\\\\', '\\')
|
361
|
+
|
285
362
|
token_len = self._position - start
|
286
363
|
return {
|
287
364
|
'type': 'literal',
|