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 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,15 @@
1
+ module JMESPath
2
+ # @api private
3
+ class ExprNode
4
+
5
+ def initialize(interpreter, node)
6
+ @interpreter = interpreter
7
+ @node = node
8
+ end
9
+
10
+ attr_reader :interpreter
11
+
12
+ attr_reader :node
13
+
14
+ end
15
+ 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
@@ -0,0 +1,3 @@
1
+ module JMESPath
2
+ VERSION = '0.2.0'
3
+ 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: