burtpath 1.1.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 +7 -0
- data/LICENSE.txt +174 -0
- data/lib/burtpath.rb +3 -0
- data/lib/jmespath/caching_parser.rb +30 -0
- data/lib/jmespath/errors.rb +17 -0
- data/lib/jmespath/lexer.rb +118 -0
- data/lib/jmespath/nodes/comparator.rb +77 -0
- data/lib/jmespath/nodes/condition.rb +136 -0
- data/lib/jmespath/nodes/current.rb +10 -0
- data/lib/jmespath/nodes/expression.rb +25 -0
- data/lib/jmespath/nodes/field.rb +63 -0
- data/lib/jmespath/nodes/flatten.rb +29 -0
- data/lib/jmespath/nodes/function.rb +464 -0
- data/lib/jmespath/nodes/index.rb +18 -0
- data/lib/jmespath/nodes/literal.rb +16 -0
- data/lib/jmespath/nodes/multi_select_hash.rb +37 -0
- data/lib/jmespath/nodes/multi_select_list.rb +22 -0
- data/lib/jmespath/nodes/or.rb +24 -0
- data/lib/jmespath/nodes/pipe.rb +6 -0
- data/lib/jmespath/nodes/projection.rb +86 -0
- data/lib/jmespath/nodes/slice.rb +93 -0
- data/lib/jmespath/nodes/subexpression.rb +63 -0
- data/lib/jmespath/nodes.rb +40 -0
- data/lib/jmespath/optimizing_parser.rb +12 -0
- data/lib/jmespath/parser.rb +288 -0
- data/lib/jmespath/runtime.rb +82 -0
- data/lib/jmespath/token.rb +41 -0
- data/lib/jmespath/token_stream.rb +60 -0
- data/lib/jmespath/version.rb +3 -0
- data/lib/jmespath.rb +42 -0
- metadata +89 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
module Nodes
|
4
|
+
class Slice < Node
|
5
|
+
def initialize(start, stop, step)
|
6
|
+
@start = start
|
7
|
+
@stop = stop
|
8
|
+
@step = step
|
9
|
+
end
|
10
|
+
|
11
|
+
def visit(value)
|
12
|
+
if String === value || Array === value
|
13
|
+
start, stop, step = adjust_slice(value.size, @start, @stop, @step)
|
14
|
+
result = []
|
15
|
+
if step > 0
|
16
|
+
i = start
|
17
|
+
while i < stop
|
18
|
+
result << value[i]
|
19
|
+
i += step
|
20
|
+
end
|
21
|
+
else
|
22
|
+
i = start
|
23
|
+
while i > stop
|
24
|
+
result << value[i]
|
25
|
+
i += step
|
26
|
+
end
|
27
|
+
end
|
28
|
+
String === value ? result.join : result
|
29
|
+
else
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def optimize
|
35
|
+
if (@step.nil? || @step == 1) && @start && @stop && @start > 0 && @stop > @start
|
36
|
+
SimpleSlice.new(@start, @stop)
|
37
|
+
else
|
38
|
+
self
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def adjust_slice(length, start, stop, step)
|
45
|
+
if step.nil?
|
46
|
+
step = 1
|
47
|
+
elsif step == 0
|
48
|
+
raise Errors::RuntimeError, 'slice step cannot be 0'
|
49
|
+
end
|
50
|
+
|
51
|
+
if start.nil?
|
52
|
+
start = step < 0 ? length - 1 : 0
|
53
|
+
else
|
54
|
+
start = adjust_endpoint(length, start, step)
|
55
|
+
end
|
56
|
+
|
57
|
+
if stop.nil?
|
58
|
+
stop = step < 0 ? -1 : length
|
59
|
+
else
|
60
|
+
stop = adjust_endpoint(length, stop, step)
|
61
|
+
end
|
62
|
+
|
63
|
+
[start, stop, step]
|
64
|
+
end
|
65
|
+
|
66
|
+
def adjust_endpoint(length, endpoint, step)
|
67
|
+
if endpoint < 0
|
68
|
+
endpoint += length
|
69
|
+
endpoint = 0 if endpoint < 0
|
70
|
+
endpoint
|
71
|
+
elsif endpoint >= length
|
72
|
+
step < 0 ? length - 1 : length
|
73
|
+
else
|
74
|
+
endpoint
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class SimpleSlice < Slice
|
80
|
+
def initialize(start, stop)
|
81
|
+
super(start, stop, 1)
|
82
|
+
end
|
83
|
+
|
84
|
+
def visit(value)
|
85
|
+
if String === value || Array === value
|
86
|
+
value[@start, @stop - @start]
|
87
|
+
else
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
module Nodes
|
4
|
+
class Subexpression < Node
|
5
|
+
def initialize(left, right)
|
6
|
+
@left = left
|
7
|
+
@right = right
|
8
|
+
end
|
9
|
+
|
10
|
+
def visit(value)
|
11
|
+
@right.visit(@left.visit(value))
|
12
|
+
end
|
13
|
+
|
14
|
+
def optimize
|
15
|
+
Chain.new(flatten).optimize
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
attr_reader :left, :right
|
21
|
+
|
22
|
+
def flatten
|
23
|
+
nodes = [@left, @right]
|
24
|
+
until nodes.none? { |node| node.is_a?(Subexpression) }
|
25
|
+
nodes = nodes.flat_map do |node|
|
26
|
+
if node.is_a?(Subexpression)
|
27
|
+
[node.left, node.right]
|
28
|
+
else
|
29
|
+
[node]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
nodes.map(&:optimize)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Chain
|
38
|
+
def initialize(children)
|
39
|
+
@children = children
|
40
|
+
end
|
41
|
+
|
42
|
+
def visit(value)
|
43
|
+
@children.reduce(value) do |v, child|
|
44
|
+
child.visit(v)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def optimize
|
49
|
+
children = @children.map(&:optimize)
|
50
|
+
index = 0
|
51
|
+
while index < children.size - 1
|
52
|
+
if children[index].chains_with?(children[index + 1])
|
53
|
+
children[index] = children[index].chain(children[index + 1])
|
54
|
+
children.delete_at(index + 1)
|
55
|
+
else
|
56
|
+
index += 1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
Chain.new(children)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
module Nodes
|
4
|
+
class Node
|
5
|
+
def visit(value)
|
6
|
+
end
|
7
|
+
|
8
|
+
def hash_like?(value)
|
9
|
+
Hash === value || Struct === value
|
10
|
+
end
|
11
|
+
|
12
|
+
def optimize
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def chains_with?(other)
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
autoload :Comparator, 'jmespath/nodes/comparator'
|
22
|
+
autoload :Condition, 'jmespath/nodes/condition'
|
23
|
+
autoload :Current, 'jmespath/nodes/current'
|
24
|
+
autoload :Expression, 'jmespath/nodes/expression'
|
25
|
+
autoload :Field, 'jmespath/nodes/field'
|
26
|
+
autoload :Flatten, 'jmespath/nodes/flatten'
|
27
|
+
autoload :Function, 'jmespath/nodes/function'
|
28
|
+
autoload :Index, 'jmespath/nodes/index'
|
29
|
+
autoload :Literal, 'jmespath/nodes/literal'
|
30
|
+
autoload :MultiSelectHash, 'jmespath/nodes/multi_select_hash'
|
31
|
+
autoload :MultiSelectList, 'jmespath/nodes/multi_select_list'
|
32
|
+
autoload :Or, 'jmespath/nodes/or'
|
33
|
+
autoload :Pipe, 'jmespath/nodes/pipe'
|
34
|
+
autoload :Projection, 'jmespath/nodes/projection'
|
35
|
+
autoload :ArrayProjection, 'jmespath/nodes/projection'
|
36
|
+
autoload :ObjectProjection, 'jmespath/nodes/projection'
|
37
|
+
autoload :Slice, 'jmespath/nodes/slice'
|
38
|
+
autoload :Subexpression, 'jmespath/nodes/subexpression'
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,288 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module JMESPath
|
4
|
+
# @api private
|
5
|
+
class Parser
|
6
|
+
|
7
|
+
# @api private
|
8
|
+
AFTER_DOT = Set.new([
|
9
|
+
:identifier, # foo.bar
|
10
|
+
:quoted_identifier, # foo."bar"
|
11
|
+
:star, # foo.*
|
12
|
+
:lbrace, # foo[1]
|
13
|
+
:lbracket, # foo{a: 0}
|
14
|
+
:function, # foo.*.to_string(@)
|
15
|
+
:filter, # foo.[?bar==10]
|
16
|
+
])
|
17
|
+
|
18
|
+
CURRENT_NODE = Nodes::Current.new
|
19
|
+
|
20
|
+
# @option options [Lexer] :lexer
|
21
|
+
def initialize(options = {})
|
22
|
+
@lexer = options[:lexer] || Lexer.new()
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [String<JMESPath>] expression
|
26
|
+
def parse(expression)
|
27
|
+
stream = TokenStream.new(expression, @lexer.tokenize(expression))
|
28
|
+
result = expr(stream)
|
29
|
+
if stream.token.type != :eof
|
30
|
+
raise Errors::SyntaxError, "expected :eof got #{stream.token.type}"
|
31
|
+
else
|
32
|
+
result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @api private
|
37
|
+
def method_missing(method_name, *args)
|
38
|
+
if matches = method_name.match(/^(nud_|led_)(.*)/)
|
39
|
+
raise Errors::SyntaxError, "unexpected token #{matches[2]}"
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# @param [TokenStream] stream
|
48
|
+
# @param [Integer] rbp Right binding power
|
49
|
+
def expr(stream, rbp = 0)
|
50
|
+
left = send("nud_#{stream.token.type}", stream)
|
51
|
+
while rbp < stream.token.binding_power
|
52
|
+
left = send("led_#{stream.token.type}", stream, left)
|
53
|
+
end
|
54
|
+
left
|
55
|
+
end
|
56
|
+
|
57
|
+
def nud_current(stream)
|
58
|
+
stream.next
|
59
|
+
CURRENT_NODE
|
60
|
+
end
|
61
|
+
|
62
|
+
def nud_expref(stream)
|
63
|
+
stream.next
|
64
|
+
Nodes::Expression.new(expr(stream, 2))
|
65
|
+
end
|
66
|
+
|
67
|
+
def nud_filter(stream)
|
68
|
+
led_filter(stream, CURRENT_NODE)
|
69
|
+
end
|
70
|
+
|
71
|
+
def nud_flatten(stream)
|
72
|
+
led_flatten(stream, CURRENT_NODE)
|
73
|
+
end
|
74
|
+
|
75
|
+
def nud_identifier(stream)
|
76
|
+
token = stream.token
|
77
|
+
n = stream.next
|
78
|
+
if n.type == :lparen
|
79
|
+
Nodes::Function::FunctionName.new(token.value)
|
80
|
+
else
|
81
|
+
Nodes::Field.new(token.value)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def nud_lbrace(stream)
|
86
|
+
valid_keys = Set.new([:quoted_identifier, :identifier])
|
87
|
+
stream.next(match:valid_keys)
|
88
|
+
pairs = []
|
89
|
+
begin
|
90
|
+
pairs << parse_key_value_pair(stream)
|
91
|
+
if stream.token.type == :comma
|
92
|
+
stream.next(match:valid_keys)
|
93
|
+
end
|
94
|
+
end while stream.token.type != :rbrace
|
95
|
+
stream.next
|
96
|
+
Nodes::MultiSelectHash.new(pairs)
|
97
|
+
end
|
98
|
+
|
99
|
+
def nud_lbracket(stream)
|
100
|
+
stream.next
|
101
|
+
type = stream.token.type
|
102
|
+
if type == :number || type == :colon
|
103
|
+
parse_array_index_expression(stream)
|
104
|
+
elsif type == :star && stream.lookahead(1).type == :rbracket
|
105
|
+
parse_wildcard_array(stream)
|
106
|
+
else
|
107
|
+
parse_multi_select_list(stream)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def nud_literal(stream)
|
112
|
+
value = stream.token.value
|
113
|
+
stream.next
|
114
|
+
Nodes::Literal.new(value)
|
115
|
+
end
|
116
|
+
|
117
|
+
def nud_quoted_identifier(stream)
|
118
|
+
token = stream.token
|
119
|
+
next_token = stream.next
|
120
|
+
if next_token.type == :lparen
|
121
|
+
msg = 'quoted identifiers are not allowed for function names'
|
122
|
+
raise Errors::SyntaxError, msg
|
123
|
+
else
|
124
|
+
Nodes::Field.new(token[:value])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def nud_star(stream)
|
129
|
+
parse_wildcard_object(stream, CURRENT_NODE)
|
130
|
+
end
|
131
|
+
|
132
|
+
def led_comparator(stream, left)
|
133
|
+
token = stream.token
|
134
|
+
stream.next
|
135
|
+
right = expr(stream)
|
136
|
+
Nodes::Comparator.create(token.value, left, right)
|
137
|
+
end
|
138
|
+
|
139
|
+
def led_dot(stream, left)
|
140
|
+
stream.next(match:AFTER_DOT)
|
141
|
+
if stream.token.type == :star
|
142
|
+
parse_wildcard_object(stream, left)
|
143
|
+
else
|
144
|
+
right = parse_dot(stream, Token::BINDING_POWER[:dot])
|
145
|
+
Nodes::Subexpression.new(left, right)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def led_filter(stream, left)
|
150
|
+
stream.next
|
151
|
+
expression = expr(stream)
|
152
|
+
if stream.token.type != :rbracket
|
153
|
+
raise Errors::SyntaxError, 'expected a closing rbracket for the filter'
|
154
|
+
end
|
155
|
+
stream.next
|
156
|
+
rhs = parse_projection(stream, Token::BINDING_POWER[:filter])
|
157
|
+
left ||= CURRENT_NODE
|
158
|
+
right = Nodes::Condition.new(expression, rhs)
|
159
|
+
Nodes::ArrayProjection.new(left, right)
|
160
|
+
end
|
161
|
+
|
162
|
+
def led_flatten(stream, left)
|
163
|
+
stream.next
|
164
|
+
left = Nodes::Flatten.new(left)
|
165
|
+
right = parse_projection(stream, Token::BINDING_POWER[:flatten])
|
166
|
+
Nodes::ArrayProjection.new(left, right)
|
167
|
+
end
|
168
|
+
|
169
|
+
def led_lbracket(stream, left)
|
170
|
+
stream.next(match: Set.new([:number, :colon, :star]))
|
171
|
+
type = stream.token.type
|
172
|
+
if type == :number || type == :colon
|
173
|
+
right = parse_array_index_expression(stream)
|
174
|
+
Nodes::Subexpression.new(left, right)
|
175
|
+
else
|
176
|
+
parse_wildcard_array(stream, left)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def led_lparen(stream, left)
|
181
|
+
args = []
|
182
|
+
name = left.name
|
183
|
+
stream.next
|
184
|
+
while stream.token.type != :rparen
|
185
|
+
args << expr(stream, 0)
|
186
|
+
if stream.token.type == :comma
|
187
|
+
stream.next
|
188
|
+
end
|
189
|
+
end
|
190
|
+
stream.next
|
191
|
+
Nodes::Function.create(name, args)
|
192
|
+
end
|
193
|
+
|
194
|
+
def led_or(stream, left)
|
195
|
+
stream.next
|
196
|
+
right = expr(stream, Token::BINDING_POWER[:or])
|
197
|
+
Nodes::Or.new(left, right)
|
198
|
+
end
|
199
|
+
|
200
|
+
def led_pipe(stream, left)
|
201
|
+
stream.next
|
202
|
+
right = expr(stream, Token::BINDING_POWER[:pipe])
|
203
|
+
Nodes::Pipe.new(left, right)
|
204
|
+
end
|
205
|
+
|
206
|
+
def parse_array_index_expression(stream)
|
207
|
+
pos = 0
|
208
|
+
parts = [nil, nil, nil]
|
209
|
+
begin
|
210
|
+
if stream.token.type == :colon
|
211
|
+
pos += 1
|
212
|
+
else
|
213
|
+
parts[pos] = stream.token.value
|
214
|
+
end
|
215
|
+
stream.next(match:Set.new([:number, :colon, :rbracket]))
|
216
|
+
end while stream.token.type != :rbracket
|
217
|
+
stream.next
|
218
|
+
if pos == 0
|
219
|
+
Nodes::Index.new(parts[0])
|
220
|
+
elsif pos > 2
|
221
|
+
raise Errors::SyntaxError, 'invalid array slice syntax: too many colons'
|
222
|
+
else
|
223
|
+
Nodes::Slice.new(*parts)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def parse_dot(stream, binding_power)
|
228
|
+
if stream.token.type == :lbracket
|
229
|
+
stream.next
|
230
|
+
parse_multi_select_list(stream)
|
231
|
+
else
|
232
|
+
expr(stream, binding_power)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def parse_key_value_pair(stream)
|
237
|
+
key = stream.token.value
|
238
|
+
stream.next(match:Set.new([:colon]))
|
239
|
+
stream.next
|
240
|
+
Nodes::MultiSelectHash::KeyValuePair.new(key, expr(stream))
|
241
|
+
end
|
242
|
+
|
243
|
+
def parse_multi_select_list(stream)
|
244
|
+
nodes = []
|
245
|
+
begin
|
246
|
+
nodes << expr(stream)
|
247
|
+
if stream.token.type == :comma
|
248
|
+
stream.next
|
249
|
+
if stream.token.type == :rbracket
|
250
|
+
raise Errors::SyntaxError, 'expression epxected, found rbracket'
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end while stream.token.type != :rbracket
|
254
|
+
stream.next
|
255
|
+
Nodes::MultiSelectList.new(nodes)
|
256
|
+
end
|
257
|
+
|
258
|
+
def parse_projection(stream, binding_power)
|
259
|
+
type = stream.token.type
|
260
|
+
if stream.token.binding_power < 10
|
261
|
+
CURRENT_NODE
|
262
|
+
elsif type == :dot
|
263
|
+
stream.next(match:AFTER_DOT)
|
264
|
+
parse_dot(stream, binding_power)
|
265
|
+
elsif type == :lbracket || type == :filter
|
266
|
+
expr(stream, binding_power)
|
267
|
+
else
|
268
|
+
raise Errors::SyntaxError, 'syntax error after projection'
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def parse_wildcard_array(stream, left = nil)
|
273
|
+
stream.next(match:Set.new([:rbracket]))
|
274
|
+
stream.next
|
275
|
+
left ||= CURRENT_NODE
|
276
|
+
right = parse_projection(stream, Token::BINDING_POWER[:star])
|
277
|
+
Nodes::ArrayProjection.new(left, right)
|
278
|
+
end
|
279
|
+
|
280
|
+
def parse_wildcard_object(stream, left = nil)
|
281
|
+
stream.next
|
282
|
+
left ||= CURRENT_NODE
|
283
|
+
right = parse_projection(stream, Token::BINDING_POWER[:star])
|
284
|
+
Nodes::ObjectProjection.new(left, right)
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
class Runtime
|
4
|
+
|
5
|
+
# Constructs a new runtime object for evaluating JMESPath expressions.
|
6
|
+
#
|
7
|
+
# runtime = JMESPath::Runtime.new
|
8
|
+
# runtime.search(expression, data)
|
9
|
+
# #=> ...
|
10
|
+
#
|
11
|
+
# ## Caching
|
12
|
+
#
|
13
|
+
# When constructing a {Runtime}, the default parser caches expressions.
|
14
|
+
# This significantly speeds up calls to {#search} multiple times
|
15
|
+
# with the same expression but different data. To disable caching, pass
|
16
|
+
# `:cache_expressions => false` to the constructor or pass a custom
|
17
|
+
# `:parser`.
|
18
|
+
#
|
19
|
+
# ## Optimizing
|
20
|
+
#
|
21
|
+
# By default the runtime will perform optimizations on the expression to
|
22
|
+
# try to make it run searches as fast as possible. If all your searches
|
23
|
+
# use different expressions this might not be worth the extra work, so you
|
24
|
+
# can disable the optimizer by passing `:optimize_expression => false`. If
|
25
|
+
# you disable caching it is also recommended that you disable optimizations,
|
26
|
+
# but you don't have to The optimizer will be disabled if you pass a custom
|
27
|
+
# parser with the `:parser` option.
|
28
|
+
#
|
29
|
+
# @example Re-use a Runtime, caching enabled by default
|
30
|
+
#
|
31
|
+
# runtime = JMESPath::Runtime.new
|
32
|
+
# runtime.parser
|
33
|
+
# #=> #<JMESPath::CachingParser ...>
|
34
|
+
#
|
35
|
+
# @example Disable caching
|
36
|
+
#
|
37
|
+
# runtime = JMESPath::Runtime.new(cache_expressions: false)
|
38
|
+
# runtime.parser
|
39
|
+
# #=> #<JMESPath::Parser ...>
|
40
|
+
#
|
41
|
+
# @option options [Boolean] :cache_expressions (true) When `false`, a non
|
42
|
+
# caching parser will be used. When `true`, a shared instance of
|
43
|
+
# {CachingParser} is used. Defaults to `true`.
|
44
|
+
#
|
45
|
+
# @option options [Boolean] :optimize_expressions (true) When `false`,
|
46
|
+
# no additional optimizations will be performed on the expression,
|
47
|
+
# when `true` the expression will be analyzed and optimized. This
|
48
|
+
# increases the time it takes to parse, but improves the speed of
|
49
|
+
# searches, so it's highly recommended if you're using the same expression
|
50
|
+
# multiple times and have not disabled caching. Defaults to `true`.
|
51
|
+
#
|
52
|
+
# @option options [Parser] :parser
|
53
|
+
#
|
54
|
+
def initialize(options = {})
|
55
|
+
@parser = options[:parser] || create_parser(options)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [Parser]
|
59
|
+
attr_reader :parser
|
60
|
+
|
61
|
+
# @param [String<JMESPath>] expression
|
62
|
+
# @param [Hash] data
|
63
|
+
# @return [Mixed,nil]
|
64
|
+
def search(expression, data)
|
65
|
+
@parser.parse(expression).visit(data)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def create_parser(options)
|
71
|
+
parser = Parser.new
|
72
|
+
unless options[:optimize_expression] == false
|
73
|
+
parser = OptimizingParser.new(parser)
|
74
|
+
end
|
75
|
+
unless options[:cache_expressions] == false
|
76
|
+
parser = CachingParser.new(parser)
|
77
|
+
end
|
78
|
+
parser
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
class Token < Struct.new(:type, :value, :position, :binding_power)
|
4
|
+
|
5
|
+
# @api private
|
6
|
+
NULL_TOKEN = Token.new(:eof, '', nil)
|
7
|
+
|
8
|
+
# binding power
|
9
|
+
# @api private
|
10
|
+
BINDING_POWER = {
|
11
|
+
:eof => 0,
|
12
|
+
:quoted_identifier => 0,
|
13
|
+
:identifier => 0,
|
14
|
+
:rbracket => 0,
|
15
|
+
:rparen => 0,
|
16
|
+
:comma => 0,
|
17
|
+
:rbrace => 0,
|
18
|
+
:number => 0,
|
19
|
+
:current => 0,
|
20
|
+
:expref => 0,
|
21
|
+
:pipe => 1,
|
22
|
+
:comparator => 2,
|
23
|
+
:or => 5,
|
24
|
+
:flatten => 6,
|
25
|
+
:star => 20,
|
26
|
+
:dot => 40,
|
27
|
+
:lbrace => 50,
|
28
|
+
:filter => 50,
|
29
|
+
:lbracket => 50,
|
30
|
+
:lparen => 60,
|
31
|
+
}
|
32
|
+
|
33
|
+
# @param [Symbol] type
|
34
|
+
# @param [Mixed] value
|
35
|
+
# @param [Integer] position
|
36
|
+
def initialize(type, value, position)
|
37
|
+
super(type, value, position, BINDING_POWER[type])
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
class TokenStream
|
4
|
+
|
5
|
+
# @param [String<JMESPath>] expression
|
6
|
+
# @param [Array<Token>] tokens
|
7
|
+
def initialize(expression, tokens)
|
8
|
+
@expression = expression
|
9
|
+
@tokens = tokens
|
10
|
+
@token = nil
|
11
|
+
@position = -1
|
12
|
+
self.next
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [String<JMESPath>]
|
16
|
+
attr_reader :expression
|
17
|
+
|
18
|
+
# @return [Token]
|
19
|
+
attr_reader :token
|
20
|
+
|
21
|
+
# @return [Integer]
|
22
|
+
attr_reader :position
|
23
|
+
|
24
|
+
# @option options [Array<Symbol>] :match Requires the next token to be
|
25
|
+
# one of the given symbols or an error is raised.
|
26
|
+
def next(options = {})
|
27
|
+
validate_match(_next, options[:match])
|
28
|
+
end
|
29
|
+
|
30
|
+
def lookahead(count)
|
31
|
+
@tokens[@position + count] || Token::NULL_TOKEN
|
32
|
+
end
|
33
|
+
|
34
|
+
# @api private
|
35
|
+
def inspect
|
36
|
+
str = []
|
37
|
+
@tokens.each do |token|
|
38
|
+
str << "%3d %-15s %s" %
|
39
|
+
[token.position, token.type, token.value.inspect]
|
40
|
+
end
|
41
|
+
str.join("\n")
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def _next
|
47
|
+
@position += 1
|
48
|
+
@token = @tokens[@position] || Token::NULL_TOKEN
|
49
|
+
end
|
50
|
+
|
51
|
+
def validate_match(token, match)
|
52
|
+
if match && !match.include?(token.type)
|
53
|
+
raise Errors::SyntaxError, "type missmatch"
|
54
|
+
else
|
55
|
+
token
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|