burtpath 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|