burtpath 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +174 -0
- data/lib/burtpath.rb +3 -0
- data/lib/jmespath/caching_parser.rb +30 -0
- data/lib/jmespath/errors.rb +17 -0
- data/lib/jmespath/lexer.rb +118 -0
- data/lib/jmespath/nodes/comparator.rb +77 -0
- data/lib/jmespath/nodes/condition.rb +136 -0
- data/lib/jmespath/nodes/current.rb +10 -0
- data/lib/jmespath/nodes/expression.rb +25 -0
- data/lib/jmespath/nodes/field.rb +63 -0
- data/lib/jmespath/nodes/flatten.rb +29 -0
- data/lib/jmespath/nodes/function.rb +464 -0
- data/lib/jmespath/nodes/index.rb +18 -0
- data/lib/jmespath/nodes/literal.rb +16 -0
- data/lib/jmespath/nodes/multi_select_hash.rb +37 -0
- data/lib/jmespath/nodes/multi_select_list.rb +22 -0
- data/lib/jmespath/nodes/or.rb +24 -0
- data/lib/jmespath/nodes/pipe.rb +6 -0
- data/lib/jmespath/nodes/projection.rb +86 -0
- data/lib/jmespath/nodes/slice.rb +93 -0
- data/lib/jmespath/nodes/subexpression.rb +63 -0
- data/lib/jmespath/nodes.rb +40 -0
- data/lib/jmespath/optimizing_parser.rb +12 -0
- data/lib/jmespath/parser.rb +288 -0
- data/lib/jmespath/runtime.rb +82 -0
- data/lib/jmespath/token.rb +41 -0
- data/lib/jmespath/token_stream.rb +60 -0
- data/lib/jmespath/version.rb +3 -0
- data/lib/jmespath.rb +42 -0
- metadata +89 -0
@@ -0,0 +1,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,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,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
|