Skip to content

Commit 5de7e07

Browse files
Merge pull request #1 from aerospike-community/pr-cherrypick
Use PRs for features we need
2 parents d7bec79 + 1c6b777 commit 5de7e07

File tree

11 files changed

+384
-84
lines changed

11 files changed

+384
-84
lines changed

README.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ Extensions
192192
+==============+==============================================+
193193
| len | - $.objects.`len` |
194194
+--------------+----------------------------------------------+
195+
| keys | - $.dict_field.`keys`(returns a list of keys)|
196+
+--------------+----------------------------------------------+
195197
| sub | - $.field.`sub(/foo\\\\+(.*)/, \\\\1)` |
196198
+--------------+----------------------------------------------+
197199
| split | - $.field.`split(+, 2, -1)` |
@@ -203,8 +205,10 @@ Extensions
203205
+--------------+----------------------------------------------+
204206
| filter | - $.objects[?(@some_field > 5)] |
205207
| | - $.objects[?some_field = "foobar")] |
206-
| | - $.objects[?some_field =~ "foobar")] |
207-
| | - $.objects[?some_field > 5 & other < 2)] |
208+
| | - $.objects[?some_field > 5 & other < 2)] and|
209+
| | - $.objects[?some_field>5 |some_field<2)] or |
210+
| | - $.objects[?(!(field>5 | field<2))] not|
211+
| | - $.objects[?@.field ~= "a.+a"] regex|
208212
+--------------+----------------------------------------------+
209213
| arithmetic | - $.foo + "_" + $.bar |
210214
| (-+*/) | - $.foo * 12 |

jsonpath_ng/ext/filter.py

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,15 @@
1414
import operator
1515
import re
1616
from six import moves
17+
import re
1718

1819
from .. import JSONPath, DatumInContext, Index
1920

21+
def contains(a,b):
22+
if re.search(b,a):
23+
return True
24+
return False
25+
2026

2127
OPERATOR_MAP = {
2228
'!=': operator.ne,
@@ -26,9 +32,37 @@
2632
'<': operator.lt,
2733
'>=': operator.ge,
2834
'>': operator.gt,
29-
'=~': lambda a, b: True if re.search(b, a) else False,
35+
'~=': contains
3036
}
3137

38+
def eval_exp(expressions,val):
39+
for expression in expressions:
40+
if type(expression)==tuple and expression[0]=='|':
41+
val1=eval_exp(expression[1],val)
42+
val2=eval_exp(expression[2],val)
43+
if (val1 or val2):
44+
return True
45+
else:
46+
return False
47+
if type(expression)==tuple and expression[0]=='&':
48+
val1=eval_exp(expression[1],val)
49+
val2=eval_exp(expression[2],val)
50+
if (val1 and val2):
51+
return True
52+
else:
53+
return False
54+
if type(expression)==tuple and expression[0]=='!':
55+
val1=eval_exp(expression[1],val)
56+
if (val1):
57+
return False
58+
else:
59+
return True
60+
else:
61+
if(len([expression])==len(list(filter(lambda x: x.find(val),[expression])))):
62+
return True
63+
else:
64+
return False
65+
3266

3367
class Filter(JSONPath):
3468
"""The JSONQuery filter"""
@@ -47,12 +81,11 @@ def find(self, datum):
4781

4882
if not isinstance(datum.value, list):
4983
return []
50-
51-
return [DatumInContext(datum.value[i], path=Index(i), context=datum)
52-
for i in moves.range(0, len(datum.value))
53-
if (len(self.expressions) ==
54-
len(list(filter(lambda x: x.find(datum.value[i]),
55-
self.expressions))))]
84+
res=[]
85+
for i in moves.range(0,len(datum.value)):
86+
if eval_exp(self.expressions,datum.value[i]):
87+
res.append(DatumInContext(datum.value[i], path=Index(i), context=datum))
88+
return(res)
5689

5790
def update(self, data, val):
5891
if type(data) is list:
@@ -86,7 +119,6 @@ def __init__(self, target, op, value):
86119

87120
def find(self, datum):
88121
datum = self.target.find(DatumInContext.wrap(datum))
89-
90122
if not datum:
91123
return []
92124
if self.op is None:

jsonpath_ng/ext/iterable.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,29 @@ def __str__(self):
9090

