enumpath 0.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/.gitignore +15 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/.yardopts +1 -0
- data/CONTRIBUTING.md +91 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +56 -0
- data/LICENSE +201 -0
- data/README.md +492 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/enumpath.gemspec +32 -0
- data/lib/enumpath.rb +62 -0
- data/lib/enumpath/logger.rb +76 -0
- data/lib/enumpath/operator.rb +53 -0
- data/lib/enumpath/operator/base.rb +84 -0
- data/lib/enumpath/operator/child.rb +34 -0
- data/lib/enumpath/operator/filter_expression.rb +116 -0
- data/lib/enumpath/operator/recursive_descent.rb +52 -0
- data/lib/enumpath/operator/slice.rb +61 -0
- data/lib/enumpath/operator/subscript_expression.rb +84 -0
- data/lib/enumpath/operator/union.rb +42 -0
- data/lib/enumpath/operator/wildcard.rb +33 -0
- data/lib/enumpath/path.rb +73 -0
- data/lib/enumpath/path/normalized_path.rb +50 -0
- data/lib/enumpath/resolver/property.rb +23 -0
- data/lib/enumpath/resolver/simple.rb +33 -0
- data/lib/enumpath/results.rb +57 -0
- data/lib/enumpath/version.rb +5 -0
- metadata +185 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Enumpath
|
4
|
+
module Operator
|
5
|
+
# Implements JSONPath recursive descent operator syntax. See {file:README.md#label-Recursive+descent+operator} for
|
6
|
+
# syntax and examples
|
7
|
+
class RecursiveDescent < Base
|
8
|
+
OPERATOR = '..'
|
9
|
+
|
10
|
+
class << self
|
11
|
+
# Simple test of whether the operator matches the {Enumpath::Operator::RecursiveDescent::OPERATOR} constant
|
12
|
+
#
|
13
|
+
# @param operator (see Enumpath::Operator::Base.detect?)
|
14
|
+
# @return (see Enumpath::Operator::Base.detect?)
|
15
|
+
def detect?(operator)
|
16
|
+
!!(operator == OPERATOR)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Yields to the block once for the enumerable itself, and once for every direct member of the enumerable that is
|
21
|
+
# also an enumerable
|
22
|
+
#
|
23
|
+
# @param (see Enumpath::Operator::Base#apply)
|
24
|
+
# @yield (see Enumpath::Operator::Base#apply)
|
25
|
+
# @yieldparam remaining_path [Array] remaining_path for the enumerable itself, or the recursive descent
|
26
|
+
# operator plus remaining_path for each direct enumerable member
|
27
|
+
# @yieldparam enum [Enumerable] enum for the enumerable itself, or the direct enumerable member for each direct
|
28
|
+
# enumerable member
|
29
|
+
# @yieldparam resolved_path [Array] resolved_path for the enumerable itself, or resolved_path plus the key for
|
30
|
+
# each direct enumerable member
|
31
|
+
def apply(remaining_path, enum, resolved_path, &block)
|
32
|
+
Enumpath.log('Applying remaining path recursively to enum') { { 'remaining path': remaining_path } }
|
33
|
+
yield(remaining_path, enum, resolved_path)
|
34
|
+
keys(enum).each do |key|
|
35
|
+
value = Enumpath::Resolver::Simple.resolve(key, enum)
|
36
|
+
if recursable?(value)
|
37
|
+
Enumpath.log('Applying remaining path recursively to key') do
|
38
|
+
{ key: key, 'remaining path': ['..'] + remaining_path }
|
39
|
+
end
|
40
|
+
yield(['..'] + remaining_path, value, resolved_path + [key])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def recursable?(value)
|
48
|
+
value.is_a?(Enumerable)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Enumpath
|
4
|
+
module Operator
|
5
|
+
# Implements JSONPath array slice operator syntax. See {file:README.md#label-Slice+operator} for syntax and examples
|
6
|
+
class Slice < Base
|
7
|
+
OPERATOR_REGEX = /^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Whether the operator matches {Enumpath::Operator::Slice::OPERATOR_REGEX}
|
11
|
+
#
|
12
|
+
# @param operator (see Enumpath::Operator::Base.detect?)
|
13
|
+
# @return (see Enumpath::Operator::Base.detect?)
|
14
|
+
def detect?(operator)
|
15
|
+
!!(operator =~ OPERATOR_REGEX)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Yields to the block once for each member of the local enumerable whose index is included by position between
|
20
|
+
# _start_ and up to (but not including) _end_. If _step_ is included then only every _step_ member is included,
|
21
|
+
# starting with the first.
|
22
|
+
#
|
23
|
+
# @param (see Enumpath::Operator::Base#apply)
|
24
|
+
# @yield (see Enumpath::Operator::Base#apply)
|
25
|
+
# @yieldparam remaining_path [Array] the included index plus remaining_path
|
26
|
+
# @yieldparam enum [Enumerable] enum
|
27
|
+
# @yieldparam resolved_path [Array] resolved_path
|
28
|
+
def apply(remaining_path, enum, resolved_path, &block)
|
29
|
+
_match, start, length, step = OPERATOR_REGEX.match(operator).to_a
|
30
|
+
max_length = enum.size
|
31
|
+
slices(start, length, step, max_length).each do |index|
|
32
|
+
Enumpath.log('Applying slice') { { slice: index } }
|
33
|
+
yield([index.to_s] + remaining_path, enum, resolved_path)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def slices(start, length, step, max_length)
|
40
|
+
start = slice_start(start, max_length)
|
41
|
+
length = slice_length(length, max_length)
|
42
|
+
step = slice_step(step)
|
43
|
+
(start...length).step(step)
|
44
|
+
end
|
45
|
+
|
46
|
+
def slice_start(start, max_length)
|
47
|
+
start = start.empty? ? 0 : start.to_i
|
48
|
+
start.negative? ? [0, start + max_length].max : [max_length, start].min
|
49
|
+
end
|
50
|
+
|
51
|
+
def slice_length(length, max_length)
|
52
|
+
length = length.empty? ? max_length : length.to_i
|
53
|
+
length.negative? ? [0, length + max_length].max : [max_length, length].min
|
54
|
+
end
|
55
|
+
|
56
|
+
def slice_step(step)
|
57
|
+
step.empty? ? 1 : step.to_i
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Enumpath
|
4
|
+
module Operator
|
5
|
+
# Implements JSONPath subscript expressions operator syntax. See
|
6
|
+
# {file:README.md#label-Subscript+expressions+operator} for syntax and examples
|
7
|
+
class SubscriptExpression < Base
|
8
|
+
ARITHMETIC_OPERATOR_REGEX = /(\+|-|\*\*|\*|\/|%)/
|
9
|
+
OPERATOR_REGEX = /^\((.*)\)$/
|
10
|
+
|
11
|
+
class << self
|
12
|
+
# Whether the operator matches {Enumpath::Operator::SubscriptExpression::OPERATOR_REGEX}
|
13
|
+
#
|
14
|
+
# @param operator (see Enumpath::Operator::Base.detect?)
|
15
|
+
# @return (see Enumpath::Operator::Base.detect?)
|
16
|
+
def detect?(operator)
|
17
|
+
!!(operator =~ OPERATOR_REGEX)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Yields to the block once if the subscript expression evaluates to a member of the enumerable
|
22
|
+
#
|
23
|
+
# @param (see Enumpath::Operator::Base#apply)
|
24
|
+
# @yield (see Enumpath::Operator::Base#apply)
|
25
|
+
# @yieldparam remaining_path [Array] remaining_path
|
26
|
+
# @yieldparam enum [Enumerable] the member of the enumerable at the value of the subscript expression
|
27
|
+
# @yieldparam resolved_path [Array] resolved_path plus the value of the subscript expression
|
28
|
+
def apply(remaining_path, enum, resolved_path, &block)
|
29
|
+
Enumpath.log('Applying subscript expression') { { expression: operator, to: enum } }
|
30
|
+
|
31
|
+
_match, unpacked_operator = OPERATOR_REGEX.match(operator).to_a
|
32
|
+
result = evaluate(unpacked_operator, enum)
|
33
|
+
|
34
|
+
value = Enumpath::Resolver::Simple.resolve(result, enum)
|
35
|
+
if !value.nil?
|
36
|
+
Enumpath.log('Applying subscript') { { 'enum at subscript': value } }
|
37
|
+
yield(remaining_path, value, resolved_path + [result.to_s])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def evaluate(unpacked_operator, enum)
|
44
|
+
property, operator, operand = unpacked_operator.split(ARITHMETIC_OPERATOR_REGEX).map(&:strip)
|
45
|
+
test(operator, operand, resolve(property, enum))
|
46
|
+
end
|
47
|
+
|
48
|
+
def resolve(property, enum)
|
49
|
+
return enum if property == '@'
|
50
|
+
value = Enumpath::Resolver::Simple.resolve(property.gsub(/^@\./, ''), enum)
|
51
|
+
value = Enumpath::Resolver::Property.resolve(property.gsub(/^@\./, ''), enum) if value.nil?
|
52
|
+
value
|
53
|
+
end
|
54
|
+
|
55
|
+
def test(operator, operand, value)
|
56
|
+
if operator.nil? || operand.nil?
|
57
|
+
Enumpath.log('Simple subscript') { { subscript: value } }
|
58
|
+
value
|
59
|
+
else
|
60
|
+
# Evaluate expression using operator
|
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
|
67
|
+
end
|
68
|
+
rescue NoMethodError
|
69
|
+
Enumpath.log('Subscript could not be evaluated') { { subscript: nil } }
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def variable_typecaster(variable)
|
74
|
+
if variable =~ /\A('|").+\1\z/ || variable =~ /^:.+/
|
75
|
+
# It quacks like a string or symbol
|
76
|
+
variable.gsub(/\A(:|('|"))|('|")\z/, '')
|
77
|
+
else
|
78
|
+
# Otherwise treat it as a number. Note that we only care about whole numbers in this case
|
79
|
+
variable.to_i
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TODO: Investigate supporting anchored paths (`$.foo.bar...`, `@.foo.bar...`)
|
4
|
+
|
5
|
+
module Enumpath
|
6
|
+
module Operator
|
7
|
+
# Implements JSONPath union operator syntax. See {file:README.md#label-Union+operator} for syntax and examples
|
8
|
+
class Union < Base
|
9
|
+
OPERATOR = /,/
|
10
|
+
CONSTRAINTS = /^,|,$/
|
11
|
+
SPLIT_REGEX = /,/
|
12
|
+
|
13
|
+
# The operator consists of
|
14
|
+
# a set of names or indices. Any member of the local enumerable that can
|
15
|
+
# be found at any of the keys or indices is yielded to the block.
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# Whether the operator matches {Enumpath::Operator::Union::OPERATOR} and does not match
|
19
|
+
# {Enumpath::Operator::Union::CONSTRAINTS}
|
20
|
+
#
|
21
|
+
# @param operator (see Enumpath::Operator::Base.detect?)
|
22
|
+
# @return (see Enumpath::Operator::Base.detect?)
|
23
|
+
def detect?(operator)
|
24
|
+
operator.scan(',').any? && operator.scan(CONSTRAINTS).none?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Yields to the block once for every union member
|
29
|
+
#
|
30
|
+
# @param (see Enumpath::Operator::Base#apply)
|
31
|
+
# @yield (see Enumpath::Operator::Base#apply)
|
32
|
+
# @yieldparam remaining_path [Array] the union member plus remaining_path
|
33
|
+
# @yieldparam enum [Enumerable] enum
|
34
|
+
# @yieldparam resolved_path [Array] resolved_path
|
35
|
+
def apply(remaining_path, enum, resolved_path, &block)
|
36
|
+
parts = operator.split(SPLIT_REGEX).map { |part| part.strip.gsub(/^['"]|['"]$/, '') }
|
37
|
+
Enumpath.log('Applying union parts') { { parts: parts } }
|
38
|
+
parts.each { |part| yield([part] + remaining_path, enum, resolved_path) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Enumpath
|
4
|
+
module Operator
|
5
|
+
# Implements JSONPath wildcard operator syntax. See {file:README.md#label-Wildcard+operator} for syntax and examples
|
6
|
+
class Wildcard < Base
|
7
|
+
OPERATOR = '*'
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Simple test of whether the operator matches the {Enumpath::Operator::Wildcard::OPERATOR} constant
|
11
|
+
#
|
12
|
+
# @param operator (see Enumpath::Operator::Base.detect?)
|
13
|
+
# @return (see Enumpath::Operator::Base.detect?)
|
14
|
+
def detect?(operator)
|
15
|
+
operator == OPERATOR
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Yields to the block once for every direct member of the enumerable
|
20
|
+
#
|
21
|
+
# @param (see Enumpath::Operator::Base#apply)
|
22
|
+
# @yield (see Enumpath::Operator::Base#apply)
|
23
|
+
# @yieldparam remaining_path [Array] the key of the given member plus remaining_path
|
24
|
+
# @yieldparam enum [Enumerable] enum
|
25
|
+
# @yieldparam resolved_path [Array] resolved_path
|
26
|
+
def apply(remaining_path, enum, resolved_path, &block)
|
27
|
+
keys = keys(enum)
|
28
|
+
Enumpath.log('Applying wildcard to keys') { { keys: keys } }
|
29
|
+
keys.each { |key| yield([key.to_s] + remaining_path, enum, resolved_path) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'enumpath/path/normalized_path'
|
4
|
+
|
5
|
+
module Enumpath
|
6
|
+
# A mechanism for applying path expressions to enumerables and tracking results
|
7
|
+
class Path
|
8
|
+
# @return [Enumpath::Path::NormalizedPath] the normalized path
|
9
|
+
attr_reader :path
|
10
|
+
|
11
|
+
# @return [Enumpath::Results] the current results array
|
12
|
+
attr_reader :results
|
13
|
+
|
14
|
+
# @param path [String, Array<String>] the path expression to apply to the enumerable
|
15
|
+
# @param result_type (see Enumpath::Results#initialize)
|
16
|
+
def initialize(path, result_type: nil)
|
17
|
+
@path = path
|
18
|
+
normalize!
|
19
|
+
@results = Enumpath::Results.new(result_type: result_type)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Apply the path expression against an enumerable
|
23
|
+
#
|
24
|
+
# @note Calling this method resets the previous results array
|
25
|
+
#
|
26
|
+
# @param enum [Enumerable] the enumerable to apply the path to
|
27
|
+
# @return [Enumpath::Results] an array of resolved values or paths
|
28
|
+
def apply(enum)
|
29
|
+
results.clear
|
30
|
+
trace(@path.dup, enum)
|
31
|
+
results
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Applies the next normalized path segment to enumerable and keeps track of resolved path segments. This method
|
37
|
+
# recursively yields to itself via each Operator subclass's {#apply} method. If there are no remaining path
|
38
|
+
# segments then it stores a new result in the results array effectively ending processing on that branch of the
|
39
|
+
# original enumerator.
|
40
|
+
#
|
41
|
+
# @param path_segments [Array] an array containing the normalized path segments to be resolved
|
42
|
+
# @param enum [Enumerable] the object to apply the next normalized path segment to
|
43
|
+
# @param resolved_path [Array] an array containing the static path segments that have been resolved so far
|
44
|
+
# @param nesting_level [Integer] used to set the indentation level for {Enumpath::Logger}
|
45
|
+
# @return [void]
|
46
|
+
def trace(path_segments, enum, resolved_path = [], nesting_level = 0)
|
47
|
+
Enumpath.logger.level = nesting_level
|
48
|
+
if path_segments.any?
|
49
|
+
Enumpath.log("Applying") { { operator: path_segments, to: enum } }
|
50
|
+
segment = path_segments.first
|
51
|
+
remaining_path = path_segments[1..-1]
|
52
|
+
operator = Enumpath::Operator.detect(segment, enum)
|
53
|
+
operator&.apply(remaining_path, enum, resolved_path) do |s, e, c|
|
54
|
+
trace(s, e, c, nesting_level + 1)
|
55
|
+
end
|
56
|
+
else
|
57
|
+
Enumpath.log('Storing') { { resolved_path: resolved_path, enum: enum } }
|
58
|
+
results.store(resolved_path, enum)
|
59
|
+
end
|
60
|
+
Enumpath.logger.level = nesting_level
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def cache
|
66
|
+
@cache ||= Enumpath.path_cache
|
67
|
+
end
|
68
|
+
|
69
|
+
def normalize!
|
70
|
+
@path = cache.get_or_set(@path) { Enumpath::Path::NormalizedPath.new(@path) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Enumpath
|
4
|
+
class Path
|
5
|
+
# A utility for automatically normalizing string path expressions
|
6
|
+
class NormalizedPath < Array
|
7
|
+
FILTER_EXPRESSION_REGEX = /[\['](\??\(.*?\))[\]']/
|
8
|
+
INDEX_NOTATION_REGEX = /#([0-9]+)/
|
9
|
+
|
10
|
+
# @param path (see Enumpath::Path#initialize)
|
11
|
+
def initialize(path)
|
12
|
+
super(normalize(path))
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def normalize(path)
|
18
|
+
return path if path.is_a?(Array)
|
19
|
+
normalized_path = path.dup
|
20
|
+
normalized_path, filter_expressions = remove_filter_expressions(normalized_path)
|
21
|
+
normalized_path.gsub!(/'?\.'?|\['?/, ';') # Replace "'?.'?" or "['?" with ";"
|
22
|
+
normalized_path.gsub!(/;;;|;;/, ';..;') # Replace ";;;" or ";;" with ";..;"
|
23
|
+
normalized_path.gsub!(/;\z|'?\]|'\z/, '') # Replace ";$" or "'?]" or "'$" with ""
|
24
|
+
normalized_path = restore_filter_expressions(normalized_path, filter_expressions)
|
25
|
+
normalized_path.gsub!(/\A\$(;|\z)/, '') # Remove root operator
|
26
|
+
normalized_path = normalized_path.split(';') # Split into segment parts
|
27
|
+
normalized_path.reject! do |segment| # Get rid of any blank segments
|
28
|
+
segment.nil? || segment.size == 0
|
29
|
+
end
|
30
|
+
Enumpath.log('Path normalized') { { original: path, normalized: normalized_path } }
|
31
|
+
normalized_path
|
32
|
+
end
|
33
|
+
|
34
|
+
# Move filter expressions (`[?(expr)]`) to the temporary array and replace with an index notation
|
35
|
+
def remove_filter_expressions(path)
|
36
|
+
filter_expressions = []
|
37
|
+
stripped_path = path.gsub(FILTER_EXPRESSION_REGEX) do
|
38
|
+
filter_expressions << $1
|
39
|
+
"[##{filter_expressions.size - 1}]"
|
40
|
+
end
|
41
|
+
[stripped_path, filter_expressions]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Replace index notations with their corresponding filter expressions (`?(expr)`) from the temporary array
|
45
|
+
def restore_filter_expressions(path_expression, filter_expressions)
|
46
|
+
path_expression.gsub(INDEX_NOTATION_REGEX) { filter_expressions[$1.to_i] }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Enumpath
|
4
|
+
module Resolver
|
5
|
+
# A utility for resolving a string as a property of an object
|
6
|
+
class Property
|
7
|
+
class << self
|
8
|
+
# Attempts to resolve a string as a property of an object. In this context a property is a public method that
|
9
|
+
# expects no arguments.
|
10
|
+
#
|
11
|
+
# @param property [String] the name of the property to attempt to resolve
|
12
|
+
# @param object [Object] the object to resolve the property against
|
13
|
+
# @return the resolved property value, or nil if it could not be resolved
|
14
|
+
def resolve(property, object)
|
15
|
+
# TODO: return if Enumpath.disable_property_resolver
|
16
|
+
object.public_send(property.to_s.to_sym)
|
17
|
+
rescue ArgumentError, NoMethodError
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Enumpath
|
4
|
+
module Resolver
|
5
|
+
# A utility for resolving a string as an index, key, or member of an enumerable
|
6
|
+
class Simple
|
7
|
+
NUMERIC_INDEX_TEST = /\A\-?(?:0|[1-9][0-9]*)\z/
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Attempts to resolve a string as an index, key, or member of an enumerable
|
11
|
+
#
|
12
|
+
# @param variable [String] the value to attempt to resolve
|
13
|
+
# @param enum [Enumerable] the enumerable to resolve the value against
|
14
|
+
# @return the resolved value, or nil if it could not be resolved
|
15
|
+
def resolve(variable, enum)
|
16
|
+
variable = variable.to_s
|
17
|
+
value = rescued_dig(enum, variable.to_i) if variable =~ NUMERIC_INDEX_TEST
|
18
|
+
value = rescued_dig(enum, variable) if value.nil?
|
19
|
+
value = rescued_dig(enum, variable.to_sym) if value.nil?
|
20
|
+
value
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def rescued_dig(enum, typecast_variable)
|
26
|
+
enum.dig(typecast_variable)
|
27
|
+
rescue NoMethodError, TypeError
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|