enumpath 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/greetings.yml +13 -0
- data/.github/workflows/rubocop-analysis.yml +47 -0
- data/.github/workflows/ruby.yml +35 -0
- data/.rubocop.yml +12 -0
- data/CONTRIBUTING.md +13 -8
- data/Gemfile +4 -2
- data/Gemfile.lock +21 -16
- data/README.md +33 -19
- data/Rakefile +6 -4
- data/bin/console +4 -3
- data/enumpath.gemspec +11 -8
- data/lib/enumpath.rb +8 -8
- data/lib/enumpath/logger.rb +14 -11
- data/lib/enumpath/operator.rb +47 -26
- data/lib/enumpath/operator/base.rb +2 -2
- data/lib/enumpath/operator/child.rb +2 -2
- data/lib/enumpath/operator/filter_expression.rb +32 -26
- data/lib/enumpath/operator/recursive_descent.rb +7 -7
- data/lib/enumpath/operator/slice.rb +2 -2
- data/lib/enumpath/operator/subscript_expression.rb +16 -14
- data/lib/enumpath/operator/union.rb +1 -1
- data/lib/enumpath/operator/wildcard.rb +1 -1
- data/lib/enumpath/path.rb +10 -8
- data/lib/enumpath/path/normalized_path.rb +24 -15
- data/lib/enumpath/version.rb +1 -1
- metadata +52 -21
- data/.travis.yml +0 -5
data/bin/console
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'enumpath'
|
5
6
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +11,5 @@ require "enumpath"
|
|
10
11
|
# require "pry"
|
11
12
|
# Pry.start
|
12
13
|
|
13
|
-
require
|
14
|
+
require 'irb'
|
14
15
|
IRB.start(__FILE__)
|
data/enumpath.gemspec
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
|
-
lib = File.expand_path('
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'enumpath/version'
|
5
6
|
|
@@ -8,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
8
9
|
spec.version = Enumpath::VERSION
|
9
10
|
spec.license = 'Apache-2.0'
|
10
11
|
spec.summary = 'A JSONPath-compatible library for navigating nested Ruby objects using path expressions'
|
11
|
-
spec.description = <<~
|
12
|
+
spec.description = <<~DESC
|
12
13
|
Enumpath is an implementation of the JSONPath spec for Ruby objects,
|
13
14
|
plus some added sugar. It's like Ruby's native Enumerable#dig method,
|
14
15
|
but fancier. It is designed for situations where you need to provide
|
@@ -16,27 +17,29 @@ Gem::Specification.new do |spec|
|
|
16
17
|
objects. This makes it exceptionally well suited for flexible ETL
|
17
18
|
(Extract, Transform, Load) processes by allowing you to define paths
|
18
19
|
through your data in a simple, easily readable, easily storable syntax.
|
19
|
-
|
20
|
+
DESC
|
20
21
|
spec.authors = ['Chris Bloom']
|
21
|
-
spec.email = ['
|
22
|
+
spec.email = ['chrisbloom7@gmail.com']
|
22
23
|
|
23
24
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
24
25
|
f.match(%r{^(test|spec|features)/})
|
25
26
|
end
|
26
27
|
spec.require_paths = ['lib']
|
27
28
|
|
28
|
-
spec.homepage = 'https://github.com/
|
29
|
+
spec.homepage = 'https://github.com/chrisbloom7/enumpath'
|
29
30
|
|
30
31
|
# Enumerable#dig was added in Ruby 2.3.0
|
31
32
|
spec.required_ruby_version = '>= 2.3.0'
|
32
33
|
|
33
|
-
spec.add_dependency 'to_regexp', '~> 0.2.1'
|
34
34
|
spec.add_dependency 'mini_cache', '~> 1.1.0'
|
35
|
+
spec.add_dependency 'to_regexp', '~> 0.2.1'
|
35
36
|
|
36
|
-
spec.add_development_dependency 'bundler', '~> 1
|
37
|
+
spec.add_development_dependency 'bundler', '~> 2.1'
|
37
38
|
spec.add_development_dependency 'null-logger', '~> 0.1'
|
38
39
|
spec.add_development_dependency 'pry-byebug', '~> 3.6'
|
39
40
|
spec.add_development_dependency 'rake', '~> 12.3'
|
40
|
-
spec.add_development_dependency 'rspec-benchmark', '~> 0.3.0'
|
41
41
|
spec.add_development_dependency 'rspec', '~> 3.8'
|
42
|
+
spec.add_development_dependency 'rspec-benchmark', '~> 0.3.0'
|
43
|
+
spec.add_development_dependency 'rspec_junit_formatter', '~> 0.4'
|
44
|
+
spec.add_development_dependency 'yard', '~> 0.9.26'
|
42
45
|
end
|
data/lib/enumpath.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'mini_cache'
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
4
|
+
require 'enumpath/logger'
|
5
|
+
require 'enumpath/operator'
|
6
|
+
require 'enumpath/path'
|
7
|
+
require 'enumpath/results'
|
8
|
+
require 'enumpath/resolver/simple'
|
9
|
+
require 'enumpath/resolver/property'
|
10
|
+
require 'enumpath/version'
|
11
11
|
|
12
12
|
# A JSONPath-compatible library for navigating Ruby objects using path expressions
|
13
13
|
module Enumpath
|
@@ -48,7 +48,7 @@ module Enumpath
|
|
48
48
|
# @private
|
49
49
|
# @see Enumpath::Logger#log
|
50
50
|
def log(title)
|
51
|
-
block_given? ? logger.log(title,
|
51
|
+
block_given? ? logger.log(title, &-> { yield }) : logger.log(title)
|
52
52
|
end
|
53
53
|
|
54
54
|
# A lightweight in-memory cache for caching normalized path expressions
|
data/lib/enumpath/logger.rb
CHANGED
@@ -6,7 +6,7 @@ module Enumpath
|
|
6
6
|
# A logger for providing debugging information while evaluating path expressions
|
7
7
|
# @private
|
8
8
|
class Logger
|
9
|
-
PAD =
|
9
|
+
PAD = ' '
|
10
10
|
|
11
11
|
SEPARATOR = '--------------------------------------'
|
12
12
|
|
@@ -31,17 +31,11 @@ module Enumpath
|
|
31
31
|
# @yield A lazily evaluated hash of key/value pairs to include in the log message
|
32
32
|
def log(title)
|
33
33
|
return unless Enumpath.verbose
|
34
|
+
|
34
35
|
append_log "#{padding}#{SEPARATOR}\n"
|
35
36
|
append_log "#{padding}Enumpath: #{title}\n"
|
36
|
-
if block_given?
|
37
|
-
|
38
|
-
vars = yield
|
39
|
-
return unless vars.is_a?(Hash)
|
40
|
-
label_size = vars.keys.map(&:size).max
|
41
|
-
vars.each do |label, value|
|
42
|
-
append_log "#{padding}#{label.to_s.ljust(label_size)}: #{massaged_value(value)}\n"
|
43
|
-
end
|
44
|
-
end
|
37
|
+
append_log "#{padding}#{SEPARATOR}\n" if block_given?
|
38
|
+
log_vars(yield) if block_given?
|
45
39
|
end
|
46
40
|
|
47
41
|
private
|
@@ -50,7 +44,16 @@ module Enumpath
|
|
50
44
|
logger << message
|
51
45
|
end
|
52
46
|
|
53
|
-
def
|
47
|
+
def log_vars(vars)
|
48
|
+
return unless vars.is_a?(Hash)
|
49
|
+
|
50
|
+
label_size = vars.keys.map(&:size).max
|
51
|
+
vars.each do |label, value|
|
52
|
+
append_log "#{padding}#{label.to_s.ljust(label_size)}: #{massaged_value(value)}\n"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def massaged_value(value) # rubocop:disable Metrics/MethodLength
|
54
57
|
if value.is_a?(Enumerable)
|
55
58
|
enum_for_log(value)
|
56
59
|
elsif value.is_a?(TrueClass)
|
data/lib/enumpath/operator.rb
CHANGED
@@ -21,32 +21,53 @@ module Enumpath
|
|
21
21
|
# @param enum [Enumerable] the enumerable to assist in detecting child operators
|
22
22
|
# @return an instance of a subclass of Enumpath::Operator based on what was detected, or nil if nothing was
|
23
23
|
# detected
|
24
|
-
def detect(operator, enum)
|
25
|
-
if
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
24
|
+
def detect(operator, enum) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
25
|
+
return operator(:Child, operator) if child?(operator, enum)
|
26
|
+
return operator(:Wildcard, operator) if wildcard?(operator)
|
27
|
+
return operator(:RecursiveDescent, operator) if recursive_descent?(operator)
|
28
|
+
return operator(:Union, operator) if union?(operator)
|
29
|
+
return operator(:SubscriptExpression, operator) if subscript_expression?(operator)
|
30
|
+
return operator(:FilterExpression, operator) if filter_expression?(operator)
|
31
|
+
return operator(:Slice, operator) if slice?(operator)
|
32
|
+
|
33
|
+
Enumpath.log('Not a valid operator for enum')
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def child?(operator, enum)
|
40
|
+
Enumpath::Operator::Child.detect?(operator, enum)
|
41
|
+
end
|
42
|
+
|
43
|
+
def wildcard?(operator)
|
44
|
+
Enumpath::Operator::Wildcard.detect?(operator)
|
45
|
+
end
|
46
|
+
|
47
|
+
def recursive_descent?(operator)
|
48
|
+
Enumpath::Operator::RecursiveDescent.detect?(operator)
|
49
|
+
end
|
50
|
+
|
51
|
+
def union?(operator)
|
52
|
+
Enumpath::Operator::Union.detect?(operator)
|
53
|
+
end
|
54
|
+
|
55
|
+
def subscript_expression?(operator)
|
56
|
+
Enumpath::Operator::SubscriptExpression.detect?(operator)
|
57
|
+
end
|
58
|
+
|
59
|
+
def filter_expression?(operator)
|
60
|
+
Enumpath::Operator::FilterExpression.detect?(operator)
|
61
|
+
end
|
62
|
+
|
63
|
+
def slice?(operator)
|
64
|
+
Enumpath::Operator::Slice.detect?(operator)
|
65
|
+
end
|
66
|
+
|
67
|
+
def operator(operator_class, operator)
|
68
|
+
Enumpath.log("#{operator_class} operator detected")
|
69
|
+
klass = Object.const_get("Enumpath::Operator::#{operator_class}")
|
70
|
+
klass.new(operator)
|
50
71
|
end
|
51
72
|
end
|
52
73
|
end
|
@@ -16,7 +16,7 @@ module Enumpath
|
|
16
16
|
# @param enum [Enumerable] an enum that can be used to assist in detection. Not all subclasses require an enum
|
17
17
|
# for detection.
|
18
18
|
# @return [true, false] whether the operator param appears to represent the operator class
|
19
|
-
def detect?(
|
19
|
+
def detect?(_operator, _enum = nil)
|
20
20
|
raise NotImplementedError
|
21
21
|
end
|
22
22
|
end
|
@@ -47,7 +47,7 @@ module Enumpath
|
|
47
47
|
# @yieldparam enum [Enumerable] the new enum after applying the operator
|
48
48
|
# @yieldparam resolved_path [Array] the new resolved_path after applying the operator
|
49
49
|
# @yieldreturn [void]
|
50
|
-
def apply(
|
50
|
+
def apply(_remaining_path, _enum, _resolved_path)
|
51
51
|
raise NotImplementedError
|
52
52
|
end
|
53
53
|
|
@@ -24,10 +24,10 @@ module Enumpath
|
|
24
24
|
# @yieldparam remaining_path [Array] remaining_path
|
25
25
|
# @yieldparam enum [Enumerable] the resolved value of the enumerable
|
26
26
|
# @yieldparam resolved_path [Array] resolved_path plus the child operator
|
27
|
-
def apply(remaining_path, enum, resolved_path
|
27
|
+
def apply(remaining_path, enum, resolved_path)
|
28
28
|
value = Enumpath::Resolver::Simple.resolve(operator, enum)
|
29
29
|
value = Enumpath::Resolver::Property.resolve(operator, enum) if value.nil?
|
30
|
-
yield(remaining_path, value, resolved_path + [operator])
|
30
|
+
yield(remaining_path, value, resolved_path + [operator]) unless value.nil?
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
@@ -17,7 +17,7 @@ module Enumpath
|
|
17
17
|
# @param operator (see Enumpath::Operator::Base.detect?)
|
18
18
|
# @return (see Enumpath::Operator::Base.detect?)
|
19
19
|
def detect?(operator)
|
20
|
-
|
20
|
+
!(operator =~ OPERATOR_REGEX).nil?
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -31,26 +31,26 @@ module Enumpath
|
|
31
31
|
# the filter
|
32
32
|
def apply(remaining_path, enum, resolved_path, &block)
|
33
33
|
Enumpath.log('Evaluating filter expression') { { expression: operator, to: enum } }
|
34
|
-
|
35
34
|
_match, unpacked_operator = OPERATOR_REGEX.match(operator).to_a
|
36
35
|
expressions = unpacked_operator.split(LOGICAL_OPERATORS_REGEX).map(&:strip)
|
37
|
-
|
38
|
-
keys(enum).each do |key|
|
39
|
-
value = Enumpath::Resolver::Simple.resolve(key, enum)
|
40
|
-
Enumpath.log('Applying filter to key') { { key: key, enum: value } }
|
41
|
-
if pass?(expressions.dup, value)
|
42
|
-
Enumpath.log('Applying filtered key') { { 'filtered key': key, 'filtered enum': value } }
|
43
|
-
yield(remaining_path, value, resolved_path + [key.to_s])
|
44
|
-
end
|
45
|
-
end
|
36
|
+
keys(enum).each { |key| apply_to_key(key, expressions, remaining_path, enum, resolved_path, &block) }
|
46
37
|
end
|
47
38
|
|
48
39
|
private
|
49
40
|
|
41
|
+
def apply_to_key(key, expressions, remaining_path, enum, resolved_path)
|
42
|
+
value = Enumpath::Resolver::Simple.resolve(key, enum)
|
43
|
+
Enumpath.log('Applying filter to key') { { key: key, enum: value } }
|
44
|
+
return unless pass?(expressions.dup, value)
|
45
|
+
|
46
|
+
Enumpath.log('Applying filtered key') { { 'filtered key': key, 'filtered enum': value } }
|
47
|
+
yield(remaining_path, value, resolved_path + [key.to_s])
|
48
|
+
end
|
49
|
+
|
50
50
|
def pass?(expressions, enum)
|
51
51
|
running_result = evaluate(expressions.shift, enum)
|
52
52
|
Enumpath.log('Initial result') { { result: running_result } }
|
53
|
-
while expressions.any?
|
53
|
+
while expressions.any?
|
54
54
|
logical_operator, expression = expressions.shift(2)
|
55
55
|
running_result = evaluate(expression, enum, logical_operator, running_result)
|
56
56
|
Enumpath.log('Running result') { { result: running_result } }
|
@@ -66,34 +66,28 @@ module Enumpath
|
|
66
66
|
{ property => value, operator: operator, operand: operand, result: expression_result,
|
67
67
|
logical_operator: logical_operator }.compact
|
68
68
|
end
|
69
|
-
|
70
|
-
Enumpath.log('&&=')
|
71
|
-
running_result &&= expression_result
|
72
|
-
elsif logical_operator == '||'
|
73
|
-
Enumpath.log('||=')
|
74
|
-
running_result ||= expression_result
|
75
|
-
else
|
76
|
-
expression_result
|
77
|
-
end
|
69
|
+
running_result(logical_operator, running_result, expression_result)
|
78
70
|
end
|
79
71
|
|
80
72
|
def resolve(property, enum)
|
81
73
|
return enum if property == '@'
|
74
|
+
|
82
75
|
value = Enumpath::Resolver::Simple.resolve(property.gsub(/^@\./, ''), enum)
|
83
76
|
value = Enumpath::Resolver::Property.resolve(property.gsub(/^@\./, ''), enum) if value.nil?
|
84
77
|
value
|
85
78
|
end
|
86
79
|
|
87
80
|
def test(operator, operand, value)
|
88
|
-
return
|
81
|
+
return (value ? true : false) if operator.nil? || operand.nil?
|
82
|
+
|
89
83
|
typecast_operand = variable_typecaster(operand)
|
90
|
-
|
84
|
+
value.public_send(operator.to_sym, typecast_operand) ? true : false
|
91
85
|
rescue NoMethodError
|
92
86
|
Enumpath.log('Filter could not be evaluated!')
|
93
87
|
false
|
94
88
|
end
|
95
89
|
|
96
|
-
def variable_typecaster(variable)
|
90
|
+
def variable_typecaster(variable) # rubocop:disable Metrics/MethodLength
|
97
91
|
if variable =~ /\A('|").+\1\z/
|
98
92
|
# It quacks like a string
|
99
93
|
variable.gsub(/\A('|")|('|")\z/, '')
|
@@ -102,8 +96,8 @@ module Enumpath
|
|
102
96
|
variable.gsub(/\A:/, '').to_sym
|
103
97
|
elsif variable =~ /true|false|nil/i
|
104
98
|
# It quacks like an unquoted boolean operator
|
105
|
-
variable == 'true'
|
106
|
-
elsif regexp = variable.to_regexp(literal: false, detect: false)
|
99
|
+
variable == 'true'
|
100
|
+
elsif (regexp = variable.to_regexp(literal: false, detect: false))
|
107
101
|
# It quacks like a regex
|
108
102
|
regexp
|
109
103
|
else
|
@@ -111,6 +105,18 @@ module Enumpath
|
|
111
105
|
variable.to_f
|
112
106
|
end
|
113
107
|
end
|
108
|
+
|
109
|
+
def running_result(logical_operator, running_result, expression_result)
|
110
|
+
if logical_operator == '&&'
|
111
|
+
Enumpath.log('&&=')
|
112
|
+
running_result && expression_result
|
113
|
+
elsif logical_operator == '||'
|
114
|
+
Enumpath.log('||=')
|
115
|
+
running_result || expression_result
|
116
|
+
else
|
117
|
+
expression_result
|
118
|
+
end
|
119
|
+
end
|
114
120
|
end
|
115
121
|
end
|
116
122
|
end
|
@@ -13,7 +13,7 @@ module Enumpath
|
|
13
13
|
# @param operator (see Enumpath::Operator::Base.detect?)
|
14
14
|
# @return (see Enumpath::Operator::Base.detect?)
|
15
15
|
def detect?(operator)
|
16
|
-
|
16
|
+
operator == OPERATOR
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -28,17 +28,17 @@ module Enumpath
|
|
28
28
|
# enumerable member
|
29
29
|
# @yieldparam resolved_path [Array] resolved_path for the enumerable itself, or resolved_path plus the key for
|
30
30
|
# each direct enumerable member
|
31
|
-
def apply(remaining_path, enum, resolved_path
|
31
|
+
def apply(remaining_path, enum, resolved_path)
|
32
32
|
Enumpath.log('Applying remaining path recursively to enum') { { 'remaining path': remaining_path } }
|
33
33
|
yield(remaining_path, enum, resolved_path)
|
34
34
|
keys(enum).each do |key|
|
35
35
|
value = Enumpath::Resolver::Simple.resolve(key, enum)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
yield(['..'] + remaining_path, value, resolved_path + [key])
|
36
|
+
next unless recursable?(value)
|
37
|
+
|
38
|
+
Enumpath.log('Applying remaining path recursively to key') do
|
39
|
+
{ key: key, 'remaining path': ['..'] + remaining_path }
|
41
40
|
end
|
41
|
+
yield(['..'] + remaining_path, value, resolved_path + [key])
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
@@ -12,7 +12,7 @@ module Enumpath
|
|
12
12
|
# @param operator (see Enumpath::Operator::Base.detect?)
|
13
13
|
# @return (see Enumpath::Operator::Base.detect?)
|
14
14
|
def detect?(operator)
|
15
|
-
|
15
|
+
!(operator =~ OPERATOR_REGEX).nil?
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -25,7 +25,7 @@ module Enumpath
|
|
25
25
|
# @yieldparam remaining_path [Array] the included index plus remaining_path
|
26
26
|
# @yieldparam enum [Enumerable] enum
|
27
27
|
# @yieldparam resolved_path [Array] resolved_path
|
28
|
-
def apply(remaining_path, enum, resolved_path
|
28
|
+
def apply(remaining_path, enum, resolved_path)
|
29
29
|
_match, start, length, step = OPERATOR_REGEX.match(operator).to_a
|
30
30
|
max_length = enum.size
|
31
31
|
slices(start, length, step, max_length).each do |index|
|
@@ -5,7 +5,7 @@ module Enumpath
|
|
5
5
|
# Implements JSONPath subscript expressions operator syntax. See
|
6
6
|
# {file:README.md#label-Subscript+expressions+operator} for syntax and examples
|
7
7
|
class SubscriptExpression < Base
|
8
|
-
ARITHMETIC_OPERATOR_REGEX =
|
8
|
+
ARITHMETIC_OPERATOR_REGEX = %r{(\+|-|\*\*|\*|\/|%)}
|
9
9
|
OPERATOR_REGEX = /^\((.*)\)$/
|
10
10
|
|
11
11
|
class << self
|
@@ -14,7 +14,7 @@ module Enumpath
|
|
14
14
|
# @param operator (see Enumpath::Operator::Base.detect?)
|
15
15
|
# @return (see Enumpath::Operator::Base.detect?)
|
16
16
|
def detect?(operator)
|
17
|
-
|
17
|
+
!(operator =~ OPERATOR_REGEX).nil?
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -25,17 +25,15 @@ module Enumpath
|
|
25
25
|
# @yieldparam remaining_path [Array] remaining_path
|
26
26
|
# @yieldparam enum [Enumerable] the member of the enumerable at the value of the subscript expression
|
27
27
|
# @yieldparam resolved_path [Array] resolved_path plus the value of the subscript expression
|
28
|
-
def apply(remaining_path, enum, resolved_path
|
28
|
+
def apply(remaining_path, enum, resolved_path)
|
29
29
|
Enumpath.log('Applying subscript expression') { { expression: operator, to: enum } }
|
30
30
|
|
31
31
|
_match, unpacked_operator = OPERATOR_REGEX.match(operator).to_a
|
32
32
|
result = evaluate(unpacked_operator, enum)
|
33
33
|
|
34
34
|
value = Enumpath::Resolver::Simple.resolve(result, enum)
|
35
|
-
|
36
|
-
|
37
|
-
yield(remaining_path, value, resolved_path + [result.to_s])
|
38
|
-
end
|
35
|
+
Enumpath.log('Applying subscript') { { 'enum at subscript': value } } unless value.nil?
|
36
|
+
yield(remaining_path, value, resolved_path + [result.to_s]) unless value.nil?
|
39
37
|
end
|
40
38
|
|
41
39
|
private
|
@@ -47,6 +45,7 @@ module Enumpath
|
|
47
45
|
|
48
46
|
def resolve(property, enum)
|
49
47
|
return enum if property == '@'
|
48
|
+
|
50
49
|
value = Enumpath::Resolver::Simple.resolve(property.gsub(/^@\./, ''), enum)
|
51
50
|
value = Enumpath::Resolver::Property.resolve(property.gsub(/^@\./, ''), enum) if value.nil?
|
52
51
|
value
|
@@ -57,19 +56,22 @@ module Enumpath
|
|
57
56
|
Enumpath.log('Simple subscript') { { subscript: value } }
|
58
57
|
value
|
59
58
|
else
|
60
|
-
|
61
|
-
typecast_operand = variable_typecaster(operand)
|
62
|
-
result = value.public_send(operator.to_sym, typecast_operand)
|
63
|
-
Enumpath.log('Evaluated subscript') do
|
64
|
-
{ value: value, operator: operator, operand: typecast_operand, result: result }
|
65
|
-
end
|
66
|
-
result
|
59
|
+
evaluate_with_operator(operator, operand, value)
|
67
60
|
end
|
68
61
|
rescue NoMethodError
|
69
62
|
Enumpath.log('Subscript could not be evaluated') { { subscript: nil } }
|
70
63
|
nil
|
71
64
|
end
|
72
65
|
|
66
|
+
def evaluate_with_operator(operator, operand, value)
|
67
|
+
typecast_operand = variable_typecaster(operand)
|
68
|
+
result = value.public_send(operator.to_sym, typecast_operand)
|
69
|
+
Enumpath.log('Evaluated subscript') do
|
70
|
+
{ value: value, operator: operator, operand: typecast_operand, result: result }
|
71
|
+
end
|
72
|
+
result
|
73
|
+
end
|
74
|
+
|
73
75
|
def variable_typecaster(variable)
|
74
76
|
if variable =~ /\A('|").+\1\z/ || variable =~ /^:.+/
|
75
77
|
# It quacks like a string or symbol
|