Skip to content

Use PRs for features we need #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ Extensions
+==============+==============================================+
| len | - $.objects.`len` |
+--------------+----------------------------------------------+
| keys | - $.dict_field.`keys`(returns a list of keys)|
+--------------+----------------------------------------------+
| sub | - $.field.`sub(/foo\\\\+(.*)/, \\\\1)` |
+--------------+----------------------------------------------+
| split | - $.field.`split(+, 2, -1)` |
Expand All @@ -203,8 +205,10 @@ Extensions
+--------------+----------------------------------------------+
| filter | - $.objects[?(@some_field > 5)] |
| | - $.objects[?some_field = "foobar")] |
| | - $.objects[?some_field =~ "foobar")] |
| | - $.objects[?some_field > 5 & other < 2)] |
| | - $.objects[?some_field > 5 & other < 2)] and|
| | - $.objects[?some_field>5 |some_field<2)] or |
| | - $.objects[?(!(field>5 | field<2))] not|
| | - $.objects[?@.field ~= "a.+a"] regex|
+--------------+----------------------------------------------+
| arithmetic | - $.foo + "_" + $.bar |
| (-+*/) | - $.foo * 12 |
Expand Down
48 changes: 40 additions & 8 deletions jsonpath_ng/ext/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,15 @@
import operator
import re
from six import moves
import re

from .. import JSONPath, DatumInContext, Index

def contains(a,b):
if re.search(b,a):
return True
return False


OPERATOR_MAP = {
'!=': operator.ne,
Expand All @@ -26,9 +32,37 @@
'<': operator.lt,
'>=': operator.ge,
'>': operator.gt,
'=~': lambda a, b: True if re.search(b, a) else False,
'~=': contains
}

def eval_exp(expressions,val):
for expression in expressions:
if type(expression)==tuple and expression[0]=='|':
val1=eval_exp(expression[1],val)
val2=eval_exp(expression[2],val)
if (val1 or val2):
return True
else:
return False
if type(expression)==tuple and expression[0]=='&':
val1=eval_exp(expression[1],val)
val2=eval_exp(expression[2],val)
if (val1 and val2):
return True
else:
return False
if type(expression)==tuple and expression[0]=='!':
val1=eval_exp(expression[1],val)
if (val1):
return False
else:
return True
else:
if(len([expression])==len(list(filter(lambda x: x.find(val),[expression])))):
return True
else:
return False


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

if not isinstance(datum.value, list):
return []

return [DatumInContext(datum.value[i], path=Index(i), context=datum)
for i in moves.range(0, len(datum.value))
if (len(self.expressions) ==
len(list(filter(lambda x: x.find(datum.value[i]),
self.expressions))))]
res=[]
for i in moves.range(0,len(datum.value)):
if eval_exp(self.expressions,datum.value[i]):
res.append(DatumInContext(datum.value[i], path=Index(i), context=datum))
return(res)

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

def find(self, datum):
datum = self.target.find(DatumInContext.wrap(datum))

if not datum:
return []
if self.op is None:
Expand Down
26 changes: 26 additions & 0 deletions jsonpath_ng/ext/iterable.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,29 @@ def __str__(self):

def __repr__(self):
return 'Len()'

class Keys(JSONPath):
"""The JSONPath referring to the keys of the current object.

Concrete syntax is '`keys`'.
"""

def find(self, datum):
datum = DatumInContext.wrap(datum)
try:
value = datum.value.keys()
except Exception as e:
return []
else:
return [DatumInContext(value[i],
context=None,
path=Keys()) for i in range (0, len(datum.value))]

def __eq__(self, other):
return isinstance(other, Keys)

def __str__(self):
return '`keys`'

def __repr__(self):
return 'Keys()'
21 changes: 14 additions & 7 deletions jsonpath_ng/ext/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from .. import lexer
from .. import parser
from .. import Fields, This, Child

from . import arithmetic as _arithmetic
from . import filter as _filter
from . import iterable as _iterable
Expand All @@ -23,12 +22,11 @@

class ExtendedJsonPathLexer(lexer.JsonPathLexer):
"""Custom LALR-lexer for JsonPath"""
literals = lexer.JsonPathLexer.literals + ['?', '@', '+', '*', '/', '-']
literals = lexer.JsonPathLexer.literals + ['?', '@', '+', '*', '/', '-', '!','~']
tokens = (['BOOL'] +
parser.JsonPathLexer.tokens +
['FILTER_OP', 'SORT_DIRECTION', 'FLOAT'])

t_FILTER_OP = r'=~|==?|<=|>=|!=|<|>'
t_FILTER_OP = r'==?|<=|>=|!=|<|>|~='

def t_BOOL(self, t):
r'true|false'
Expand Down Expand Up @@ -94,6 +92,8 @@ def p_jsonpath_named_operator(self, p):
"jsonpath : NAMED_OPERATOR"
if p[1] == 'len':
p[0] = _iterable.Len()
elif p[1] == 'keys':
p[0] = _iterable.Keys()
elif p[1] == 'sorted':
p[0] = _iterable.SortedThis()
elif p[1].startswith("split("):
Expand Down Expand Up @@ -121,11 +121,18 @@ def p_expression(self, p):
def p_expressions_expression(self, p):
"expressions : expression"
p[0] = [p[1]]


