Skip to content

Commit e394b53

Browse files
committed
Merge tag 'JEP-14' into feature/vnext-preview
2 parents 49e084b + f71d99d commit e394b53

File tree

3 files changed

+400
-5
lines changed

3 files changed

+400
-5
lines changed

jmespath/functions.py

Lines changed: 186 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,22 +93,28 @@ def call_function(self, function_name, resolved_args, *args, **kwargs):
9393
return function(self, *resolved_args)
9494

9595
def _validate_arguments(self, args, signature, function_name):
96-
if signature and signature[-1].get('variadic'):
96+
required_arguments_count = len([param for param in signature if not param.get('optional') or not param['optional']])
97+
optional_arguments_count = len([param for param in signature if param.get('optional') and param['optional']])
98+
has_variadic = signature[-1].get('variadic') if signature != None else False
99+
if has_variadic:
97100
if len(args) < len(signature):
98101
raise exceptions.VariadictArityError(
99102
len(signature), len(args), function_name)
100-
elif len(args) != len(signature):
103+
elif optional_arguments_count > 0:
104+
if len(args) < required_arguments_count or len(args) > (required_arguments_count + optional_arguments_count):
105+
raise exceptions.ArityError(
106+
len(signature), len(args), function_name)
107+
elif len(args) != required_arguments_count:
101108
raise exceptions.ArityError(
102109
len(signature), len(args), function_name)
103110
return self._type_check(args, signature, function_name)
104111

105112
def _type_check(self, actual, signature, function_name):
106-
for i in range(len(signature)):
107-
allowed_types = signature[i]['types']
113+
for i in range(min(len(signature), len(actual))):
114+
allowed_types = self._get_allowed_types_from_signature(signature[i])
108115
if allowed_types:
109116
self._type_check_single(actual[i], allowed_types,
110117
function_name)
111-
112118
def _type_check_single(self, current, types, function_name):
113119
# Type checking involves checking the top level type,
114120
# and in the case of arrays, potentially checking the types
@@ -132,6 +138,13 @@ def _type_check_single(self, current, types, function_name):
132138
self._subtype_check(current, allowed_subtypes,
133139
types, function_name)
134140

141+
## signature supports monotype {'type': 'type-name'}
142+
## or multiple types {'types': ['type1-name', 'type2-name']}
143+
def _get_allowed_types_from_signature(self, spec):
144+
if spec.get('type'):
145+
spec.update({'types': [spec.get('type')]})
146+
return spec.get('types')
147+
135148
def _get_allowed_pytypes(self, types):
136149
allowed_types = []
137150
allowed_subtypes = []
@@ -176,6 +189,14 @@ def _subtype_check(self, current, allowed_subtypes, types, function_name):
176189
@signature({'types': ['number']})
177190
def _func_abs(self, arg):
178191
return abs(arg)
192+
193+
@signature({'types': ['string']})
194+
def _func_lower(self, arg):
195+
return arg.lower()
196+
197+
@signature({'types': ['string']})
198+
def _func_upper(self, arg):
199+
return arg.upper()
179200

180201
@signature({'types': ['array-number']})
181202
def _func_avg(self, arg):
@@ -307,6 +328,166 @@ def _func_keys(self, arg):
307328
# should we also return the indices of a list?
308329
return list(arg.keys())
309330

