janeway-jsonpath 0.2.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/README.md +109 -38
- data/lib/janeway/ast/array_slice_selector.rb +9 -7
- data/lib/janeway/ast/binary_operator.rb +25 -23
- data/lib/janeway/ast/current_node.rb +8 -5
- data/lib/janeway/ast/descendant_segment.rb +1 -1
- data/lib/janeway/ast/expression.rb +11 -1
- data/lib/janeway/ast/filter_selector.rb +1 -1
- data/lib/janeway/ast/helpers.rb +1 -0
- data/lib/janeway/ast/index_selector.rb +2 -3
- data/lib/janeway/ast/name_selector.rb +2 -2
- data/lib/janeway/ast/null.rb +1 -0
- data/lib/janeway/ast/query.rb +42 -1
- data/lib/janeway/ast/root_node.rb +8 -5
- data/lib/janeway/ast/string_type.rb +2 -0
- data/lib/janeway/ast/unary_operator.rb +2 -2
- data/lib/janeway/ast/wildcard_selector.rb +1 -0
- data/lib/janeway/error.rb +1 -1
- data/lib/janeway/functions/count.rb +2 -4
- data/lib/janeway/functions/length.rb +2 -3
- data/lib/janeway/functions/match.rb +3 -9
- data/lib/janeway/functions/search.rb +4 -9
- data/lib/janeway/functions/value.rb +2 -3
- data/lib/janeway/interpreter.rb +29 -587
- data/lib/janeway/interpreters/array_slice_selector_interpreter.rb +44 -0
- data/lib/janeway/interpreters/base.rb +43 -0
- data/lib/janeway/interpreters/binary_operator_interpreter.rb +163 -0
- data/lib/janeway/interpreters/child_segment_interpreter.rb +44 -0
- data/lib/janeway/interpreters/current_node_interpreter.rb +33 -0
- data/lib/janeway/interpreters/descendant_segment_interpreter.rb +40 -0
- data/lib/janeway/interpreters/filter_selector_interpreter.rb +63 -0
- data/lib/janeway/interpreters/function_interpreter.rb +99 -0
- data/lib/janeway/interpreters/index_selector_interpreter.rb +39 -0
- data/lib/janeway/interpreters/name_selector_interpreter.rb +30 -0
- data/lib/janeway/interpreters/root_node_interpreter.rb +23 -0
- data/lib/janeway/interpreters/tree_constructor.rb +39 -0
- data/lib/janeway/interpreters/unary_operator_interpreter.rb +32 -0
- data/lib/janeway/interpreters/wildcard_selector_interpreter.rb +38 -0
- data/lib/janeway/interpreters/yielder.rb +34 -0
- data/lib/janeway/lexer.rb +12 -16
- data/lib/janeway/parser.rb +51 -90
- data/lib/janeway/token.rb +1 -0
- data/lib/janeway/version.rb +1 -1
- data/lib/janeway.rb +25 -5
- metadata +17 -3
- data/lib/janeway/ast/identifier.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 23479bd7bd49f2ac64ce5e8f27399df191efd5c7efe76aaca4fd5615cf429319
|
4
|
+
data.tar.gz: b1dea6ae594f5ab51f0105fb32f81ad03a5028223214f38e01c05cae0fad011b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 46d9ea3bfe2b012d538f53abcad7f29f604ce5c54de51f7f0354d78aa9be4871955cf4666898fdf0ba0e6262147b4d9f29570ca394046b1473597b2c8118f023
|
7
|
+
data.tar.gz: f323676b0cfeb66a2cbbad9331ff782dbd2691d42258ca607d9e6aa4bf511f6cc6e439962d40720b75a0aceee746f5ed224f4c3f4fbd68c29a2d093395807830
|
data/README.md
CHANGED
@@ -1,64 +1,135 @@
|
|
1
1
|
# Janeway JSONPath parser
|
2
2
|
|
3
|
-
### Purpose
|
4
|
-
|
5
3
|
This is a [JsonPath](https://goessner.net/articles/JsonPath/) parser.
|
4
|
+
It strictly follows [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html) and passes the [JSONPath Compliance Test Suite](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite).
|
6
5
|
|
7
|
-
It reads a JSON input file and a query.
|
8
|
-
It uses the query to find and return a set of matching values from the input.
|
6
|
+
It reads a JSON input file and a query, and uses the query to find and return a set of matching values from the input.
|
9
7
|
This does for JSON the same job that XPath does for XML.
|
10
8
|
|
11
9
|
This project includes:
|
10
|
+
|
12
11
|
* command-line tool to run jsonpath queries on a JSON input
|
13
12
|
* ruby library to run jsonpath queries on a JSON input
|
14
13
|
|
15
|
-
|
14
|
+
**Contents**
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
* use helpful query parse errors which help understand and improve queries, rather than describing issues in the code
|
23
|
-
* modern, linted ruby 3 code with frozen string literals
|
16
|
+
- [Install](#install)
|
17
|
+
- [Usage](#usage)
|
18
|
+
- [Related projects](#related-projects)
|
19
|
+
- [Goals](#goals)
|
20
|
+
- [Non-goals](#non-goals)
|
24
21
|
|
25
|
-
###
|
22
|
+
### Install
|
26
23
|
|
27
|
-
|
24
|
+
Install the gem from the command-line:
|
25
|
+
```
|
26
|
+
gem install janeway-jsonpath`
|
27
|
+
```
|
28
28
|
|
29
|
-
|
30
|
-
|
29
|
+
or add it to your Gemfile:
|
30
|
+
|
31
|
+
```
|
32
|
+
gem 'janeway-jsonpath', '~> 0.2.0'
|
33
|
+
```
|
34
|
+
|
35
|
+
### Usage
|
36
|
+
|
37
|
+
#### Janeway command-line tool
|
38
|
+
|
39
|
+
Give it a query and some input JSON, and it prints a JSON result.
|
40
|
+
Use single quotes around the JSON query to avoid shell interaction.
|
41
|
+
Example:
|
42
|
+
|
43
|
+
```
|
44
|
+
$ janeway '$..book[?(@.price<10)]' example.json
|
45
|
+
[
|
46
|
+
{
|
47
|
+
"category": "reference",
|
48
|
+
"author": "Nigel Rees",
|
49
|
+
"title": "Sayings of the Century",
|
50
|
+
"price": 8.95
|
51
|
+
},
|
52
|
+
{
|
53
|
+
"category": "fiction",
|
54
|
+
"author": "Herman Melville",
|
55
|
+
"title": "Moby Dick",
|
56
|
+
"isbn": "0-553-21311-3",
|
57
|
+
"price": 8.99
|
58
|
+
}
|
59
|
+
]
|
60
|
+
```
|
61
|
+
|
62
|
+
You can also pipe JSON into it:
|
63
|
+
```
|
64
|
+
$ cat example.json | janeway '$..book[?(@.price<10)]'
|
65
|
+
```
|
66
|
+
|
67
|
+
See the help message for more capabilities: `janeway --help`
|
31
68
|
|
32
|
-
|
69
|
+
#### Janeway ruby libarary
|
33
70
|
|
34
|
-
|
71
|
+
Here's an example of using Janeway to execute a JSONPath query in ruby code:
|
72
|
+
```ruby
|
73
|
+
require 'janeway'
|
74
|
+
require 'json'
|
35
75
|
|
36
|
-
|
76
|
+
data = JSON.parse(File.read(ARGV.first))
|
77
|
+
results = Janeway.find_all('$..book[?(@.price<10)]', data)
|
78
|
+
```
|
37
79
|
|
38
|
-
|
80
|
+
Alternatively, compile the query once, and share it between threads or ractors.
|
39
81
|
|
40
|
-
The
|
41
|
-
This implementation has been around for a long time.
|
42
|
-
I personally have used it in a software project for several years.
|
43
|
-
Here are differences I've found in porting this application from joshbuddy/jsonpath to janeway.
|
82
|
+
The Janeway::AST::Query object is not modified after parsing, so it is easy to freeze and share concurrently.
|
44
83
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
84
|
+
```ruby
|
85
|
+
# Create ractors with their own data sources
|
86
|
+
ractors =
|
87
|
+
Array.new(4) do |index|
|
88
|
+
Ractor.new(index) do |i|
|
89
|
+
query = receive
|
90
|
+
data = JSON.parse File.read("input-file-#{i}.json")
|
91
|
+
puts query.find_all(data)
|
92
|
+
end
|
93
|
+
end
|
49
94
|
|
50
|
-
|
51
|
-
|
95
|
+
# Construct JSONPath query object and send it to each ractor
|
96
|
+
query = Janeway.compile('$..book[?(@.price<10)]')
|
97
|
+
ractors.each { |ractor| ractor.send(query).take }
|
98
|
+
```
|
52
99
|
|
53
|
-
|
54
|
-
$ janeway '$.store' example.json
|
100
|
+
### Related Projects
|
55
101
|
|
56
|
-
|
57
|
-
$ jsonpath '$.nodes..services..id' spec/resources/grid-full.json
|
102
|
+
- [joshbuddy/jsonpath](https://github.com/joshbuddy/jsonpath)
|
58
103
|
|
59
|
-
|
104
|
+
This is the classic 'jsonpath' ruby gem. It has been around since 2008.
|
105
|
+
It is not compliant with RFC 9535, because it was written long before the standard was finalized, but it's a capable and useful parser and has long been the best jsonpath library available for ruby.
|
106
|
+
|
107
|
+
See [Porting](PORTING.md) for tips on converting a ruby project from [joshbuddy/jsonpath](https://github.com/joshbuddy/jsonpath) to janeway.
|
108
|
+
|
109
|
+
- [JPT - reference implementation based on parsing the ABNF grammar of RFC 9535](https://github.com/cabo/jpt)
|
110
|
+
|
111
|
+
Also there are many non-ruby implementations of RFC 9535, here are just a few:
|
112
|
+
- [jesse (dart)](https://github.com/f3ath/jessie)
|
113
|
+
- [python-jsonpath-rfc9535 (python)](https://github.com/jg-rp/python-jsonpath-rfc9535)
|
114
|
+
- [theory/jsonpath (go)](https://github.com/theory/jsonpath)
|
115
|
+
|
116
|
+
### Goals
|
117
|
+
|
118
|
+
* maintain perfect compliance with [IETF RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html)
|
119
|
+
* raise helpful query parse errors designed to help users understand and improve queries, rather than describing issues in the code
|
120
|
+
* don't use regular expressions for parsing, for performance
|
121
|
+
* don't use `eval`, which is known to be an attack vector
|
122
|
+
* be simple and fast with minimal dependencies
|
123
|
+
* provide ruby-like accessors (eg. #each, #delete_if) for processing results
|
124
|
+
* modern, linted ruby 3 code with frozen string literals
|
125
|
+
|
126
|
+
### Non-goals
|
127
|
+
|
128
|
+
* Changing behavior to follow [other implementations](https://cburgmer.github.io/json-path-comparison/)
|
129
|
+
|
130
|
+
The JSONPath RFC was in draft status for a long time and has seen many changes.
|
131
|
+
There are many implementations based on older drafts, and others which add features that were never in the RFC at all.
|
60
132
|
|
61
|
-
|
62
|
-
The examples in the RFC have been implemented as unit tests.
|
133
|
+
The goal is adherence to the [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html) rather than adding features that are in other implementations. This implementation's results are supposed to be identical to other RFC-compliant implementations in [dart](https://github.com/f3ath/jessie), [python](https://github.com/jg-rp/python-jsonpath-rfc9535) and other languages.
|
63
134
|
|
64
|
-
|
135
|
+
The RFC was finalized in 2024. With the finalized RFC and the rigorous [suite of compliance tests](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite), it is now possible to have JSONPath implementations in many languages with identical behavior.
|
@@ -136,14 +136,16 @@ module Janeway
|
|
136
136
|
[lower, upper]
|
137
137
|
end
|
138
138
|
|
139
|
-
#
|
139
|
+
# @param brackets [Boolean] add surrounding brackets if true
|
140
140
|
# @return [String]
|
141
|
-
def to_s(
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
141
|
+
def to_s(brackets: true, **_ignored)
|
142
|
+
index_str =
|
143
|
+
if @step
|
144
|
+
"#{@start}:#{@end}:#{@step}"
|
145
|
+
else
|
146
|
+
"#{@start}:#{@end}"
|
147
|
+
end
|
148
|
+
brackets ? "[#{index_str}]" : index_str
|
147
149
|
end
|
148
150
|
|
149
151
|
# @param level [Integer]
|
@@ -2,19 +2,21 @@
|
|
2
2
|
|
3
3
|
module Janeway
|
4
4
|
module AST
|
5
|
+
# AST node for binary operators:
|
6
|
+
# == != <= >= < > || &&
|
5
7
|
class BinaryOperator < Janeway::AST::Expression
|
6
|
-
attr_reader :
|
8
|
+
attr_reader :name, :left, :right
|
7
9
|
|
8
10
|
def initialize(operator, left = nil, right = nil)
|
9
11
|
super(nil)
|
10
12
|
raise ArgumentError, "expect symbol, got #{operator.inspect}" unless operator.is_a?(Symbol)
|
11
13
|
|
12
|
-
@
|
14
|
+
@name = operator # eg. :equal
|
13
15
|
self.left = left if left
|
14
16
|
self.right = right if right
|
15
17
|
end
|
16
18
|
|
17
|
-
# Set the left-hand-side
|
19
|
+
# Set the left-hand-side expression
|
18
20
|
# @param expr [AST::Expression]
|
19
21
|
def left=(expr)
|
20
22
|
if comparison_operator? && !(expr.literal? || expr.singular_query?)
|
@@ -32,7 +34,7 @@ module Janeway
|
|
32
34
|
@left = expr
|
33
35
|
end
|
34
36
|
|
35
|
-
# Set the left-hand-side
|
37
|
+
# Set the left-hand-side expression
|
36
38
|
# @param expr [AST::Expression]
|
37
39
|
def right=(expr)
|
38
40
|
if comparison_operator? && !(expr.literal? || expr.singular_query?)
|
@@ -52,23 +54,6 @@ module Janeway
|
|
52
54
|
"(#{@left} #{operator_to_s} #{@right})"
|
53
55
|
end
|
54
56
|
|
55
|
-
private
|
56
|
-
|
57
|
-
def operator_to_s
|
58
|
-
case operator
|
59
|
-
when :and then '&&'
|
60
|
-
when :equal then '=='
|
61
|
-
when :greater_than then '>'
|
62
|
-
when :greater_than_or_equal then '>='
|
63
|
-
when :less_than then '<'
|
64
|
-
when :less_than_or_equal then '<='
|
65
|
-
when :not_equal then '!='
|
66
|
-
when :or then '||'
|
67
|
-
else
|
68
|
-
raise "unknown binary operator #{operator}"
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
57
|
# @param level [Integer]
|
73
58
|
# @return [Array]
|
74
59
|
def tree(level)
|
@@ -91,12 +76,29 @@ module Janeway
|
|
91
76
|
operator_type == :logical
|
92
77
|
end
|
93
78
|
|
79
|
+
private
|
80
|
+
|
81
|
+
def operator_to_s
|
82
|
+
case name
|
83
|
+
when :and then '&&'
|
84
|
+
when :equal then '=='
|
85
|
+
when :greater_than then '>'
|
86
|
+
when :greater_than_or_equal then '>='
|
87
|
+
when :less_than then '<'
|
88
|
+
when :less_than_or_equal then '<='
|
89
|
+
when :not_equal then '!='
|
90
|
+
when :or then '||'
|
91
|
+
else
|
92
|
+
raise "unknown binary operator #{name}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
94
96
|
def operator_type
|
95
|
-
case
|
97
|
+
case name
|
96
98
|
when :and, :or then :logical
|
97
99
|
when :equal, :not_equal, :greater_than, :greater_than_or_equal, :less_than, :less_than_or_equal then :comparison
|
98
100
|
else
|
99
|
-
raise "unknown binary operator #{
|
101
|
+
raise "unknown binary operator #{name}"
|
100
102
|
end
|
101
103
|
end
|
102
104
|
end
|
@@ -29,8 +29,11 @@ module Janeway
|
|
29
29
|
#
|
30
30
|
# Construct accepts an optional Selector which will be applied to the "current" node
|
31
31
|
class CurrentNode < Janeway::AST::Expression
|
32
|
+
# Subsequent expression that modifies the output of this expression
|
33
|
+
attr_accessor :next
|
34
|
+
|
32
35
|
def to_s
|
33
|
-
"@#{@
|
36
|
+
"@#{@next}"
|
34
37
|
end
|
35
38
|
|
36
39
|
# True if this is the root of a singular-query.
|
@@ -38,10 +41,10 @@ module Janeway
|
|
38
41
|
#
|
39
42
|
# @return [Boolean]
|
40
43
|
def singular_query?
|
41
|
-
return true unless @
|
44
|
+
return true unless @next # there are no following selectors
|
42
45
|
|
43
46
|
selector_types = []
|
44
|
-
selector = @
|
47
|
+
selector = @next
|
45
48
|
loop do
|
46
49
|
selector_types << selector.class
|
47
50
|
selector = selector.next
|
@@ -54,13 +57,13 @@ module Janeway
|
|
54
57
|
# True if this is a bare current node operator, without a following expression
|
55
58
|
# @return [Boolean]
|
56
59
|
def empty?
|
57
|
-
@
|
60
|
+
@next.nil?
|
58
61
|
end
|
59
62
|
|
60
63
|
# @param level [Integer]
|
61
64
|
# @return [Array]
|
62
65
|
def tree(level)
|
63
|
-
[indented(level, '@'), @
|
66
|
+
[indented(level, '@'), @next.tree(indent + 1)]
|
64
67
|
end
|
65
68
|
end
|
66
69
|
end
|
@@ -7,13 +7,23 @@ module Janeway
|
|
7
7
|
module AST
|
8
8
|
INDENT = ' '
|
9
9
|
|
10
|
+
# Base class for jsonpath expressions.
|
11
|
+
#
|
12
|
+
# Every AST node is a subclass of this.
|
13
|
+
# This includes selectors, root and child identifiers, descendant segments,
|
14
|
+
# and the nodes that occur within a filter selector such as the current
|
15
|
+
# node identifier, operators and literals.
|
10
16
|
class Expression
|
17
|
+
# Value provided by subclass constructor.
|
11
18
|
attr_accessor :value
|
12
19
|
|
20
|
+
# Next expression in the AST, if any
|
21
|
+
attr_reader :next
|
22
|
+
|
13
23
|
def initialize(val = nil)
|
14
24
|
# don't set the instance variable if unused, because it makes the
|
15
25
|
# "#inspect" output cleaner in rspec test failures
|
16
|
-
@value = val unless val.nil?
|
26
|
+
@value = val unless val.nil? # false must be stored though!
|
17
27
|
end
|
18
28
|
|
19
29
|
# @return [String]
|
data/lib/janeway/ast/helpers.rb
CHANGED
@@ -17,9 +17,8 @@ module Janeway
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# @param brackets [Boolean] include brackets around selector
|
20
|
-
|
21
|
-
|
22
|
-
brackets ? "[#{@value}]" : @value.to_s
|
20
|
+
def to_s(brackets: true, **_ignored)
|
21
|
+
brackets ? "[#{@value}]#{@next}" : "#{@value}#{@next}"
|
23
22
|
end
|
24
23
|
end
|
25
24
|
end
|
@@ -17,8 +17,8 @@ module Janeway
|
|
17
17
|
raise "Invalid name: #{value.inspect}:#{value.class}" unless value.is_a?(String)
|
18
18
|
end
|
19
19
|
|
20
|
-
# @param
|
21
|
-
# @param
|
20
|
+
# @param brackets [Boolean] request for bracket syntax
|
21
|
+
# @param dot_prefix [Boolean] include . prefix, if shorthand notation is used
|
22
22
|
def to_s(brackets: false, dot_prefix: true)
|
23
23
|
# Add quotes and surrounding brackets if the name includes chars that require quoting.
|
24
24
|
# These chars are not allowed in dotted notation, only bracket notation.
|
data/lib/janeway/ast/null.rb
CHANGED
data/lib/janeway/ast/query.rb
CHANGED
@@ -32,13 +32,54 @@ module Janeway
|
|
32
32
|
Janeway::Interpreter.new(self).interpret(input)
|
33
33
|
end
|
34
34
|
|
35
|
+
# Iterate through each value matched by the JSONPath query.
|
36
|
+
#
|
37
|
+
# @param input [Hash, Array] ruby object to be searched
|
38
|
+
# @yieldparam [Object] matched value
|
39
|
+
# @return [void]
|
40
|
+
def each(input, &block)
|
41
|
+
return enum_for(:each, input) unless block_given?
|
42
|
+
|
43
|
+
interpreter = Janeway::Interpreter.new(self)
|
44
|
+
interpreter.push Janeway::Interpreters::Yielder.new(&block)
|
45
|
+
interpreter.interpret(input)
|
46
|
+
end
|
47
|
+
|
35
48
|
def to_s
|
36
49
|
@root.to_s
|
37
50
|
end
|
38
51
|
|
52
|
+
# Return a list of all the nodes in the AST.
|
53
|
+
# The AST of a jsonpath query is a straight line, so this is expressible as an array.
|
54
|
+
# The only part of the AST with branches is inside a filter selector, but that doesn't show up here.
|
55
|
+
# @return [Array<Expression>]
|
56
|
+
def node_list
|
57
|
+
nodes = []
|
58
|
+
node = @root
|
59
|
+
loop do
|
60
|
+
nodes << node
|
61
|
+
break unless node.next
|
62
|
+
|
63
|
+
node = node.next
|
64
|
+
end
|
65
|
+
nodes
|
66
|
+
end
|
67
|
+
|
68
|
+
# Remove the last selector in the query from the node list, and return the removed selector.
|
69
|
+
# @return [AST::Selector, nil] last selector in the query, if any
|
70
|
+
def pop
|
71
|
+
nodes = node_list
|
72
|
+
return nil if node_list.size == 1 # only 1 node, don't pop
|
73
|
+
|
74
|
+
# Remove the last selector and return it
|
75
|
+
last_node = nodes.pop
|
76
|
+
nodes.last.next = nil # delete the second-last node's link to the last node
|
77
|
+
last_node
|
78
|
+
end
|
79
|
+
|
39
80
|
# Queries are considered equal if their ASTs evaluate to the same JSONPath string.
|
40
81
|
#
|
41
|
-
# The string output is generated
|
82
|
+
# The string output is generated from the AST and should be considered a "normalized"
|
42
83
|
# form of the query. It may have different whitespace and parentheses than the original
|
43
84
|
# input but will be semantically equivalent.
|
44
85
|
def ==(other)
|
@@ -14,8 +14,11 @@ module Janeway
|
|
14
14
|
# $(? $.key1 == $.key2 )
|
15
15
|
#
|
16
16
|
class RootNode < Janeway::AST::Expression
|
17
|
+
# Subsequent expression that modifies the output of this expression
|
18
|
+
attr_accessor :next
|
19
|
+
|
17
20
|
def to_s
|
18
|
-
"$#{@
|
21
|
+
"$#{@next}"
|
19
22
|
end
|
20
23
|
|
21
24
|
# True if this is the root of a singular-query.
|
@@ -23,13 +26,13 @@ module Janeway
|
|
23
26
|
#
|
24
27
|
# @return [Boolean]
|
25
28
|
def singular_query?
|
26
|
-
return true unless @
|
29
|
+
return true unless @next # there are no following selectors
|
27
30
|
|
28
31
|
selector_types = []
|
29
|
-
selector = @
|
32
|
+
selector = @next
|
30
33
|
loop do
|
31
34
|
selector_types << selector.class
|
32
|
-
selector = selector
|
35
|
+
selector = selector&.next
|
33
36
|
break unless selector
|
34
37
|
end
|
35
38
|
allowed = [AST::IndexSelector, AST::NameSelector]
|
@@ -39,7 +42,7 @@ module Janeway
|
|
39
42
|
# @param level [Integer]
|
40
43
|
# @return [Array]
|
41
44
|
def tree(level = 0)
|
42
|
-
[indented(level, '$'), @
|
45
|
+
[indented(level, '$'), @next.tree(level + 1)]
|
43
46
|
end
|
44
47
|
end
|
45
48
|
end
|
@@ -4,11 +4,11 @@ module Janeway
|
|
4
4
|
module AST
|
5
5
|
# Represent unary operators "!", "-"
|
6
6
|
class UnaryOperator < Janeway::AST::Expression
|
7
|
-
attr_accessor :
|
7
|
+
attr_accessor :name, :operand
|
8
8
|
|
9
9
|
def initialize(operator, operand = nil)
|
10
10
|
super()
|
11
|
-
@
|
11
|
+
@name = operator # eg. :not
|
12
12
|
@operand = operand
|
13
13
|
end
|
14
14
|
|
data/lib/janeway/error.rb
CHANGED
@@ -27,16 +27,14 @@ module Janeway
|
|
27
27
|
arg = parse_function_parameter
|
28
28
|
parameters = [arg]
|
29
29
|
raise Error, "Invalid parameter - count() expects node list, got #{arg.value.inspect}" if arg.literal?
|
30
|
-
unless current.type == :group_end
|
31
|
-
raise Error, 'Too many parameters for count() function call'
|
32
|
-
end
|
30
|
+
raise Error, 'Too many parameters for count() function call' unless current.type == :group_end
|
33
31
|
|
34
32
|
# Define function body
|
35
33
|
AST::Function.new('count', parameters) do |node_list|
|
36
34
|
if node_list.is_a?(Array)
|
37
35
|
node_list.size
|
38
36
|
else
|
39
|
-
1 #
|
37
|
+
1 # the count of a non-empty singular nodelist such as count(@) is always 1.
|
40
38
|
end
|
41
39
|
end
|
42
40
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Janeway
|
4
|
+
# Mixin to provide JSONPath function handlers for Parser
|
4
5
|
module Functions
|
5
6
|
# The length() function extension provides a way to compute the length of a value
|
6
7
|
# and make that available for further processing in the filter expression:
|
@@ -18,9 +19,7 @@ module Janeway
|
|
18
19
|
unless arg.singular_query? || arg.literal?
|
19
20
|
raise Error, "Invalid parameter - length() expects literal value or singular query, got #{arg.value.inspect}"
|
20
21
|
end
|
21
|
-
unless current.type == :group_end
|
22
|
-
raise Error, 'Too many parameters for length() function call'
|
23
|
-
end
|
22
|
+
raise Error, 'Too many parameters for length() function call' unless current.type == :group_end
|
24
23
|
|
25
24
|
# Meaning of return value depends on the JSON type:
|
26
25
|
# * string - number of Unicode scalar values in the string.
|
@@ -41,22 +41,16 @@ module Janeway
|
|
41
41
|
# Read first parameter
|
42
42
|
parameters = []
|
43
43
|
parameters << parse_function_parameter
|
44
|
+
raise Error, 'Not enough parameters for match() function call' unless current.type == :union
|
44
45
|
|
45
|
-
#
|
46
|
-
if current.type == :union
|
47
|
-
consume # ,
|
48
|
-
else
|
49
|
-
raise Error, 'Not enough parameters for match() function call'
|
50
|
-
end
|
46
|
+
consume # ,
|
51
47
|
|
52
48
|
# Read second parameter (the regexp)
|
53
49
|
# This could be a string, in which case it is available now.
|
54
50
|
# Otherwise it is an expression that takes the regexp from the input document,
|
55
51
|
# and the iregexp will not be available until interpretation.
|
56
52
|
parameters << parse_function_parameter
|
57
|
-
unless current.type == :group_end
|
58
|
-
raise Error, 'Too many parameters for match() function call'
|
59
|
-
end
|
53
|
+
raise Error, 'Too many parameters for match() function call' unless current.type == :group_end
|
60
54
|
|
61
55
|
AST::Function.new('match', parameters) do |str, str_iregexp|
|
62
56
|
if str.is_a?(String) && str_iregexp.is_a?(String)
|