jmespath 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of jmespath might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/lib/jmespath.rb +23 -0
- data/lib/jmespath/errors.rb +17 -0
- data/lib/jmespath/expr_node.rb +15 -0
- data/lib/jmespath/lexer.rb +118 -0
- data/lib/jmespath/parser.rb +350 -0
- data/lib/jmespath/runtime.rb +20 -0
- data/lib/jmespath/token.rb +41 -0
- data/lib/jmespath/token_stream.rb +60 -0
- data/lib/jmespath/tree_interpreter.rb +525 -0
- data/lib/jmespath/version.rb +3 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1dbe0a2b46b3a401846035f1c310a6d9e3e90aeb
|
4
|
+
data.tar.gz: c25be88050e43b57d065b976cce0fd6b6eebcff0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bea4a7c9e7d4013eefb09b186e30c8012d44ebf56fed7611cb5df19ee533e0a0b9b183bc031268e224d36b7c2afb96a4ff092cc8db22ba5d4e295169ee8afe7c
|
7
|
+
data.tar.gz: f94c1a2a49ee75d6f86a345d0ac2f31d799c407e376697302ed29814d5733b3612ba822b1151b2f19bba9814eb95e9f175e7b75ba399a5ba629d3d5af5eb35d7
|
data/lib/jmespath.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module JMESPath
|
2
|
+
|
3
|
+
autoload :Errors, 'jmespath/errors'
|
4
|
+
autoload :ExprNode, 'jmespath/expr_node'
|
5
|
+
autoload :Lexer, 'jmespath/lexer'
|
6
|
+
autoload :Parser, 'jmespath/parser'
|
7
|
+
autoload :Runtime, 'jmespath/runtime'
|
8
|
+
autoload :Token, 'jmespath/token'
|
9
|
+
autoload :TokenStream, 'jmespath/token_stream'
|
10
|
+
autoload :TreeInterpreter, 'jmespath/tree_interpreter'
|
11
|
+
autoload :VERSION, 'jmespath/version'
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
# @param [String<JMESPath>] expression
|
16
|
+
# @param [Hash] data
|
17
|
+
# @return [Mixed,nil]
|
18
|
+
def search(expression, data)
|
19
|
+
Runtime.new.search(expression, data)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module JMESPath
|
2
|
+
module Errors
|
3
|
+
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class RuntimeError < Error; end
|
7
|
+
|
8
|
+
class SyntaxError < Error; end
|
9
|
+
|
10
|
+
class InvalidTypeError < Error; end
|
11
|
+
|
12
|
+
class InvalidArityError < Error; end
|
13
|
+
|
14
|
+
class UnknownFunctionError < Error; end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module JMESPath
|
4
|
+
# @api private
|
5
|
+
class Lexer
|
6
|
+
|
7
|
+
# @api private
|
8
|
+
TOKEN_PATTERNS = {}
|
9
|
+
|
10
|
+
# @api private
|
11
|
+
TOKEN_TYPES = {}
|
12
|
+
|
13
|
+
{
|
14
|
+
'[a-zA-Z_][a-zA-Z_0-9]*' => :identifier,
|
15
|
+
'\.' => :dot,
|
16
|
+
'\*' => :star,
|
17
|
+
'\[\]' => :flatten,
|
18
|
+
'-?\d+' => :number,
|
19
|
+
'\|\|' => :or,
|
20
|
+
'\|' => :pipe,
|
21
|
+
'\[\?' => :filter,
|
22
|
+
'\[' => :lbracket,
|
23
|
+
'\]' => :rbracket,
|
24
|
+
'"(?:\\\\\\\\|\\\\"|[^"])*"' => :quoted_identifier,
|
25
|
+
'`(?:\\\\\\\\|\\\\`|[^`])*`' => :literal,
|
26
|
+
',' => :comma,
|
27
|
+
':' => :colon,
|
28
|
+
'@' => :current,
|
29
|
+
'&' => :expref,
|
30
|
+
'\(' => :lparen,
|
31
|
+
'\)' => :rparen,
|
32
|
+
'\{' => :lbrace,
|
33
|
+
'\}' => :rbrace,
|
34
|
+
'!=' => :comparator,
|
35
|
+
'==' => :comparator,
|
36
|
+
'<=' => :comparator,
|
37
|
+
'>=' => :comparator,
|
38
|
+
'<' => :comparator,
|
39
|
+
'>' => :comparator,
|
40
|
+
'[ \t]' => :skip,
|
41
|
+
}.each.with_index do |(pattern, type), n|
|
42
|
+
TOKEN_PATTERNS[n] = pattern
|
43
|
+
TOKEN_TYPES[n] = type
|
44
|
+
end
|
45
|
+
|
46
|
+
# @api private
|
47
|
+
TOKEN_REGEX = /(#{TOKEN_PATTERNS.values.join(')|(')})/
|
48
|
+
|
49
|
+
# @api private
|
50
|
+
JSON_VALUE = /^[\["{]/
|
51
|
+
|
52
|
+
# @api private
|
53
|
+
JSON_NUMBER = /^\-?[0-9]*(\.[0-9]+)?([e|E][+|\-][0-9]+)?$/
|
54
|
+
|
55
|
+
# @param [String<JMESPath>] expression
|
56
|
+
# @return [Array<Hash>]
|
57
|
+
def tokenize(expression)
|
58
|
+
offset = 0
|
59
|
+
tokens = []
|
60
|
+
expression.scan(TOKEN_REGEX).each do |match|
|
61
|
+
match_index = match.find_index { |token| !token.nil? }
|
62
|
+
match_value = match[match_index]
|
63
|
+
type = TOKEN_TYPES[match_index]
|
64
|
+
token = Token.new(type, match_value, offset)
|
65
|
+
if token.type != :skip
|
66
|
+
case token.type
|
67
|
+
when :number then token_number(token, expression, offset)
|
68
|
+
when :literal then token_literal(token, expression, offset)
|
69
|
+
when :quoted_identifier
|
70
|
+
token_quoted_identifier(token, expression, offset)
|
71
|
+
end
|
72
|
+
tokens << token
|
73
|
+
end
|
74
|
+
offset += match_value.size
|
75
|
+
end
|
76
|
+
tokens << Token.new(:eof, nil, offset)
|
77
|
+
unless expression.size == offset
|
78
|
+
syntax_error('invalid expression', expression, offset)
|
79
|
+
end
|
80
|
+
tokens
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def token_number(token, expression, offset)
|
86
|
+
token[:value] = token[:value].to_i
|
87
|
+
end
|
88
|
+
|
89
|
+
def token_literal(token, expression, offset)
|
90
|
+
token[:value] = token[:value][1..-2].lstrip.gsub('\`', '`')
|
91
|
+
token[:value] =
|
92
|
+
case token[:value]
|
93
|
+
when 'true', 'false' then token[:value] == 'true'
|
94
|
+
when 'null' then nil
|
95
|
+
when '' then syntax_error("empty json literal", expression, offset)
|
96
|
+
when JSON_VALUE then decode_json(token[:value], expression, offset)
|
97
|
+
when JSON_NUMBER then decode_json(token[:value], expression, offset)
|
98
|
+
else decode_json('"' + token[:value] + '"', expression, offset)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def token_quoted_identifier(token, expression, offset)
|
103
|
+
token[:value] = decode_json(token[:value], expression, offset)
|
104
|
+
end
|
105
|
+
|
106
|
+
def decode_json(json, expression, offset)
|
107
|
+
MultiJson.load(json)
|
108
|
+
rescue MultiJson::ParseError => e
|
109
|
+
syntax_error(e.message, expression, offset)
|
110
|
+
end
|
111
|
+
|
112
|
+
def syntax_error(message, expression, offset)
|
113
|
+
msg = message + "in #{expression.inspect} at #{offset}"
|
114
|
+
raise Errors::SyntaxError.new(msg)
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,350 @@
|
|
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 = { type: :current }
|
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
|
+
#puts "\n" + stream.inspect + "\n\n"
|
29
|
+
result = expr(stream)
|
30
|
+
if stream.token.type != :eof
|
31
|
+
raise Errors::SyntaxError, "expected :eof got #{stream.token.type}"
|
32
|
+
else
|
33
|
+
result
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
def method_missing(method_name, *args)
|
39
|
+
if matches = method_name.match(/^(nud_|led_)(.*)/)
|
40
|
+
raise Errors::SyntaxError, "unexpected token #{matches[2]}"
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# @param [TokenStream] stream
|
49
|
+
# @param [Integer] rbp Right binding power
|
50
|
+
def expr(stream, rbp = 0)
|
51
|
+
#puts "nud_#{stream.token.type}"
|
52
|
+
left = send("nud_#{stream.token.type}", stream)
|
53
|
+
while rbp < stream.token.binding_power
|
54
|
+
#puts "#{rbp} #{stream.token.binding_power} led_#{stream.token.type}"
|
55
|
+
left = send("led_#{stream.token.type}", stream, left)
|
56
|
+
end
|
57
|
+
left
|
58
|
+
end
|
59
|
+
|
60
|
+
def nud_current(stream)
|
61
|
+
stream.next
|
62
|
+
CURRENT_NODE
|
63
|
+
end
|
64
|
+
|
65
|
+
def nud_expref(stream)
|
66
|
+
stream.next
|
67
|
+
{
|
68
|
+
type: :expression,
|
69
|
+
children: [expr(stream, 2)]
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def nud_filter(stream)
|
74
|
+
led_filter(stream, CURRENT_NODE)
|
75
|
+
end
|
76
|
+
|
77
|
+
def nud_flatten(stream)
|
78
|
+
led_flatten(stream, CURRENT_NODE)
|
79
|
+
end
|
80
|
+
|
81
|
+
def nud_identifier(stream)
|
82
|
+
token = stream.token
|
83
|
+
stream.next
|
84
|
+
{ type: :field, key: token.value }
|
85
|
+
end
|
86
|
+
|
87
|
+
def nud_lbrace(stream)
|
88
|
+
valid_keys = Set.new([:quoted_identifier, :identifier])
|
89
|
+
stream.next(match:valid_keys)
|
90
|
+
pairs = []
|
91
|
+
begin
|
92
|
+
pairs << parse_key_value_pair(stream)
|
93
|
+
if stream.token.type == :comma
|
94
|
+
stream.next(match:valid_keys)
|
95
|
+
end
|
96
|
+
end while stream.token.type != :rbrace
|
97
|
+
stream.next
|
98
|
+
{
|
99
|
+
type: :multi_select_hash,
|
100
|
+
children: pairs
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
def nud_lbracket(stream)
|
105
|
+
stream.next
|
106
|
+
type = stream.token.type
|
107
|
+
if type == :number || type == :colon
|
108
|
+
parse_array_index_expression(stream)
|
109
|
+
elsif type == :star && stream.lookahead(1).type == :rbracket
|
110
|
+
parse_wildcard_array(stream)
|
111
|
+
else
|
112
|
+
parse_multi_select_list(stream)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def nud_literal(stream)
|
117
|
+
value = stream.token.value
|
118
|
+
stream.next
|
119
|
+
{
|
120
|
+
type: :literal,
|
121
|
+
value: value
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
def nud_quoted_identifier(stream)
|
126
|
+
token = stream.token
|
127
|
+
next_token = stream.next
|
128
|
+
if next_token.type == :lparen
|
129
|
+
msg = 'quoted identifiers are not allowed for function names'
|
130
|
+
raise Errors::SyntaxError, msg
|
131
|
+
else
|
132
|
+
{ type: :field, key: token[:value] }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def nud_star(stream)
|
137
|
+
parse_wildcard_object(stream, CURRENT_NODE)
|
138
|
+
end
|
139
|
+
|
140
|
+
def led_comparator(stream, left)
|
141
|
+
token = stream.token
|
142
|
+
stream.next
|
143
|
+
{
|
144
|
+
type: :comparator,
|
145
|
+
relation: token.value,
|
146
|
+
children: [
|
147
|
+
left,
|
148
|
+
expr(stream),
|
149
|
+
]
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
def led_dot(stream, left)
|
154
|
+
stream.next(match:AFTER_DOT)
|
155
|
+
if stream.token.type == :star
|
156
|
+
parse_wildcard_object(stream, left)
|
157
|
+
else
|
158
|
+
{
|
159
|
+
type: :subexpression,
|
160
|
+
children: [
|
161
|
+
left,
|
162
|
+
parse_dot(stream, Token::BINDING_POWER[:dot])
|
163
|
+
]
|
164
|
+
}
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def led_filter(stream, left)
|
169
|
+
stream.next
|
170
|
+
expression = expr(stream)
|
171
|
+
if stream.token.type != :rbracket
|
172
|
+
raise Errors::SyntaxError, 'expected a closing rbracket for the filter'
|
173
|
+
end
|
174
|
+
stream.next
|
175
|
+
rhs = parse_projection(stream, Token::BINDING_POWER[:filter])
|
176
|
+
{
|
177
|
+
type: :projection,
|
178
|
+
from: :array,
|
179
|
+
children: [
|
180
|
+
left ? left : CURRENT_NODE,
|
181
|
+
{
|
182
|
+
type: :condition,
|
183
|
+
children: [expression, rhs],
|
184
|
+
}
|
185
|
+
]
|
186
|
+
}
|
187
|
+
end
|
188
|
+
|
189
|
+
def led_flatten(stream, left)
|
190
|
+
stream.next
|
191
|
+
{
|
192
|
+
type: :projection,
|
193
|
+
from: :array,
|
194
|
+
children: [
|
195
|
+
{ type: :flatten, children: [left] },
|
196
|
+
parse_projection(stream, Token::BINDING_POWER[:flatten])
|
197
|
+
]
|
198
|
+
}
|
199
|
+
end
|
200
|
+
|
201
|
+
def led_lbracket(stream, left)
|
202
|
+
stream.next(match: Set.new([:number, :colon, :star]))
|
203
|
+
type = stream.token.type
|
204
|
+
if type == :number || type == :colon
|
205
|
+
{
|
206
|
+
type: :subexpression,
|
207
|
+
children: [
|
208
|
+
left,
|
209
|
+
parse_array_index_expression(stream)
|
210
|
+
]
|
211
|
+
}
|
212
|
+
else
|
213
|
+
parse_wildcard_array(stream, left)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def led_lparen(stream, left)
|
218
|
+
args = []
|
219
|
+
name = left[:key]
|
220
|
+
stream.next
|
221
|
+
while stream.token.type != :rparen
|
222
|
+
args << expr(stream, 0)
|
223
|
+
if stream.token.type == :comma
|
224
|
+
stream.next
|
225
|
+
end
|
226
|
+
end
|
227
|
+
stream.next
|
228
|
+
{
|
229
|
+
type: :function,
|
230
|
+
fn: name,
|
231
|
+
children: args,
|
232
|
+
}
|
233
|
+
end
|
234
|
+
|
235
|
+
def led_or(stream, left)
|
236
|
+
stream.next
|
237
|
+
{
|
238
|
+
type: :or,
|
239
|
+
children: [left, expr(stream, Token::BINDING_POWER[:or])]
|
240
|
+
}
|
241
|
+
end
|
242
|
+
|
243
|
+
def led_pipe(stream, left)
|
244
|
+
stream.next
|
245
|
+
{
|
246
|
+
type: :pipe,
|
247
|
+
children: [left, expr(stream, Token::BINDING_POWER[:pipe])],
|
248
|
+
}
|
249
|
+
end
|
250
|
+
|
251
|
+
def parse_array_index_expression(stream)
|
252
|
+
pos = 0
|
253
|
+
parts = [nil, nil, nil]
|
254
|
+
begin
|
255
|
+
if stream.token.type == :colon
|
256
|
+
pos += 1
|
257
|
+
else
|
258
|
+
parts[pos] = stream.token.value
|
259
|
+
end
|
260
|
+
stream.next(match:Set.new([:number, :colon, :rbracket]))
|
261
|
+
end while stream.token.type != :rbracket
|
262
|
+
stream.next
|
263
|
+
if pos == 0
|
264
|
+
{ type: :index, index: parts[0] }
|
265
|
+
elsif pos > 2
|
266
|
+
raise Errors::SyntaxError, 'invalid array slice syntax: too many colons'
|
267
|
+
else
|
268
|
+
{ type: :slice, args: parts }
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def parse_dot(stream, binding_power)
|
273
|
+
if stream.token.type == :lbracket
|
274
|
+
stream.next
|
275
|
+
parse_multi_select_list(stream)
|
276
|
+
else
|
277
|
+
expr(stream, binding_power)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def parse_key_value_pair(stream)
|
282
|
+
key = stream.token.value
|
283
|
+
stream.next(match:Set.new([:colon]))
|
284
|
+
stream.next
|
285
|
+
{
|
286
|
+
type: :key_value_pair,
|
287
|
+
key: key,
|
288
|
+
children: [expr(stream)]
|
289
|
+
}
|
290
|
+
end
|
291
|
+
|
292
|
+
def parse_multi_select_list(stream)
|
293
|
+
nodes = []
|
294
|
+
begin
|
295
|
+
nodes << expr(stream)
|
296
|
+
if stream.token.type == :comma
|
297
|
+
stream.next
|
298
|
+
if stream.token.type == :rbracket
|
299
|
+
raise Errors::SyntaxError, 'expression epxected, found rbracket'
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end while stream.token.type != :rbracket
|
303
|
+
stream.next
|
304
|
+
{
|
305
|
+
type: :multi_select_list,
|
306
|
+
children: nodes
|
307
|
+
}
|
308
|
+
end
|
309
|
+
|
310
|
+
def parse_projection(stream, binding_power)
|
311
|
+
type = stream.token.type
|
312
|
+
if stream.token.binding_power < 10
|
313
|
+
CURRENT_NODE
|
314
|
+
elsif type == :dot
|
315
|
+
stream.next(match:AFTER_DOT)
|
316
|
+
parse_dot(stream, binding_power)
|
317
|
+
elsif type == :lbracket || type == :filter
|
318
|
+
expr(stream, binding_power)
|
319
|
+
else
|
320
|
+
raise Errors::SyntaxError, 'syntax error after projection'
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def parse_wildcard_array(stream, left = nil)
|
325
|
+
stream.next(match:Set.new([:rbracket]))
|
326
|
+
stream.next
|
327
|
+
{
|
328
|
+
type: :projection,
|
329
|
+
from: :array,
|
330
|
+
children: [
|
331
|
+
left ? left : CURRENT_NODE,
|
332
|
+
parse_projection(stream, Token::BINDING_POWER[:star])
|
333
|
+
]
|
334
|
+
}
|
335
|
+
end
|
336
|
+
|
337
|
+
def parse_wildcard_object(stream, left = nil)
|
338
|
+
stream.next
|
339
|
+
{
|
340
|
+
type: :projection,
|
341
|
+
from: :object,
|
342
|
+
children: [
|
343
|
+
left ? left : CURRENT_NODE,
|
344
|
+
parse_projection(stream, Token::BINDING_POWER[:star])
|
345
|
+
]
|
346
|
+
}
|
347
|
+
end
|
348
|
+
|
349
|
+
end
|
350
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
class Runtime
|
4
|
+
|
5
|
+
# @option options [Parser] :parser
|
6
|
+
# @option options [Interpreter] :interpreter
|
7
|
+
def initialize(options = {})
|
8
|
+
@parser = options[:parser] || Parser.new
|
9
|
+
@interpreter = options[:interpreter] || TreeInterpreter.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [String<JMESPath>] expression
|
13
|
+
# @param [Hash] data
|
14
|
+
# @return [Mixed,nil]
|
15
|
+
def search(expression, data)
|
16
|
+
@interpreter.visit(@parser.parse(expression), data)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
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
|
@@ -0,0 +1,525 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
class TreeInterpreter
|
4
|
+
|
5
|
+
def visit(node, data)
|
6
|
+
dispatch(node, data)
|
7
|
+
end
|
8
|
+
|
9
|
+
# @api private
|
10
|
+
def method_missing(method_name, *args)
|
11
|
+
if matches = method_name.to_s.match(/^function_(.*)/)
|
12
|
+
raise Errors::UnknownFunctionError, "unknown function #{matches[1]}()"
|
13
|
+
else
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def dispatch(node, value)
|
21
|
+
case node[:type]
|
22
|
+
|
23
|
+
when :field
|
24
|
+
if hash_like?(value)
|
25
|
+
value[node[:key]]
|
26
|
+
else
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
when :subexpression
|
31
|
+
dispatch(node[:children][1], dispatch(node[:children][0], value))
|
32
|
+
|
33
|
+
when :index
|
34
|
+
if array_like?(value)
|
35
|
+
value[node[:index]]
|
36
|
+
else
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
when :projection
|
41
|
+
# Interprets a projection node, passing the values of the left
|
42
|
+
# child through the values of the right child and aggregating
|
43
|
+
# the non-null results into the return value.
|
44
|
+
left = dispatch(node[:children][0], value)
|
45
|
+
if node[:from] == :object && hash_like?(left)
|
46
|
+
projection(left.values, node)
|
47
|
+
elsif node[:from] == :object && left == []
|
48
|
+
projection(left, node)
|
49
|
+
elsif node[:from] == :array && array_like?(left)
|
50
|
+
projection(left, node)
|
51
|
+
else
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
when :flatten
|
56
|
+
value = dispatch(node[:children][0], value)
|
57
|
+
if array_like?(value)
|
58
|
+
value.inject([]) do |values, v|
|
59
|
+
values + (array_like?(v) ? v : [v])
|
60
|
+
end
|
61
|
+
else
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
when :literal
|
66
|
+
node[:value]
|
67
|
+
|
68
|
+
when :current
|
69
|
+
value
|
70
|
+
|
71
|
+
when :or
|
72
|
+
result = dispatch(node[:children][0], value)
|
73
|
+
if result.nil? or result.empty?
|
74
|
+
dispatch(node[:children][1], value)
|
75
|
+
else
|
76
|
+
result
|
77
|
+
end
|
78
|
+
|
79
|
+
when :pipe
|
80
|
+
dispatch(node[:children][1], dispatch(node[:children][0], value))
|
81
|
+
|
82
|
+
when :multi_select_list
|
83
|
+
if value.nil?
|
84
|
+
value
|
85
|
+
else
|
86
|
+
node[:children].map { |n| dispatch(n, value) }
|
87
|
+
end
|
88
|
+
|
89
|
+
when :multi_select_hash
|
90
|
+
if value.nil?
|
91
|
+
nil
|
92
|
+
else
|
93
|
+
node[:children].each.with_object({}) do |child, hash|
|
94
|
+
hash[child[:key]] = dispatch(child[:children][0], value)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
when :comparator
|
100
|
+
left = dispatch(node[:children][0], value)
|
101
|
+
right = dispatch(node[:children][1], value)
|
102
|
+
case node[:relation]
|
103
|
+
when '==' then compare_values(left, right)
|
104
|
+
when '!=' then !compare_values(left, right)
|
105
|
+
when '>' then is_int(left) && is_int(right) && left > right
|
106
|
+
when '>=' then is_int(left) && is_int(right) && left >= right
|
107
|
+
when '<' then is_int(left) && is_int(right) && left < right
|
108
|
+
when '<=' then is_int(left) && is_int(right) && left <= right
|
109
|
+
end
|
110
|
+
|
111
|
+
when :condition
|
112
|
+
true == dispatch(node[:children][0], value) ?
|
113
|
+
dispatch(node[:children][1], value) :
|
114
|
+
nil
|
115
|
+
|
116
|
+
when :function
|
117
|
+
args = node[:children].map { |child| dispatch(child, value) }
|
118
|
+
send("function_#{node[:fn]}", *args)
|
119
|
+
|
120
|
+
when :slice
|
121
|
+
function_slice(value, *node[:args])
|
122
|
+
|
123
|
+
when :expression
|
124
|
+
ExprNode.new(self, node[:children][0])
|
125
|
+
|
126
|
+
else
|
127
|
+
raise NotImplementedError
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def hash_like?(value)
|
132
|
+
Hash === value || Struct === value
|
133
|
+
end
|
134
|
+
|
135
|
+
def array_like?(value)
|
136
|
+
Array === value
|
137
|
+
end
|
138
|
+
|
139
|
+
def projection(values, node)
|
140
|
+
values.inject([]) do |list, v|
|
141
|
+
list << dispatch(node[:children][1], v)
|
142
|
+
end.compact
|
143
|
+
end
|
144
|
+
|
145
|
+
def function_abs(*args)
|
146
|
+
if args.count == 1
|
147
|
+
value = args.first
|
148
|
+
else
|
149
|
+
raise Errors::InvalidArityError, "function abs() expects one argument"
|
150
|
+
end
|
151
|
+
if Numeric === value
|
152
|
+
value.abs
|
153
|
+
else
|
154
|
+
raise Errors::InvalidTypeError, "function abs() expects a number"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def function_avg(*args)
|
159
|
+
if args.count == 1
|
160
|
+
values = args.first
|
161
|
+
else
|
162
|
+
raise Errors::InvalidArityError, "function avg() expects one argument"
|
163
|
+
end
|
164
|
+
if Array === values
|
165
|
+
values.inject(0) do |total,n|
|
166
|
+
if Numeric === n
|
167
|
+
total + n
|
168
|
+
else
|
169
|
+
raise Errors::InvalidTypeError, "function avg() expects numeric values"
|
170
|
+
end
|
171
|
+
end / values.size.to_f
|
172
|
+
else
|
173
|
+
raise Errors::InvalidTypeError, "function avg() expects a number"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def function_ceil(*args)
|
178
|
+
if args.count == 1
|
179
|
+
value = args.first
|
180
|
+
else
|
181
|
+
raise Errors::InvalidArityError, "function ceil() expects one argument"
|
182
|
+
end
|
183
|
+
if Numeric === value
|
184
|
+
value.ceil
|
185
|
+
else
|
186
|
+
raise Errors::InvalidTypeError, "function ceil() expects a numeric value"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def function_contains(*args)
|
191
|
+
if args.count == 2
|
192
|
+
if String === args[0] || Array === args[0]
|
193
|
+
args[0].include?(args[1])
|
194
|
+
else
|
195
|
+
raise Errors::InvalidTypeError, "contains expects 2nd arg to be a list"
|
196
|
+
end
|
197
|
+
else
|
198
|
+
raise Errors::InvalidArityError, "function contains() expects 2 arguments"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def function_floor(*args)
|
203
|
+
if args.count == 1
|
204
|
+
value = args.first
|
205
|
+
else
|
206
|
+
raise Errors::InvalidArityError, "function floor() expects one argument"
|
207
|
+
end
|
208
|
+
if Numeric === value
|
209
|
+
value.floor
|
210
|
+
else
|
211
|
+
raise Errors::InvalidTypeError, "function floor() expects a numeric value"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def function_length(*args)
|
216
|
+
if args.count == 1
|
217
|
+
value = args.first
|
218
|
+
else
|
219
|
+
raise Errors::InvalidArityError, "function length() expects one argument"
|
220
|
+
end
|
221
|
+
case value
|
222
|
+
when Hash, Array, String then value.size
|
223
|
+
else raise Errors::InvalidTypeError, "function length() expects string, array or object"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def function_max(*args)
|
228
|
+
if args.count == 1
|
229
|
+
values = args.first
|
230
|
+
else
|
231
|
+
raise Errors::InvalidArityError, "function max() expects one argument"
|
232
|
+
end
|
233
|
+
if Array === values
|
234
|
+
values.inject(values.first) do |max, v|
|
235
|
+
if Numeric === v
|
236
|
+
v > max ? v : max
|
237
|
+
else
|
238
|
+
raise Errors::InvalidTypeError, "function max() expects numeric values"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
else
|
242
|
+
raise Errors::InvalidTypeError, "function max() expects an array"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def function_min(*args)
|
247
|
+
if args.count == 1
|
248
|
+
values = args.first
|
249
|
+
else
|
250
|
+
raise Errors::InvalidArityError, "function min() expects one argument"
|
251
|
+
end
|
252
|
+
if Array === values
|
253
|
+
values.inject(values.first) do |min, v|
|
254
|
+
if Numeric === v
|
255
|
+
v < min ? v : min
|
256
|
+
else
|
257
|
+
raise Errors::InvalidTypeError, "function min() expects numeric values"
|
258
|
+
end
|
259
|
+
end
|
260
|
+
else
|
261
|
+
raise Errors::InvalidTypeError, "function min() expects an array"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def function_type(*args)
|
266
|
+
if args.count == 1
|
267
|
+
get_type(args.first)
|
268
|
+
else
|
269
|
+
raise Errors::InvalidArityError, "function type() expects one argument"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def function_keys(*args)
|
274
|
+
if args.count == 1
|
275
|
+
value = args.first
|
276
|
+
if hash_like?(value)
|
277
|
+
case value
|
278
|
+
when Hash then value.keys.map(&:to_s)
|
279
|
+
when Struct then value.members.map(&:to_s)
|
280
|
+
else raise NotImplementedError
|
281
|
+
end
|
282
|
+
else
|
283
|
+
raise Errors::InvalidTypeError, "function keys() expects a hash"
|
284
|
+
end
|
285
|
+
else
|
286
|
+
raise Errors::InvalidArityError, "function keys() expects one argument"
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def function_values(*args)
|
291
|
+
if args.count == 1
|
292
|
+
value = args.first
|
293
|
+
if hash_like?(value)
|
294
|
+
value.values
|
295
|
+
elsif array_like?(value)
|
296
|
+
value
|
297
|
+
else
|
298
|
+
raise Errors::InvalidTypeError, "function values() expects an array or a hash"
|
299
|
+
end
|
300
|
+
else
|
301
|
+
raise Errors::InvalidArityError, "function values() expects one argument"
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def function_join(*args)
|
306
|
+
if args.count == 2
|
307
|
+
glue = args[0]
|
308
|
+
values = args[1]
|
309
|
+
if !(String === glue)
|
310
|
+
raise Errors::InvalidTypeError, "function join() expects the first argument to be a string"
|
311
|
+
elsif array_like?(values) && values.all? { |v| String === v }
|
312
|
+
values.join(glue)
|
313
|
+
else
|
314
|
+
raise Errors::InvalidTypeError, "function join() expects values to be an array of strings"
|
315
|
+
end
|
316
|
+
else
|
317
|
+
raise Errors::InvalidArityError, "function join() expects an array of strings"
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def function_to_string(*args)
|
322
|
+
if args.count == 1
|
323
|
+
value = args.first
|
324
|
+
String === value ? value : MultiJson.dump(value)
|
325
|
+
else
|
326
|
+
raise Errors::InvalidArityError, "function to_string() expects one argument"
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def function_to_number(*args)
|
331
|
+
if args.count == 1
|
332
|
+
begin
|
333
|
+
value = Float(args.first)
|
334
|
+
Integer(value) === value ? value.to_i : value
|
335
|
+
rescue
|
336
|
+
nil
|
337
|
+
end
|
338
|
+
else
|
339
|
+
raise Errors::InvalidArityError, "function to_number() expects one argument"
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
def function_sum(*args)
|
344
|
+
if args.count == 1 && array_like?(args.first)
|
345
|
+
args.first.inject(0) do |sum,n|
|
346
|
+
if Numeric === n
|
347
|
+
sum + n
|
348
|
+
else
|
349
|
+
raise Errors::InvalidTypeError, "function sum() expects values to be numeric"
|
350
|
+
end
|
351
|
+
end
|
352
|
+
else
|
353
|
+
raise Errors::InvalidArityError, "function sum() expects one argument"
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def function_not_null(*args)
|
358
|
+
if args.count > 0
|
359
|
+
args.find { |value| !value.nil? }
|
360
|
+
else
|
361
|
+
raise Errors::InvalidArityError, "function not_null() expects one or more arguments"
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def function_sort(*args)
|
366
|
+
if args.count == 1
|
367
|
+
value = args.first
|
368
|
+
if array_like?(value)
|
369
|
+
value.sort do |a, b|
|
370
|
+
a_type = get_type(a)
|
371
|
+
b_type = get_type(b)
|
372
|
+
if ['string', 'number'].include?(a_type) && a_type == b_type
|
373
|
+
a <=> b
|
374
|
+
else
|
375
|
+
raise Errors::InvalidTypeError, "function sort() expects values to be an array of numbers or integers"
|
376
|
+
end
|
377
|
+
end
|
378
|
+
else
|
379
|
+
raise Errors::InvalidTypeError, "function sort() expects values to be an array of numbers or integers"
|
380
|
+
end
|
381
|
+
else
|
382
|
+
raise Errors::InvalidArityError, "function sort() expects one argument"
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
def function_sort_by(*args)
|
387
|
+
if args.count == 2
|
388
|
+
if get_type(args[0]) == 'array' && get_type(args[1]) == 'expression'
|
389
|
+
values = args[0]
|
390
|
+
expression = args[1]
|
391
|
+
values.sort do |a,b|
|
392
|
+
a_value = expression.interpreter.visit(expression.node, a)
|
393
|
+
b_value = expression.interpreter.visit(expression.node, b)
|
394
|
+
a_type = get_type(a_value)
|
395
|
+
b_type = get_type(b_value)
|
396
|
+
if ['string', 'number'].include?(a_type) && a_type == b_type
|
397
|
+
a_value <=> b_value
|
398
|
+
else
|
399
|
+
raise Errors::InvalidTypeError, "function sort() expects values to be an array of numbers or integers"
|
400
|
+
end
|
401
|
+
end
|
402
|
+
else
|
403
|
+
raise Errors::InvalidTypeError, "function sort_by() expects an array and an expression"
|
404
|
+
end
|
405
|
+
else
|
406
|
+
raise Errors::InvalidArityError, "function sort_by() expects two arguments"
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
def function_max_by(*args)
|
411
|
+
number_compare(:max, *args)
|
412
|
+
end
|
413
|
+
|
414
|
+
def function_min_by(*args)
|
415
|
+
number_compare(:min, *args)
|
416
|
+
end
|
417
|
+
|
418
|
+
def number_compare(mode, *args)
|
419
|
+
if args.count == 2
|
420
|
+
if get_type(args[0]) == 'array' && get_type(args[1]) == 'expression'
|
421
|
+
values = args[0]
|
422
|
+
expression = args[1]
|
423
|
+
args[0].send("#{mode}_by") do |entry|
|
424
|
+
value = expression.interpreter.visit(expression.node, entry)
|
425
|
+
if get_type(value) == 'number'
|
426
|
+
value
|
427
|
+
else
|
428
|
+
raise Errors::InvalidTypeError, "function #{mode}_by() expects values to be an numbers"
|
429
|
+
end
|
430
|
+
end
|
431
|
+
else
|
432
|
+
raise Errors::InvalidTypeError, "function #{mode}_by() expects an array and an expression"
|
433
|
+
end
|
434
|
+
else
|
435
|
+
raise Errors::InvalidArityError, "function #{mode}_by() expects two arguments"
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
def function_slice(values, *args)
|
440
|
+
if String === values || array_like?(values)
|
441
|
+
_slice(values, *args)
|
442
|
+
else
|
443
|
+
nil
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
def _slice(values, start, stop, step)
|
448
|
+
start, stop, step = _adjust_slice(values.size, start, stop, step)
|
449
|
+
result = []
|
450
|
+
if step > 0
|
451
|
+
i = start
|
452
|
+
while i < stop
|
453
|
+
result << values[i]
|
454
|
+
i += step
|
455
|
+
end
|
456
|
+
else
|
457
|
+
i = start
|
458
|
+
while i > stop
|
459
|
+
result << values[i]
|
460
|
+
i += step
|
461
|
+
end
|
462
|
+
end
|
463
|
+
String === values ? result.join : result
|
464
|
+
end
|
465
|
+
|
466
|
+
def _adjust_slice(length, start, stop, step)
|
467
|
+
if step.nil?
|
468
|
+
step = 1
|
469
|
+
elsif step == 0
|
470
|
+
raise Errors::RuntimeError, 'slice step cannot be 0'
|
471
|
+
end
|
472
|
+
|
473
|
+
if start.nil?
|
474
|
+
start = step < 0 ? length - 1 : 0
|
475
|
+
else
|
476
|
+
start = _adjust_endpoint(length, start, step)
|
477
|
+
end
|
478
|
+
|
479
|
+
if stop.nil?
|
480
|
+
stop = step < 0 ? -1 : length
|
481
|
+
else
|
482
|
+
stop = _adjust_endpoint(length, stop, step)
|
483
|
+
end
|
484
|
+
|
485
|
+
[start, stop, step]
|
486
|
+
end
|
487
|
+
|
488
|
+
def _adjust_endpoint(length, endpoint, step)
|
489
|
+
if endpoint < 0
|
490
|
+
endpoint += length
|
491
|
+
endpoint = 0 if endpoint < 0
|
492
|
+
endpoint
|
493
|
+
elsif endpoint >= length
|
494
|
+
step < 0 ? length - 1 : length
|
495
|
+
else
|
496
|
+
endpoint
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
def compare_values(a, b)
|
501
|
+
if a == b
|
502
|
+
true
|
503
|
+
else
|
504
|
+
false
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
def is_int(value)
|
509
|
+
Integer === value
|
510
|
+
end
|
511
|
+
|
512
|
+
def get_type(value)
|
513
|
+
case
|
514
|
+
when ExprNode === value then 'expression'
|
515
|
+
when String === value then 'string'
|
516
|
+
when hash_like?(value) then 'object'
|
517
|
+
when array_like?(value) then 'array'
|
518
|
+
when [true, false].include?(value) then 'boolean'
|
519
|
+
when value.nil? then 'null'
|
520
|
+
when Numeric === value then 'number'
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
end
|
525
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jmespath
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Trevor Rowe
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: multi_json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
description: Implementes JMESPath for Ruby
|
28
|
+
email: trevorrowe@gmail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- lib/jmespath/errors.rb
|
34
|
+
- lib/jmespath/expr_node.rb
|
35
|
+
- lib/jmespath/lexer.rb
|
36
|
+
- lib/jmespath/parser.rb
|
37
|
+
- lib/jmespath/runtime.rb
|
38
|
+
- lib/jmespath/token.rb
|
39
|
+
- lib/jmespath/token_stream.rb
|
40
|
+
- lib/jmespath/tree_interpreter.rb
|
41
|
+
- lib/jmespath/version.rb
|
42
|
+
- lib/jmespath.rb
|
43
|
+
homepage: http://github.com/trevorrowe/jmespath.rb
|
44
|
+
licenses:
|
45
|
+
- Apache 2.0
|
46
|
+
metadata: {}
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 2.1.11
|
64
|
+
signing_key:
|
65
|
+
specification_version: 4
|
66
|
+
summary: JMESPath - Ruby Edition
|
67
|
+
test_files: []
|
68
|
+
has_rdoc:
|