9191
def __repr__(self):
9292
return 'Len()'
93+
94+
class Keys(JSONPath):
95+
"""The JSONPath referring to the keys of the current object.
96+
97+
Concrete syntax is '`keys`'.
98+
"""
99+
100+
def find(self, datum):
101+
datum = DatumInContext.wrap(datum)
102+
try:
103+
value = datum.value.keys()
104+
except Exception as e:
105+
return []
106+
else:
107+
return [DatumInContext(value[i],
108+
context=None,
109+
path=Keys()) for i in range (0, len(datum.value))]
110+
111+
def __eq__(self, other):
112+
return isinstance(other, Keys)
113+
114+
def __str__(self):
115+
return '`keys`'
116+
117+
def __repr__(self):
118+
return 'Keys()'

jsonpath_ng/ext/parser.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from .. import lexer
1515
from .. import parser
1616
from .. import Fields, This, Child
17-
1817
from . import arithmetic as _arithmetic
1918
from . import filter as _filter
2019
from . import iterable as _iterable
@@ -23,12 +22,11 @@
2322

2423
class ExtendedJsonPathLexer(lexer.JsonPathLexer):
2524
"""Custom LALR-lexer for JsonPath"""
26-
literals = lexer.JsonPathLexer.literals + ['?', '@', '+', '*', '/', '-']
25+
literals = lexer.JsonPathLexer.literals + ['?', '@', '+', '*', '/', '-', '!','~']
2726
tokens = (['BOOL'] +
2827
parser.JsonPathLexer.tokens +
2928
['FILTER_OP', 'SORT_DIRECTION', 'FLOAT'])
30-
31-
t_FILTER_OP = r'=~|==?|<=|>=|!=|<|>'
29+
t_FILTER_OP = r'==?|<=|>=|!=|<|>|~='
3230

3331
def t_BOOL(self, t):
3432
r'true|false'
@@ -94,6 +92,8 @@ def p_jsonpath_named_operator(self, p):
9492
"jsonpath : NAMED_OPERATOR"
9593
if p[1] == 'len':
9694
p[0] = _iterable.Len()
95+
elif p[1] == 'keys':
96+
p[0] = _iterable.Keys()
9797
elif p[1] == 'sorted':
9898
p[0] = _iterable.SortedThis()
9999
elif p[1].startswith("split("):
@@ -121,11 +121,18 @@ def p_expression(self, p):
121121
def p_expressions_expression(self, p):
122122
"expressions : expression"
123123
p[0] = [p[1]]
124-
124+
125+
def p_expressions_not(self, p):
126+
"expressions : '!' expressions"
127+
p[0]=[('!',p[2])]
128+
125129
def p_expressions_and(self, p):
126130
"expressions : expressions '&' expressions"
127-
# TODO(sileht): implements '|'
128-
p[0] = p[1] + p[3]
131+
p[0] = [('&',p[1],p[3])]
132+
133+
def p_expressions_or(self, p):
134+
"expressions : expressions '|' expressions"
135+
p[0] = [('|',p[1],p[3])]
129136

130137
def p_expressions_parens(self, p):
131138
"expressions : '(' expressions ')'"

