enumpath 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|