summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKenn Knowles <kenn.knowles@gmail.com>2013-03-27 15:39:18 -0400
committerKenn Knowles <kenn.knowles@gmail.com>2013-03-27 15:39:18 -0400
commitd9d2a097975e003fb4ac8517393b9bf0811d3d26 (patch)
treeecabafff690126ba2adf13bc161ab7f40b5e0c08
parent5219ef90ea7540604ce475a18e4a6e140bd8b710 (diff)
downloadjsonpath-rw-d9d2a097975e003fb4ac8517393b9bf0811d3d26.tar.gz
Switch @ for `this` since it is so common in field names (for example when JSON is generated from XML)
-rw-r--r--jsonpath_rw/jsonpath.py2
-rw-r--r--jsonpath_rw/lexer.py28
-rw-r--r--jsonpath_rw/parser.py9
-rw-r--r--tests/test_jsonpath.py21
-rw-r--r--tests/test_lexer.py2
5 files changed, 44 insertions, 18 deletions
diff --git a/jsonpath_rw/jsonpath.py b/jsonpath_rw/jsonpath.py
index 563e147..8af0e87 100644
--- a/jsonpath_rw/jsonpath.py
+++ b/jsonpath_rw/jsonpath.py
@@ -198,7 +198,7 @@ class This(JSONPath):
return val
def __str__(self):
- return '@'
+ return '`this`'
def __repr__(self):
return 'This()'
diff --git a/jsonpath_rw/lexer.py b/jsonpath_rw/lexer.py
index c7e42fd..35a4305 100644
--- a/jsonpath_rw/lexer.py
+++ b/jsonpath_rw/lexer.py
@@ -37,21 +37,22 @@ class JsonPathLexer(object):
#
# Anyhow, it is pythonic to give some rope to hang oneself with :-)
- literals = ['*', '.', '[', ']', '(', ')', '$', ',', ':', '|', '&', '@']
+ literals = ['*', '.', '[', ']', '(', ')', '$', ',', ':', '|', '&']
reserved_words = { 'where': 'WHERE' }
- tokens = ['DOUBLEDOT', 'NUMBER', 'ID'] + list(reserved_words.values())
+ tokens = ['DOUBLEDOT', 'NUMBER', 'ID', 'NAMED_OPERATOR'] + list(reserved_words.values())
states = [ ('singlequote', 'exclusive'),
- ('doublequote', 'exclusive') ]
+ ('doublequote', 'exclusive'),
+ ('backquote', 'exclusive') ]
# Normal lexing, rather easy
t_DOUBLEDOT = r'\.\.'
t_ignore = ' \t'
def t_ID(self, t):
- r'[a-zA-Z_][a-zA-Z0-9_]*'
+ r'[a-zA-Z_@][a-zA-Z0-9_@]*'
t.type = self.reserved_words.get(t.value, 'ID')
return t
@@ -95,6 +96,25 @@ class JsonPathLexer(object):
def t_doublequote_error(self, t):
raise Exception('Error on line %s, col %s while lexing doublequoted field: Unexpected character: %s ' % (t.lexer.lineno, t.lexpos - t.latest_newline, t.value[0]))
+
+ # Back-quoted "magic" operators
+ t_backquote_ignore = ''
+ def t_BACKQUOTE(self, t):
+ r'`'
+ t.lexer.string_start = t.lexer.lexpos
+ t.lexer.push_state('backquote')
+
+ def t_backquote_BACKQUOTE(self, t):
+ r'([^`]|\\`)*`'
+ t.value = t.value[:-1]
+ t.type = 'NAMED_OPERATOR'
+ t.lexer.pop_state()
+ return t
+
+ def t_backquote_error(self, t):
+ raise Exception('Error on line %s, col %s while lexing backquoted operator: Unexpected character: %s ' % (t.lexer.lineno, t.lexpos - t.latest_newline, t.value[0]))
+
+
# Counting lines, handling errors
def t_newline(self, t):
r'\n'
diff --git a/jsonpath_rw/parser.py b/jsonpath_rw/parser.py
index b8c1231..85d05d9 100644
--- a/jsonpath_rw/parser.py
+++ b/jsonpath_rw/parser.py
@@ -84,9 +84,12 @@ class JsonPathParser(object):
"jsonpath : fields_or_any"
p[0] = Fields(*p[1])
- def p_jsonpath_this(self, p):
- "jsonpath : '@'"
- p[0] = This()
+ def p_jsonpath_named_operator(self, p):
+ "jsonpath : NAMED_OPERATOR"
+ if p[1] == 'this':
+ p[0] = This()
+ else:
+ raise Exception('Unknown named operator `%s` at %s:%s' % (t.value, t.lineno, t.col))
def p_jsonpath_root(self, p):
"jsonpath : '$'"
diff --git a/tests/test_jsonpath.py b/tests/test_jsonpath.py
index fd7cf32..c97c096 100644
--- a/tests/test_jsonpath.py
+++ b/tests/test_jsonpath.py
@@ -103,10 +103,11 @@ class TestJsonPath(unittest.TestCase):
jsonpath.auto_id_field = None
self.check_cases([ ('foo', {'foo': 'baz'}, ['baz']),
('foo,baz', {'foo': 1, 'baz': 2}, [1, 2]),
+ ('@foo', {'@foo': 1}, [1]),
('*', {'foo': 1, 'baz': 2}, set([1, 2])) ])
jsonpath.auto_id_field = 'id'
- self.check_cases([ ('*', {'foo': 1, 'baz': 2}, set([1, 2, '@'])) ])
+ self.check_cases([ ('*', {'foo': 1, 'baz': 2}, set([1, 2, '`this`'])) ])
def test_root_value(self):
jsonpath.auto_id_field = None
@@ -119,9 +120,9 @@ class TestJsonPath(unittest.TestCase):
def test_this_value(self):
jsonpath.auto_id_field = None
self.check_cases([
- ('@', {'foo': 'baz'}, [{'foo':'baz'}]),
- ('foo.@', {'foo': 'baz'}, ['baz']),
- ('foo.@.baz', {'foo': {'baz': 3}}, [3]),
+ ('`this`', {'foo': 'baz'}, [{'foo':'baz'}]),
+ ('foo.`this`', {'foo': 'baz'}, ['baz']),
+ ('foo.`this`.baz', {'foo': {'baz': 3}}, [3]),
])
def test_index_value(self):
@@ -194,9 +195,9 @@ class TestJsonPath(unittest.TestCase):
def test_this_paths(self):
jsonpath.auto_id_field = None
self.check_paths([
- ('@', {'foo': 'baz'}, ['@']),
- ('foo.@', {'foo': 'baz'}, ['foo']),
- ('foo.@.baz', {'foo': {'baz': 3}}, ['foo.baz']),
+ ('`this`', {'foo': 'baz'}, ['`this`']),
+ ('foo.`this`', {'foo': 'baz'}, ['foo']),
+ ('foo.`this`.baz', {'foo': {'baz': 3}}, ['foo.baz']),
])
def test_index_paths(self):
@@ -240,9 +241,9 @@ class TestJsonPath(unittest.TestCase):
def test_this_auto_id(self):
jsonpath.auto_id_field = 'id'
self.check_cases([
- ('id', {'foo': 'baz'}, ['@']), # This is, again, a wonky case that is not that interesting
- ('foo.@.id', {'foo': 'baz'}, ['foo']),
- ('foo.@.baz.id', {'foo': {'baz': 3}}, ['foo.baz']),
+ ('id', {'foo': 'baz'}, ['`this`']), # This is, again, a wonky case that is not that interesting
+ ('foo.`this`.id', {'foo': 'baz'}, ['foo']),
+ ('foo.`this`.baz.id', {'foo': {'baz': 3}}, ['foo.baz']),
])
def test_index_auto_id(self):
diff --git a/tests/test_lexer.py b/tests/test_lexer.py
index 53ad7ef..c1ddc0f 100644
--- a/tests/test_lexer.py
+++ b/tests/test_lexer.py
@@ -43,5 +43,7 @@ class TestLexer(unittest.TestCase):
self.assert_lex_equiv('fuzz.*', [self.token('fuzz', 'ID'), self.token('.', '.'), self.token('*', '*')])
self.assert_lex_equiv('fuzz..bang', [self.token('fuzz', 'ID'), self.token('..', 'DOUBLEDOT'), self.token('bang', 'ID')])
self.assert_lex_equiv('&', [self.token('&', '&')])
+ self.assert_lex_equiv('@', [self.token('@', 'ID')])
+ self.assert_lex_equiv('`this`', [self.token('this', 'NAMED_OPERATOR')])
self.assert_lex_equiv('|', [self.token('|', '|')])
self.assert_lex_equiv('where', [self.token('where', 'WHERE')])