jsonpath_ng/jsonpath.py

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -606,8 +606,8 @@ class Index(JSONPath):
606606
NOTE: For the concrete syntax of `[*]`, the abstract syntax is a Slice() with no parameters (equiv to `[:]`
607607
"""
608608

609-
def __init__(self, index):
610-
self.index = index
609+
def __init__(self, *indices):
610+
self.indices = indices
611611

612612
def find(self, datum):
613613
return self._find_base(datum, create=False)
@@ -621,10 +621,12 @@ def _find_base(self, datum, create):
621621
if datum.value == {}:
622622
datum.value = _create_list_key(datum.value)
623623
self._pad_value(datum.value)
624-
if datum.value and len(datum.value) > self.index:
625-
return [DatumInContext(datum.value[self.index], path=self, context=datum)]
626-
else:
627-
return []
624+
rv = []
625+
for index in self.indices:
626+
# invalid indices do not crash, return [] instead
627+
if datum.value and len(datum.value) > index:
628+
rv += [DatumInContext(datum.value[index], path=Index(index), context=datum)]
629+
return rv
628630

629631
def update(self, data, val):
630632
return self._update_base(data, val, create=False)
@@ -638,31 +640,39 @@ def _update_base(self, data, val, create):
638640
data = _create_list_key(data)
639641
self._pad_value(data)
640642
if hasattr(val, '__call__'):
641-
val.__call__(data[self.index], data, self.index)
642-
elif len(data) > self.index:
643-
data[self.index] = val
643+
for index in self.indices:
644+
val.__call__(data[index], data, index)
645+
else:
646+
if not isinstance(val, list):
647+
val = [val]
648+
# allows somelist[5,1,2] = [some_value, another_value, third_value]
649+
# skip the indices that are too high but the value will be applied to the next index
650+
for index in self.indices:
651+
if len(data) > index:
652+
data[index] = val.pop(0)
644653
return data
645654

646655
def filter(self, fn, data):
647-
if fn(data[self.index]):
648-
data.pop(self.index) # relies on mutation :(
656+
for index in self.indices:
657+
if fn(data[index]):
658+
data.pop(index) # relies on mutation :(
649659
return data
650660

651661
def __eq__(self, other):
652-
return isinstance(other, Index) and self.index == other.index
662+
return isinstance(other, Index) and sorted(self.indices) == sorted(other.indices)
653663

654664
def __str__(self):
655-
return '[%i]' % self.index
665+
return str(list(self.indices))
656666

657667
def __repr__(self):
658-
return '%s(index=%r)' % (self.__class__.__name__, self.index)
668+
return '%s(indices=%r)' % (self.__class__.__name__, self.indices)
659669

660670
def _pad_value(self, value):
661-
if len(value) <= self.index:
662-
pad = self.index - len(value) + 1
671+
_max = max(self.indices)
672+
if len(value) <= _max:
673+
pad = _max - len(value) + 1
663674
value += [{} for __ in range(pad)]
664675

665-
666676
class Slice(JSONPath):
667677
"""
668678
JSONPath matching a slice of an array.
@@ -709,7 +719,7 @@ def find(self, datum):
709719
return [DatumInContext(datum.value[i], path=Index(i), context=datum) for i in xrange(0, len(datum.value))]
710720
else:
711721
return [DatumInContext(datum.value[i], path=Index(i), context=datum) for i in range(0, len(datum.value))[self.start:self.end:self.step]]
712-
722+
713723
def update(self, data, val):
714724
for datum in self.find(data):
715725
datum.path.update(data, val)

jsonpath_ng/parser.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def p_jsonpath_root(self, p):
120120

121121
def p_jsonpath_idx(self, p):
122122
"jsonpath : '[' idx ']'"
123-
p[0] = p[2]
123+
p[0] = Index(*p[2])
124124

125125
def p_jsonpath_slice(self, p):
126126
"jsonpath : '[' slice ']'"
@@ -136,7 +136,7 @@ def p_jsonpath_child_fieldbrackets(self, p):
136136

137137
def p_jsonpath_child_idxbrackets(self, p):
138138
"jsonpath : jsonpath '[' idx ']'"
139-
p[0] = Child(p[1], p[3])
139+
p[0] = Child(p[1], Index(*p[3]))
140140

141141
def p_jsonpath_child_slicebrackets(self, p):
142142
"jsonpath : jsonpath '[' slice ']'"
@@ -165,15 +165,20 @@ def p_fields_comma(self, p):
165165

166166
def p_idx(self, p):
167167
"idx : NUMBER"
168-
p[0] = Index(p[1])
168+
p[0] = [p[1]]
169169

170+
def p_idx_comma(self, p):
171+
"idx : idx ',' idx "
172+
p[0] = p[1] + p[3]
173+
170174
def p_slice_any(self, p):
171175
"slice : '*'"
172176
p[0] = Slice()
173177

174178
def p_slice(self, p): # Currently does not support `step`
175-
"slice : maybe_int ':' maybe_int"
176-
p[0] = Slice(start=p[1], end=p[3])
179+
"""slice : maybe_int ':' maybe_int
180+
| maybe_int ':' maybe_int ':' maybe_int """
181+
p[0] = Slice(*p[1::2])
177182

178183
def p_maybe_int(self, p):
179184
"""maybe_int : NUMBER

0 commit comments

Comments
 (0)