331+
@signature(
332+
{'type': 'string'},
333+
{'type': 'string'},
334+
{'type': 'number', 'optional': True},
335+
{'type': 'number', 'optional': True})
336+
def _func_find_first(self, text, search, start = 0, end = None):
337+
self._ensure_integer('find_first', start, start)
338+
self._ensure_integer('find_first', end, end)
339+
return self._find_impl(
340+
text,
341+
search,
342+
lambda t, s: t.find(s),
343+
start,
344+
end
345+
)
346+
347+
@signature(
348+
{'type': 'string'},
349+
{'type': 'string'},
350+
{'type': 'number', 'optional': True},
351+
{'type': 'number', 'optional': True})
352+
def _func_find_last(self, text, search, start = 0, end = None):
353+
self._ensure_integer('find_last', start, start)
354+
self._ensure_integer('find_last', end, end)
355+
return self._find_impl(
356+
text,
357+
search,
358+
lambda t, s: t.rfind(s),
359+
start,
360+
end
361+
)
362+
363+
def _find_impl(self, text, search, func, start, end):
364+
if len(search) == 0:
365+
return None
366+
if end == None:
367+
end = len(text)
368+
369+
pos = func(text[start:end], search)
370+
if start < 0:
371+
start = start + len(text)
372+
373+
# restrict resulting range to valid indices
374+
start = min(max(start, 0), len(text))
375+
return start + pos if pos != -1 else None
376+
377+
@signature(
378+
{'type': 'string'},
379+
{'type': 'number'},
380+
{'type': 'string', 'optional': True})
381+
def _func_pad_left(self, text, width, padding = ' '):
382+
self._ensure_non_negative_integer('pad_left', 'width', width)
383+
return self._pad_impl(lambda : text.rjust(width, padding), padding)
384+
385+
@signature(
386+
{'type': 'string'},
387+
{'type': 'number'},
388+
{'type': 'string', 'optional': True})
389+
def _func_pad_right(self, text, width, padding = ' '):
390+
self._ensure_non_negative_integer('pad_left', 'width', width)
391+
return self._pad_impl(lambda : text.ljust(width, padding), padding)
392+
393+
def _pad_impl(self, func, padding):
394+
if len(padding) != 1:
395+
raise exceptions.JMESPathError(
396+
'syntax-error: pad_right() expects $padding to have a '
397+
'single character, but received `{}` instead.'
398+
.format(padding))
399+
return func()
400+
401+
@signature(
402+
{'type': 'string'},
403+
{'type': 'string'},
404+
{'type': 'string'},
405+
{'type': 'number', 'optional': True})
406+
def _func_replace(self, text, search, replacement, count = None):
407+
self._ensure_non_negative_integer(
408+
'replace',
409+
'count',
410+
count)
411+
412+
if count != None:
413+
return text.replace(search, replacement, int(count))
414+
return text.replace(search, replacement)
415+
416+
@signature(
417+
{'type': 'string'},
418+
{'type': 'string'},
419+
{'type': 'number', 'optional': True})
420+
def _func_split(self, text, search, count = None):
421+
self._ensure_non_negative_integer(
422+
'split',
423+
'count',
424+
count)
425+
426+
if len(search) == 0:
427+
chars = list(text)
428+
if count == None:
429+
return chars
430+
431+
head = [c for c in chars[:count]]
432+
tail = [''.join(chars[count:])]
433+
return head + tail
434+
435+
if count != None:
436+
return text.split(search, count)
437+
return text.split(search)
438+
439+
def _ensure_integer(
440+
self,
441+
func_name,
442+
param_name,
443+
param_value):
444+
445+
if param_value != None:
446+
if int(param_value) != param_value:
447+
raise exceptions.JMESPathError(
448+
'invalid-type: {}() expects ${} to be a '
449+
'integer, but received {} instead.'
450+
.format(
451+
func_name,
452+
param_name,
453+
param_value
454+
))
455+
456+
def _ensure_non_negative_integer(
457+
self,
458+
func_name,
459+
param_name,
460+
param_value):
461+
462+
if param_value != None:
463+
if int(param_value) != param_value or int(param_value) < 0:
464+
raise exceptions.JMESPathError(
465+
'invalid-type: {}() expects ${} to be a '
466+
'non-negative integer, but received {} instead.'
467+
.format(
468+
func_name,
469+
param_name,
470+
param_value
471+
))
472+
473+
@signature({'type': 'string'}, {'type': 'string', 'optional': True})
474+
def _func_trim(self, text, chars = None):
475+
if chars == None or len(chars) == 0:
476+
return text.strip()
477+
return text.strip(chars)
478+
479+
@signature({'type': 'string'}, {'type': 'string', 'optional': True})
480+
def _func_trim_left(self, text, chars = None):
481+
if chars == None or len(chars) == 0:
482+
return text.lstrip()
483+
return text.lstrip(chars)
484+
485+
@signature({'type': 'string'}, {'type': 'string', 'optional': True})
486+
def _func_trim_right(self, text, chars = None):
487+
if chars == None or len(chars) == 0:
488+
return text.rstrip()
489+
return text.rstrip(chars)
490+
310491
@signature({"types": ['object']})
311492
def _func_values(self, arg):
312493
return list(arg.values())
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
[
2+
{
3+
"given": {
4+
"abab": "aabaaabaaaab",
5+
"string": "subject string",
6+
"split": "avg|-|min|-|max|-|mean|-|mode|-|median"
7+
},
8+
"cases": [
9+
{
10+
"expression": "find_first(string, 'string', `1`, `2`, `3`)",
11+
"error": "invalid-arity"
12+
},
13+
{
14+
"expression": "find_first(@, 'string', `1`, `2`)",
15+
"error": "invalid-type"
16+
},
17+
{
18+
"expression": "find_first(string, 'string', '1')",
19+
"error": "invalid-type"
20+
},
21+
{
22+
"expression": "find_first(string, 'string', `1`, '2')",
23+
"error": "invalid-type"
24+
},
25+
{
26+
"expression": "find_first(string, 'string', `1.3`, '2')",
27+
"error": "invalid-type"
28+
},
29+
{
30+
"expression": "find_first(string, 'string', `1`, '2.4')",
31+
"error": "invalid-type"
32+
},
33+
34+
{ "expression": "find_first(string, 'string')", "result": 8 },
35+
{ "expression": "find_first(string, 'string', `0`)", "result": 8 },
36+
{ "expression": "find_first(string, 'string', `0`, `14`)", "result": 8 },
37+
{ "expression": "find_first(string, 'string', `-6`)", "result": 8 },
38+
{ "expression": "find_first(string, 'string', `-99`, `100`)", "result": 8 },
39+
{ "expression": "find_first(string, 'string', `0`, `13`)", "result": null },
40+
{ "expression": "find_first(string, 'string', `8`)", "result": 8 },
41+
{ "expression": "find_first(string, 'string', `8`, `11`)", "result": null },
42+
{ "expression": "find_first(string, 'string', `9`)", "result": null },
43+
{ "expression": "find_first(string, 's')", "result": 0 },
44+
{ "expression": "find_first(string, 's', `1`)", "result": 8 },
45+
{ "expression": "find_first(string, '')", "result": null },
46+
{ "expression": "find_first('', '')", "result": null },
47+
48+
{ "expression": "find_last(string, 'string')", "result": 8 },
49+
{ "expression": "find_last(string, 'string', `8`)", "result": 8 },
50+
{ "expression": "find_last(string, 'string', `8`, `9`)", "result": null },
51+
{ "expression": "find_last(string, 'string', `9`)", "result": null },
52+
{ "expression": "find_last(string, 's', `1`)", "result": 8 },
53+
{ "expression": "find_last(string, 's', `-6`)", "result": 8 },
54+
{ "expression": "find_last(string, 's', `0`, `7`)", "result": 0 },
55+
{ "expression": "find_last(string, '')", "result": null },
56+
{ "expression": "find_last('', '')", "result": null },
57+
58+
{ "expression": "lower('STRING')", "result": "string" },
59+
{ "expression": "upper('string')", "result": "STRING" },
60+
61+
{
62+
"expression": "replace(abab, 'aa', '-', `0.333333`)",
63+
"error": "invalid-type"
64+
},
65+
66+
{
67+
"expression": "replace(abab, 'aa', '-', `0.001`)",
68+
"error": "invalid-type"
69+
},
70+
71+
{ "expression": "replace(abab, 'aa', '-', `0`)", "result": "aabaaabaaaab" },
72+
{ "expression": "replace(abab, 'aa', '-', `1`)", "result": "-baaabaaaab" },
73+
{ "expression": "replace(abab, 'aa', '-', `2`)", "result": "-b-abaaaab" },
74+
{ "expression": "replace(abab, 'aa', '-', `3`)", "result": "-b-ab-aab" },
75+
{ "expression": "replace(abab, 'aa', '-')", "result": "-b-ab--b" },
76+
77+
{ "expression": "trim(' subject string ')", "result": "subject string" },
78+
{ "expression": "trim(' subject string ', '')", "result": "subject string" },
79+
{ "expression": "trim(' subject string ', ' ')", "result": "subject string" },
80+
{ "expression": "trim(' subject string ', 's')", "result": " subject string " },
81+
{ "expression": "trim(' subject string ', 'su')", "result": " subject string " },
82+
{ "expression": "trim(' subject string ', 'su ')", "result": "bject string" },
83+
{ "expression": "trim(' subject string ', 'gsu ')", "result": "bject strin" },
84+
85+
{
86+
"expression": "trim('\u0009\u000A\u000B\u000C\u000D\u0020\u0085\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000')",
87+
"result": ""
88+
},
89+
90+
{ "expression": "trim_left(' subject string ')", "result": "subject string " },
91+
{ "expression": "trim_left(' subject string ', 's')", "result": " subject string " },
92+
{ "expression": "trim_left(' subject string ', 'su')", "result": " subject string " },
93+
{ "expression": "trim_left(' subject string ', 'su ')", "result": "bject string " },
94+
{ "expression": "trim_left(' subject string ', 'gsu ')", "result": "bject string " },
95+
96+
{ "expression": "trim_right(' subject string ')", "result": " subject string" },
97+
{ "expression": "trim_right(' subject string ', 's')", "result": " subject string " },
98+
{ "expression": "trim_right(' subject string ', 'su')", "result": " subject string " },
99+
{ "expression": "trim_right(' subject string ', 'su ')", "result": " subject string" },
100+
{ "expression": "trim_right(' subject string ', 'gsu ')", "result": " subject strin" },
101+
102+
{
103+
"expression": "pad_left('string', '1')",
104+
"error": "syntax"
105+
106+
},
107+
{
108+
"expression": "pad_left('string', `1`, '--')",
109+
"error": "syntax"
110+
111+
},
112+
{
113+
"expression": "pad_left('string', '1.4')",
114+
"error": "invalid-type"
115+
116+
},
117+
118+
{ "expression": "pad_left('string', `0`)", "result": "string" },
119+
{ "expression": "pad_left('string', `5`)", "result": "string" },
120+
{ "expression": "pad_left('string', `10`)", "result": " string" },
121+
{ "expression": "pad_left('string', `10`, '-')", "result": "----string" },
122+
123+
{ "expression": "pad_right('string', `0`)", "result": "string" },
124+
{ "expression": "pad_right('string', `5`)", "result": "string" },
125+
{ "expression": "pad_right('string', `10`)", "result": "string " },
126+
{ "expression": "pad_right('string', `10`, '-')", "result": "string----" },
127+
128+
{
129+
"expression": "split('/', '/', `3.7`)",
130+
"error": "invalid-type"
131+
},
132+
133+
{ "expression": "split('/', '/')", "result": [ "", "" ] },
134+
{ "expression": "split('', '')", "result": [ ] },
135+
{ "expression": "split('all chars', '')", "result": [ "a", "l", "l", " ", "c", "h", "a", "r", "s" ] },
136+
{ "expression": "split('all chars', '', `3`)", "result": [ "a", "l", "l", " chars" ] },
137+
138+
{ "expression": "split(split, '|-|')", "result": [ "avg", "min", "max", "mean", "mode", "median" ] },
139+
{ "expression": "split(split, '|-|', `3`)", "result": [ "avg", "min", "max", "mean|-|mode|-|median" ] },
140+
{ "expression": "split(split, '|-|', `2`)", "result": [ "avg", "min", "max|-|mean|-|mode|-|median" ] },
141+
{ "expression": "split(split, '|-|', `1`)", "result": [ "avg", "min|-|max|-|mean|-|mode|-|median" ] },
142+
{ "expression": "split(split, '|-|', `0`)", "result": [ "avg|-|min|-|max|-|mean|-|mode|-|median" ] }
143+
]
144+
}
145+
]

0 commit comments

Comments
 (0)