def p_expressions_not(self, p):
"expressions : '!' expressions"
p[0]=[('!',p[2])]

def p_expressions_and(self, p):
"expressions : expressions '&' expressions"
# TODO(sileht): implements '|'
p[0] = p[1] + p[3]
p[0] = [('&',p[1],p[3])]

def p_expressions_or(self, p):
"expressions : expressions '|' expressions"
p[0] = [('|',p[1],p[3])]

def p_expressions_parens(self, p):
"expressions : '(' expressions ')'"
Expand Down
46 changes: 28 additions & 18 deletions jsonpath_ng/jsonpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,8 +606,8 @@ class Index(JSONPath):
NOTE: For the concrete syntax of `[*]`, the abstract syntax is a Slice() with no parameters (equiv to `[:]`
"""

def __init__(self, index):
self.index = index
def __init__(self, *indices):
self.indices = indices

def find(self, datum):
return self._find_base(datum, create=False)
Expand All @@ -621,10 +621,12 @@ def _find_base(self, datum, create):
if datum.value == {}:
datum.value = _create_list_key(datum.value)
self._pad_value(datum.value)
if datum.value and len(datum.value) > self.index:
return [DatumInContext(datum.value[self.index], path=self, context=datum)]
else:
return []
rv = []
for index in self.indices:
# invalid indices do not crash, return [] instead
if datum.value and len(datum.value) > index:
rv += [DatumInContext(datum.value[index], path=Index(index), context=datum)]
return rv

def update(self, data, val):
return self._update_base(data, val, create=False)
Expand All @@ -638,31 +640,39 @@ def _update_base(self, data, val, create):
data = _create_list_key(data)
self._pad_value(data)
if hasattr(val, '__call__'):
val.__call__(data[self.index], data, self.index)
elif len(data) > self.index:
data[self.index] = val
for index in self.indices:
val.__call__(data[index], data, index)
else:
if not isinstance(val, list):
val = [val]
# allows somelist[5,1,2] = [some_value, another_value, third_value]
# skip the indices that are too high but the value will be applied to the next index
for index in self.indices:
if len(data) > index:
data[index] = val.pop(0)
return data

def filter(self, fn, data):
if fn(data[self.index]):
data.pop(self.index) # relies on mutation :(
for index in self.indices:
if fn(data[index]):
data.pop(index) # relies on mutation :(
return data

def __eq__(self, other):
return isinstance(other, Index) and self.index == other.index
return isinstance(other, Index) and sorted(self.indices) == sorted(other.indices)

def __str__(self):
return '[%i]' % self.index
return str(list(self.indices))

def __repr__(self):
return '%s(index=%r)' % (self.__class__.__name__, self.index)
return '%s(indices=%r)' % (self.__class__.__name__, self.indices)

def _pad_value(self, value):
if len(value) <= self.index:
pad = self.index - len(value) + 1
_max = max(self.indices)
if len(value) <= _max:
pad = _max - len(value) + 1
value += [{} for __ in range(pad)]


class Slice(JSONPath):
"""
JSONPath matching a slice of an array.
Expand Down Expand Up @@ -709,7 +719,7 @@ def find(self, datum):
return [DatumInContext(datum.value[i], path=Index(i), context=datum) for i in xrange(0, len(datum.value))]
else:
return [DatumInContext(datum.value[i], path=Index(i), context=datum) for i in range(0, len(datum.value))[self.start:self.end:self.step]]

def update(self, data, val):
for datum in self.find(data):
datum.path.update(data, val)
Expand Down
15 changes: 10 additions & 5 deletions jsonpath_ng/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def p_jsonpath_root(self, p):

def p_jsonpath_idx(self, p):
"jsonpath : '[' idx ']'"
p[0] = p[2]
p[0] = Index(*p[2])

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

def p_jsonpath_child_idxbrackets(self, p):
"jsonpath : jsonpath '[' idx ']'"
p[0] = Child(p[1], p[3])
p[0] = Child(p[1], Index(*p[3]))

def p_jsonpath_child_slicebrackets(self, p):
"jsonpath : jsonpath '[' slice ']'"
Expand Down Expand Up @@ -165,15 +165,20 @@ def p_fields_comma(self, p):

def p_idx(self, p):
"idx : NUMBER"
p[0] = Index(p[1])
p[0] = [p[1]]

def p_idx_comma(self, p):
"idx : idx ',' idx "
p[0] = p[1] + p[3]

def p_slice_any(self, p):
"slice : '*'"
p[0] = Slice()

def p_slice(self, p): # Currently does not support `step`
"slice : maybe_int ':' maybe_int"
p[0] = Slice(start=p[1], end=p[3])
"""slice : maybe_int ':' maybe_int
| maybe_int ':' maybe_int ':' maybe_int """
p[0] = Slice(*p[1::2])

def p_maybe_int(self, p):
"""maybe_int : NUMBER
Expand Down
Loading