enumpath 0.1.1 → 0.1.2
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 +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
|