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.
@@ -0,0 +1,29 @@
1
+ module JMESPath
2
+ # @api private
3
+ module Nodes
4
+ class Flatten < Node
5
+ def initialize(child)
6
+ @child = child
7
+ end
8
+
9
+ def visit(value)
10
+ value = @child.visit(value)
11
+ if Array === value
12
+ value.each_with_object([]) do |v, values|
13
+ if Array === v
14
+ values.concat(v)
15
+ else
16
+ values.push(v)
17
+ end
18
+ end
19
+ else
20
+ nil
21
+ end
22
+ end
23
+
24
+ def optimize
25
+ self.class.new(@child.optimize)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,464 @@
1
+ module JMESPath
2
+ # @api private
3
+ module Nodes
4
+ class Function < Node
5
+ FUNCTIONS = {}
6
+
7
+ def initialize(children)
8
+ @children = children
9
+ end
10
+
11
+ def self.create(name, children)
12
+ if (type = FUNCTIONS[name])
13
+ type.new(children)
14
+ else
15
+ raise Errors::UnknownFunctionError, "unknown function #{name}()"
16
+ end
17
+ end
18
+
19
+ def visit(value)
20
+ call(@children.map { |child| child.visit(value) })
21
+ end
22
+
23
+ def optimize
24
+ self.class.new(@children.map(&:optimize))
25
+ end
26
+
27
+ class FunctionName
28
+ attr_reader :name
29
+
30
+ def initialize(name)
31
+ @name = name
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def call(args)
38
+ nil
39
+ end
40
+ end
41
+
42
+ module TypeChecker
43
+ def get_type(value)
44
+ case value
45
+ when String then STRING_TYPE
46
+ when true, false then BOOLEAN_TYPE
47
+ when nil then NULL_TYPE
48
+ when Numeric then NUMBER_TYPE
49
+ when Hash, Struct then OBJECT_TYPE
50
+ when Array then ARRAY_TYPE
51
+ when Expression then EXPRESSION_TYPE
52
+ end
53
+ end
54
+
55
+ ARRAY_TYPE = 0
56
+ BOOLEAN_TYPE = 1
57
+ EXPRESSION_TYPE = 2
58
+ NULL_TYPE = 3
59
+ NUMBER_TYPE = 4
60
+ OBJECT_TYPE = 5
61
+ STRING_TYPE = 6
62
+
63
+ TYPE_NAMES = {
64
+ ARRAY_TYPE => 'array',
65
+ BOOLEAN_TYPE => 'boolean',
66
+ EXPRESSION_TYPE => 'expression',
67
+ NULL_TYPE => 'null',
68
+ NUMBER_TYPE => 'number',
69
+ OBJECT_TYPE => 'object',
70
+ STRING_TYPE => 'string',
71
+ }.freeze
72
+ end
73
+
74
+ class AbsFunction < Function
75
+ FUNCTIONS['abs'] = self
76
+
77
+ def call(args)
78
+ if args.count == 1
79
+ value = args.first
80
+ else
81
+ raise Errors::InvalidArityError, "function abs() expects one argument"
82
+ end
83
+ if Numeric === value
84
+ value.abs
85
+ else
86
+ raise Errors::InvalidTypeError, "function abs() expects a number"
87
+ end
88
+ end
89
+ end
90
+
91
+ class AvgFunction < Function
92
+ FUNCTIONS['avg'] = self
93
+
94
+ def call(args)
95
+ if args.count == 1
96
+ values = args.first
97
+ else
98
+ raise Errors::InvalidArityError, "function avg() expects one argument"
99
+ end
100
+ if Array === values
101
+ values.inject(0) do |total,n|
102
+ if Numeric === n
103
+ total + n
104
+ else
105
+ raise Errors::InvalidTypeError, "function avg() expects numeric values"
106
+ end
107
+ end / values.size.to_f
108
+ else
109
+ raise Errors::InvalidTypeError, "function avg() expects a number"
110
+ end
111
+ end
112
+ end
113
+
114
+ class CeilFunction < Function
115
+ FUNCTIONS['ceil'] = self
116
+
117
+ def call(args)
118
+ if args.count == 1
119
+ value = args.first
120
+ else
121
+ raise Errors::InvalidArityError, "function ceil() expects one argument"
122
+ end
123
+ if Numeric === value
124
+ value.ceil
125
+ else
126
+ raise Errors::InvalidTypeError, "function ceil() expects a numeric value"
127
+ end
128
+ end
129
+ end
130
+
131
+ class ContainsFunction < Function
132
+ FUNCTIONS['contains'] = self
133
+
134
+ def call(args)
135
+ if args.count == 2
136
+ haystack = args[0]
137
+ needle = args[1]
138
+ if String === haystack || Array === haystack
139
+ haystack.include?(needle)
140
+ else
141
+ raise Errors::InvalidTypeError, "contains expects 2nd arg to be a list"
142
+ end
143
+ else
144
+ raise Errors::InvalidArityError, "function contains() expects 2 arguments"
145
+ end
146
+ end
147
+ end
148
+
149
+ class FloorFunction < Function
150
+ FUNCTIONS['floor'] = self
151
+
152
+ def call(args)
153
+ if args.count == 1
154
+ value = args.first
155
+ else
156
+ raise Errors::InvalidArityError, "function floor() expects one argument"
157
+ end
158
+ if Numeric === value
159
+ value.floor
160
+ else
161
+ raise Errors::InvalidTypeError, "function floor() expects a numeric value"
162
+ end
163
+ end
164
+ end
165
+
166
+ class LengthFunction < Function
167
+ FUNCTIONS['length'] = self
168
+
169
+ def call(args)
170
+ if args.count == 1
171
+ value = args.first
172
+ else
173
+ raise Errors::InvalidArityError, "function length() expects one argument"
174
+ end
175
+ case value
176
+ when Hash, Array, String then value.size
177
+ else raise Errors::InvalidTypeError, "function length() expects string, array or object"
178
+ end
179
+ end
180
+ end
181
+
182
+ class MaxFunction < Function
183
+ FUNCTIONS['max'] = self
184
+
185
+ def call(args)
186
+ if args.count == 1
187
+ values = args.first
188
+ else
189
+ raise Errors::InvalidArityError, "function max() expects one argument"
190
+ end
191
+ if Array === values
192
+ values.inject(values.first) do |max, v|
193
+ if Numeric === v
194
+ v > max ? v : max
195
+ else
196
+ raise Errors::InvalidTypeError, "function max() expects numeric values"
197
+ end
198
+ end
199
+ else
200
+ raise Errors::InvalidTypeError, "function max() expects an array"
201
+ end
202
+ end
203
+ end
204
+
205
+ class MinFunction < Function
206
+ FUNCTIONS['min'] = self
207
+
208
+ def call(args)
209
+ if args.count == 1
210
+ values = args.first
211
+ else
212
+ raise Errors::InvalidArityError, "function min() expects one argument"
213
+ end
214
+ if Array === values
215
+ values.inject(values.first) do |min, v|
216
+ if Numeric === v
217
+ v < min ? v : min
218
+ else
219
+ raise Errors::InvalidTypeError, "function min() expects numeric values"
220
+ end
221
+ end
222
+ else
223
+ raise Errors::InvalidTypeError, "function min() expects an array"
224
+ end
225
+ end
226
+ end
227
+
228
+ class TypeFunction < Function
229
+ include TypeChecker
230
+
231
+ FUNCTIONS['type'] = self
232
+
233
+ def call(args)
234
+ if args.count == 1
235
+ TYPE_NAMES[get_type(args.first)]
236
+ else
237
+ raise Errors::InvalidArityError, "function type() expects one argument"
238
+ end
239
+ end
240
+ end
241
+
242
+ class KeysFunction < Function
243
+ FUNCTIONS['keys'] = self
244
+
245
+ def call(args)
246
+ if args.count == 1
247
+ value = args.first
248
+ if hash_like?(value)
249
+ case value
250
+ when Hash then value.keys.map(&:to_s)
251
+ when Struct then value.members.map(&:to_s)
252
+ else raise NotImplementedError
253
+ end
254
+ else
255
+ raise Errors::InvalidTypeError, "function keys() expects a hash"
256
+ end
257
+ else
258
+ raise Errors::InvalidArityError, "function keys() expects one argument"
259
+ end
260
+ end
261
+ end
262
+
263
+ class ValuesFunction < Function
264
+ FUNCTIONS['values'] = self
265
+
266
+ def call(args)
267
+ if args.count == 1
268
+ value = args.first
269
+ if hash_like?(value)
270
+ value.values
271
+ elsif Array === value
272
+ value
273
+ else
274
+ raise Errors::InvalidTypeError, "function values() expects an array or a hash"
275
+ end
276
+ else
277
+ raise Errors::InvalidArityError, "function values() expects one argument"
278
+ end
279
+ end
280
+ end
281
+
282
+ class JoinFunction < Function
283
+ FUNCTIONS['join'] = self
284
+
285
+ def call(args)
286
+ if args.count == 2
287
+ glue = args[0]
288
+ values = args[1]
289
+ if !(String === glue)
290
+ raise Errors::InvalidTypeError, "function join() expects the first argument to be a string"
291
+ elsif Array === values && values.all? { |v| String === v }
292
+ values.join(glue)
293
+ else
294
+ raise Errors::InvalidTypeError, "function join() expects values to be an array of strings"
295
+ end
296
+ else
297
+ raise Errors::InvalidArityError, "function join() expects an array of strings"
298
+ end
299
+ end
300
+ end
301
+
302
+ class ToStringFunction < Function
303
+ FUNCTIONS['to_string'] = self
304
+
305
+ def call(args)
306
+ if args.count == 1
307
+ value = args.first
308
+ String === value ? value : MultiJson.dump(value)
309
+ else
310
+ raise Errors::InvalidArityError, "function to_string() expects one argument"
311
+ end
312
+ end
313
+ end
314
+
315
+ class ToNumberFunction < Function
316
+ FUNCTIONS['to_number'] = self
317
+
318
+ def call(args)
319
+ if args.count == 1
320
+ begin
321
+ value = Float(args.first)
322
+ Integer(value) === value ? value.to_i : value
323
+ rescue
324
+ nil
325
+ end
326
+ else
327
+ raise Errors::InvalidArityError, "function to_number() expects one argument"
328
+ end
329
+ end
330
+ end
331
+
332
+ class SumFunction < Function
333
+ FUNCTIONS['sum'] = self
334
+
335
+ def call(args)
336
+ if args.count == 1 && Array === args.first
337
+ args.first.inject(0) do |sum,n|
338
+ if Numeric === n
339
+ sum + n
340
+ else
341
+ raise Errors::InvalidTypeError, "function sum() expects values to be numeric"
342
+ end
343
+ end
344
+ else
345
+ raise Errors::InvalidArityError, "function sum() expects one argument"
346
+ end
347
+ end
348
+ end
349
+
350
+ class NotNullFunction < Function
351
+ FUNCTIONS['not_null'] = self
352
+
353
+ def call(args)
354
+ if args.count > 0
355
+ args.find { |value| !value.nil? }
356
+ else
357
+ raise Errors::InvalidArityError, "function not_null() expects one or more arguments"
358
+ end
359
+ end
360
+ end
361
+
362
+ class SortFunction < Function
363
+ include TypeChecker
364
+
365
+ FUNCTIONS['sort'] = self
366
+
367
+ def call(args)
368
+ if args.count == 1
369
+ value = args.first
370
+ if Array === value
371
+ value.sort do |a, b|
372
+ a_type = get_type(a)
373
+ b_type = get_type(b)
374
+ if (a_type == STRING_TYPE || a_type == NUMBER_TYPE) && a_type == b_type
375
+ a <=> b
376
+ else
377
+ raise Errors::InvalidTypeError, "function sort() expects values to be an array of numbers or integers"
378
+ end
379
+ end
380
+ else
381
+ raise Errors::InvalidTypeError, "function sort() expects values to be an array of numbers or integers"
382
+ end
383
+ else
384
+ raise Errors::InvalidArityError, "function sort() expects one argument"
385
+ end
386
+ end
387
+ end
388
+
389
+ class SortByFunction < Function
390
+ include TypeChecker
391
+
392
+ FUNCTIONS['sort_by'] = self
393
+
394
+ def call(args)
395
+ if args.count == 2
396
+ if get_type(args[0]) == ARRAY_TYPE && get_type(args[1]) == EXPRESSION_TYPE
397
+ values = args[0]
398
+ expression = args[1]
399
+ values.sort do |a,b|
400
+ a_value = expression.eval(a)
401
+ b_value = expression.eval(b)
402
+ a_type = get_type(a_value)
403
+ b_type = get_type(b_value)
404
+ if (a_type == STRING_TYPE || a_type == NUMBER_TYPE) && a_type == b_type
405
+ a_value <=> b_value
406
+ else
407
+ raise Errors::InvalidTypeError, "function sort() expects values to be an array of numbers or integers"
408
+ end
409
+ end
410
+ else
411
+ raise Errors::InvalidTypeError, "function sort_by() expects an array and an expression"
412
+ end
413
+ else
414
+ raise Errors::InvalidArityError, "function sort_by() expects two arguments"
415
+ end
416
+ end
417
+ end
418
+
419
+ module NumberComparator
420
+ include TypeChecker
421
+
422
+ def number_compare(mode, *args)
423
+ if args.count == 2
424
+ values = args[0]
425
+ expression = args[1]
426
+ if get_type(values) == ARRAY_TYPE && get_type(expression) == EXPRESSION_TYPE
427
+ values.send(mode) do |entry|
428
+ value = expression.eval(entry)
429
+ if get_type(value) == NUMBER_TYPE
430
+ value
431
+ else
432
+ raise Errors::InvalidTypeError, "function #{mode}() expects values to be an numbers"
433
+ end
434
+ end
435
+ else
436
+ raise Errors::InvalidTypeError, "function #{mode}() expects an array and an expression"
437
+ end
438
+ else
439
+ raise Errors::InvalidArityError, "function #{mode}() expects two arguments"
440
+ end
441
+ end
442
+ end
443
+
444
+ class MaxByFunction < Function
445
+ include NumberComparator
446
+
447
+ FUNCTIONS['max_by'] = self
448
+
449
+ def call(args)
450
+ number_compare(:max_by, *args)
451
+ end
452
+ end
453
+
454
+ class MinByFunction < Function
455
+ include NumberComparator
456
+
457
+ FUNCTIONS['min_by'] = self
458
+
459
+ def call(args)
460
+ number_compare(:min_by, *args)
461
+ end
462
+ end
463
+ end
464
+ end
@@ -0,0 +1,18 @@
1
+ module JMESPath
2
+ # @api private
3
+ module Nodes
4
+ class Index < Field
5
+ def initialize(index)
6
+ super(index)
7
+ end
8
+
9
+ def visit(value)
10
+ if value.is_a?(Array)
11
+ value[@key]
12
+ else
13
+ nil
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ module JMESPath
2
+ # @api private
3
+ module Nodes
4
+ class Literal < Node
5
+ attr_reader :value
6
+
7
+ def initialize(value)
8
+ @value = value
9
+ end
10
+
11
+ def visit(value)
12
+ @value
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,37 @@
1
+ module JMESPath
2
+ # @api private
3
+ module Nodes
4
+ class MultiSelectHash < Node
5
+ def initialize(kv_pairs)
6
+ @kv_pairs = kv_pairs
7
+ end
8
+
9
+ def visit(value)
10
+ if value.nil?
11
+ nil
12
+ else
13
+ @kv_pairs.each_with_object({}) do |pair, hash|
14
+ hash[pair.key] = pair.value.visit(value)
15
+ end
16
+ end
17
+ end
18
+
19
+ def optimize
20
+ self.class.new(@kv_pairs.map(&:optimize))
21
+ end
22
+
23
+ class KeyValuePair
24
+ attr_reader :key, :value
25
+
26
+ def initialize(key, value)
27
+ @key = key
28
+ @value = value
29
+ end
30
+
31
+ def optimize
32
+ self.class.new(@key, @value.optimize)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ module JMESPath
2
+ # @api private
3
+ module Nodes
4
+ class MultiSelectList < Node
5
+ def initialize(children)
6
+ @children = children
7
+ end
8
+
9
+ def visit(value)
10
+ if value.nil?
11
+ value
12
+ else
13
+ @children.map { |n| n.visit(value) }
14
+ end
15
+ end
16
+
17
+ def optimize
18
+ self.class.new(@children.map(&:optimize))
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ module JMESPath
2
+ # @api private
3
+ module Nodes
4
+ class Or < Node
5
+ def initialize(left, right)
6
+ @left = left
7
+ @right = right
8
+ end
9
+
10
+ def visit(value)
11
+ result = @left.visit(value)
12
+ if result.nil? or result.empty?
13
+ @right.visit(value)
14
+ else
15
+ result
16
+ end
17
+ end
18
+
19
+ def optimize
20
+ self.class.new(@left.optimize, @right.optimize)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ module JMESPath
2
+ # @api private
3
+ module Nodes
4
+ Pipe = Subexpression
5
+ end
6
+ end
@@ -0,0 +1,86 @@
1
+ module JMESPath
2
+ # @api private
3
+ module Nodes
4
+ class Projection < Node
5
+ def initialize(target, projection)
6
+ @target = target
7
+ @projection = projection
8
+ end
9
+
10
+ def visit(value)
11
+ if (targets = extract_targets(@target.visit(value)))
12
+ list = []
13
+ targets.each do |v|
14
+ vv = @projection.visit(v)
15
+ unless vv.nil?
16
+ list << vv
17
+ end
18
+ end
19
+ list
20
+ end
21
+ end
22
+
23
+ def optimize
24
+ if @projection.is_a?(Current)
25
+ fast_instance
26
+ else
27
+ self.class.new(@target.optimize, @projection.optimize)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def extract_targets(left_value)
34
+ nil
35
+ end
36
+ end
37
+
38
+ module FastProjector
39
+ def visit(value)
40
+ if (targets = extract_targets(@target.visit(value)))
41
+ targets.compact
42
+ end
43
+ end
44
+ end
45
+
46
+ class ArrayProjection < Projection
47
+ def extract_targets(target)
48
+ if Array === target
49
+ target
50
+ else
51
+ nil
52
+ end
53
+ end
54
+
55
+ def fast_instance
56
+ FastArrayProjection.new(@target.optimize, @projection.optimize)
57
+ end
58
+ end
59
+
60
+ class FastArrayProjection < ArrayProjection
61
+ include FastProjector
62
+ end
63
+
64
+ class ObjectProjection < Projection
65
+ EMPTY_ARRAY = [].freeze
66
+
67
+ def extract_targets(target)
68
+ if hash_like?(target)
69
+ target.values
70
+ elsif target == EMPTY_ARRAY
71
+ EMPTY_ARRAY
72
+ else
73
+ nil
74
+ end
75
+ end
76
+
77
+ def fast_instance
78
+ FastObjectProjection.new(@target.optimize, @projection.optimize)
79
+ end
80
+ end
81
+
82
+ class FastObjectProjection < ObjectProjection
83
+ include FastProjector
84
+ end
85
+ end
86
+ end