shiny_json_logic 0.3.4 → 0.3.6
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/CHANGELOG.md +10 -0
- data/Gemfile.lock +1 -1
- data/README.md +2 -2
- data/lib/shiny_json_logic/comparisons/comparable.rb +15 -2
- data/lib/shiny_json_logic/engine.rb +12 -5
- data/lib/shiny_json_logic/numericals/min_max_collection.rb +21 -13
- data/lib/shiny_json_logic/operations/addition.rb +6 -2
- data/lib/shiny_json_logic/operations/all.rb +5 -3
- data/lib/shiny_json_logic/operations/and.rb +5 -2
- data/lib/shiny_json_logic/operations/base.rb +11 -4
- data/lib/shiny_json_logic/operations/coalesce.rb +5 -2
- data/lib/shiny_json_logic/operations/concatenation.rb +18 -5
- data/lib/shiny_json_logic/operations/division.rb +5 -2
- data/lib/shiny_json_logic/operations/exists.rb +8 -8
- data/lib/shiny_json_logic/operations/filter.rb +18 -5
- data/lib/shiny_json_logic/operations/inclusion.rb +16 -1
- data/lib/shiny_json_logic/operations/iterable/base.rb +30 -37
- data/lib/shiny_json_logic/operations/max.rb +1 -1
- data/lib/shiny_json_logic/operations/merge.rb +14 -3
- data/lib/shiny_json_logic/operations/min.rb +1 -1
- data/lib/shiny_json_logic/operations/missing.rb +34 -13
- data/lib/shiny_json_logic/operations/missing_some.rb +21 -5
- data/lib/shiny_json_logic/operations/modulo.rb +5 -2
- data/lib/shiny_json_logic/operations/none.rb +5 -3
- data/lib/shiny_json_logic/operations/or.rb +5 -2
- data/lib/shiny_json_logic/operations/preserve.rb +6 -4
- data/lib/shiny_json_logic/operations/product.rb +5 -2
- data/lib/shiny_json_logic/operations/reduce.rb +14 -19
- data/lib/shiny_json_logic/operations/some.rb +5 -1
- data/lib/shiny_json_logic/operations/subtraction.rb +5 -2
- data/lib/shiny_json_logic/operations/throw.rb +1 -1
- data/lib/shiny_json_logic/operations/try.rb +7 -3
- data/lib/shiny_json_logic/operations/val.rb +29 -8
- data/lib/shiny_json_logic/operations/var.rb +37 -11
- data/lib/shiny_json_logic/operator_solver.rb +44 -40
- data/lib/shiny_json_logic/scope_stack.rb +23 -53
- data/lib/shiny_json_logic/truthy.rb +10 -2
- data/lib/shiny_json_logic/utils/array.rb +1 -3
- data/lib/shiny_json_logic/version.rb +1 -1
- data/lib/shiny_json_logic.rb +1 -1
- metadata +1 -1
|
@@ -16,12 +16,15 @@ module ShinyJsonLogic
|
|
|
16
16
|
safe_arithmetic do
|
|
17
17
|
result = nil
|
|
18
18
|
count = 0
|
|
19
|
+
i = 0
|
|
20
|
+
n = operands.size
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
evaluated = evaluate(
|
|
22
|
+
while i < n
|
|
23
|
+
evaluated = evaluate(operands[i], scope_stack)
|
|
22
24
|
num = Numericals::Numerify.numerify(evaluated)
|
|
23
25
|
count += 1
|
|
24
26
|
result = result.nil? ? num : result.remainder(num)
|
|
27
|
+
i += 1
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
return handle_invalid_args if count < 2
|
|
@@ -8,10 +8,12 @@ module ShinyJsonLogic
|
|
|
8
8
|
class None < Iterable::Base
|
|
9
9
|
raise_on_dynamic_args!
|
|
10
10
|
|
|
11
|
-
def self.
|
|
12
|
-
|
|
11
|
+
def self.on_each(_item, filter, scope_stack)
|
|
12
|
+
throw(:early_return, false) if Truthy.call(Engine.call(filter, scope_stack))
|
|
13
|
+
end
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
def self.on_after(results, _scope_stack)
|
|
16
|
+
true
|
|
15
17
|
end
|
|
16
18
|
end
|
|
17
19
|
end
|
|
@@ -15,9 +15,12 @@ module ShinyJsonLogic
|
|
|
15
15
|
return false if rules.empty?
|
|
16
16
|
|
|
17
17
|
result = nil
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
i = 0
|
|
19
|
+
n = rules.size
|
|
20
|
+
while i < n
|
|
21
|
+
result = evaluate(rules[i], scope_stack)
|
|
20
22
|
return result if Truthy.call(result)
|
|
23
|
+
i += 1
|
|
21
24
|
end
|
|
22
25
|
result
|
|
23
26
|
end
|
|
@@ -9,11 +9,13 @@ module ShinyJsonLogic
|
|
|
9
9
|
def self.call(rules, scope_stack)
|
|
10
10
|
# Preserve doesn't create new scopes - evaluates each item directly
|
|
11
11
|
collection = Utils::Array.wrap(rules)
|
|
12
|
-
|
|
13
|
-
results =
|
|
14
|
-
|
|
12
|
+
n = collection.size
|
|
13
|
+
results = Array.new(n)
|
|
14
|
+
i = 0
|
|
15
|
+
while i < n
|
|
16
|
+
results[i] = Engine.call(collection[i], scope_stack)
|
|
17
|
+
i += 1
|
|
15
18
|
end
|
|
16
|
-
|
|
17
19
|
results.size == 1 ? results.first : results
|
|
18
20
|
end
|
|
19
21
|
end
|
|
@@ -16,14 +16,17 @@ module ShinyJsonLogic
|
|
|
16
16
|
safe_arithmetic do
|
|
17
17
|
result = nil
|
|
18
18
|
count = 0
|
|
19
|
+
i = 0
|
|
20
|
+
n = operands.size
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
evaluated = evaluate(
|
|
22
|
+
while i < n
|
|
23
|
+
evaluated = evaluate(operands[i], scope_stack)
|
|
22
24
|
num = Numericals::Numerify.numerify(evaluated)
|
|
23
25
|
num = 0 if num.nil?
|
|
24
26
|
return handle_nan if num.nil?
|
|
25
27
|
count += 1
|
|
26
28
|
result = result.nil? ? num.to_f : result * num.to_f
|
|
29
|
+
i += 1
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
return 1 if count == 0
|
|
@@ -12,31 +12,26 @@ module ShinyJsonLogic
|
|
|
12
12
|
|
|
13
13
|
def self.call(rules, scope_stack)
|
|
14
14
|
rules = resolve_rules(rules, scope_stack)
|
|
15
|
+
filter = setup_filter(rules)
|
|
16
|
+
collection = setup_collection(rules, scope_stack)
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
accumulator = Engine.call(rules[2], scope_stack)
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
reduce_scope = { "current" => nil, "accumulator" => nil }
|
|
21
|
+
scope_stack << reduce_scope
|
|
20
22
|
|
|
21
|
-
index_scope = { "index" => 0 }
|
|
22
|
-
reduce_scope = { "current" => nil, "accumulator" => nil }
|
|
23
|
-
|
|
24
|
-
collection.each_with_index do |item, index|
|
|
25
|
-
index_scope["index"] = index
|
|
26
|
-
reduce_scope["current"] = item
|
|
27
|
-
reduce_scope["accumulator"] = accumulator
|
|
28
|
-
scope_stack.push(index_scope, index: index)
|
|
29
|
-
scope_stack.push(reduce_scope, index: index)
|
|
30
23
|
begin
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
i = 0
|
|
25
|
+
n = collection.size
|
|
26
|
+
while i < n
|
|
27
|
+
reduce_scope["current"] = collection[i]
|
|
28
|
+
reduce_scope["accumulator"] = accumulator
|
|
29
|
+
accumulator = Engine.call(filter, scope_stack)
|
|
30
|
+
i += 1
|
|
31
|
+
end
|
|
32
|
+
ensure
|
|
36
33
|
scope_stack.pop
|
|
37
|
-
raise e
|
|
38
34
|
end
|
|
39
|
-
end
|
|
40
35
|
|
|
41
36
|
safe_arithmetic { accumulator }
|
|
42
37
|
end
|
|
@@ -8,8 +8,12 @@ module ShinyJsonLogic
|
|
|
8
8
|
class Some < Iterable::Base
|
|
9
9
|
raise_on_dynamic_args!
|
|
10
10
|
|
|
11
|
+
def self.on_each(_item, filter, scope_stack)
|
|
12
|
+
throw(:early_return, true) if Truthy.call(Engine.call(filter, scope_stack))
|
|
13
|
+
end
|
|
14
|
+
|
|
11
15
|
def self.on_after(results, _scope_stack)
|
|
12
|
-
|
|
16
|
+
false
|
|
13
17
|
end
|
|
14
18
|
end
|
|
15
19
|
end
|
|
@@ -16,13 +16,16 @@ module ShinyJsonLogic
|
|
|
16
16
|
safe_arithmetic do
|
|
17
17
|
result = nil
|
|
18
18
|
count = 0
|
|
19
|
+
i = 0
|
|
20
|
+
n = operands.size
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
evaluated = evaluate(
|
|
22
|
+
while i < n
|
|
23
|
+
evaluated = evaluate(operands[i], scope_stack)
|
|
22
24
|
num = Numericals::Numerify.numerify(evaluated)
|
|
23
25
|
num = 0 if num.nil?
|
|
24
26
|
count += 1
|
|
25
27
|
result = result.nil? ? num : result - num
|
|
28
|
+
i += 1
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
return handle_invalid_args if count == 0
|
|
@@ -16,7 +16,7 @@ module ShinyJsonLogic
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
extracted_type = error_type.is_a?(Hash) && error_type.key?("type") ? error_type["type"] : error_type
|
|
19
|
-
extracted_type = scope_stack.
|
|
19
|
+
extracted_type = scope_stack.last["type"] if extracted_type.nil?
|
|
20
20
|
|
|
21
21
|
raise Errors::Base.new(type: extracted_type)
|
|
22
22
|
end
|
|
@@ -6,12 +6,15 @@ module ShinyJsonLogic
|
|
|
6
6
|
def self.call(rules, scope_stack)
|
|
7
7
|
items = Utils::Array.wrap_nil(rules)
|
|
8
8
|
last_error = nil
|
|
9
|
+
i = 0
|
|
10
|
+
n = items.size
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
while i < n
|
|
13
|
+
item = items[i]
|
|
11
14
|
# If previous item was an error, switch context to error payload
|
|
12
15
|
if last_error
|
|
13
|
-
scope_stack
|
|
14
|
-
scope_stack
|
|
16
|
+
scope_stack << {} # intermediate level for [[1]] access
|
|
17
|
+
scope_stack << last_error.payload
|
|
15
18
|
end
|
|
16
19
|
|
|
17
20
|
begin
|
|
@@ -34,6 +37,7 @@ module ShinyJsonLogic
|
|
|
34
37
|
|
|
35
38
|
last_error = e
|
|
36
39
|
end
|
|
40
|
+
i += 1
|
|
37
41
|
end
|
|
38
42
|
|
|
39
43
|
# All items were errors, re-raise the last one
|
|
@@ -8,11 +8,21 @@ module ShinyJsonLogic
|
|
|
8
8
|
module Operations
|
|
9
9
|
class Val < Base
|
|
10
10
|
def self.execute(rules, scope_stack)
|
|
11
|
+
# Fast path: null or empty → return current scope
|
|
12
|
+
if rules.nil?
|
|
13
|
+
return Utils::DataHash.wrap(scope_stack.last)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Fast path: single string key (most common case)
|
|
17
|
+
if rules.is_a?(String)
|
|
18
|
+
return Utils::DataHash.wrap(Utils::HashFetch.fetch(scope_stack.last, rules))
|
|
19
|
+
end
|
|
20
|
+
|
|
11
21
|
raw_keys = Utils::Array.wrap_nil(rules)
|
|
12
22
|
|
|
13
|
-
# {"val": []}
|
|
23
|
+
# {"val": []} - return current scope
|
|
14
24
|
if raw_keys.empty? || raw_keys == [nil]
|
|
15
|
-
return Utils::DataHash.wrap(scope_stack.
|
|
25
|
+
return Utils::DataHash.wrap(scope_stack.last)
|
|
16
26
|
end
|
|
17
27
|
|
|
18
28
|
# Check if first element is an array (scope navigation syntax)
|
|
@@ -29,12 +39,18 @@ module ShinyJsonLogic
|
|
|
29
39
|
end
|
|
30
40
|
|
|
31
41
|
levels = level_indicator.abs
|
|
32
|
-
return Utils::DataHash.wrap(
|
|
42
|
+
return Utils::DataHash.wrap(ScopeStack.resolve(scope_stack, levels, evaluated_keys))
|
|
33
43
|
end
|
|
34
44
|
|
|
35
|
-
# Normal case: {"val":
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
# Normal case: {"val": ["key1", "key2"]}
|
|
46
|
+
keys_n = raw_keys.size
|
|
47
|
+
keys = Array.new(keys_n)
|
|
48
|
+
ki = 0
|
|
49
|
+
while ki < keys_n
|
|
50
|
+
keys[ki] = evaluate(raw_keys[ki], scope_stack)
|
|
51
|
+
ki += 1
|
|
52
|
+
end
|
|
53
|
+
current_data = scope_stack.last
|
|
38
54
|
Utils::DataHash.wrap(dig_value(current_data, keys))
|
|
39
55
|
end
|
|
40
56
|
|
|
@@ -42,10 +58,15 @@ module ShinyJsonLogic
|
|
|
42
58
|
return nil if data.nil?
|
|
43
59
|
return data if keys.empty?
|
|
44
60
|
|
|
45
|
-
|
|
61
|
+
obj = data
|
|
62
|
+
i = 0
|
|
63
|
+
n = keys.size
|
|
64
|
+
while i < n
|
|
46
65
|
return nil if obj.nil?
|
|
47
|
-
Utils::HashFetch.fetch(obj,
|
|
66
|
+
obj = Utils::HashFetch.fetch(obj, keys[i].to_s)
|
|
67
|
+
i += 1
|
|
48
68
|
end
|
|
69
|
+
obj
|
|
49
70
|
end
|
|
50
71
|
private_class_method :dig_value
|
|
51
72
|
end
|
|
@@ -9,35 +9,61 @@ module ShinyJsonLogic
|
|
|
9
9
|
module Operations
|
|
10
10
|
class Var < Base
|
|
11
11
|
def self.execute(rules, scope_stack)
|
|
12
|
+
# Fast path: simple string key, no default
|
|
13
|
+
if rules.is_a?(String)
|
|
14
|
+
current_data = scope_stack.last
|
|
15
|
+
if rules.empty?
|
|
16
|
+
return wrap(current_data)
|
|
17
|
+
end
|
|
18
|
+
return wrap(fetch_value(current_data, rules))
|
|
19
|
+
end
|
|
20
|
+
|
|
12
21
|
items = Utils::Array.wrap_nil(rules)
|
|
13
22
|
key = evaluate(items[0], scope_stack)
|
|
14
|
-
default = items
|
|
15
|
-
current_data = scope_stack.
|
|
23
|
+
default = items.length > 1 ? evaluate(items[1], scope_stack) : nil
|
|
24
|
+
current_data = scope_stack.last
|
|
16
25
|
|
|
17
26
|
if key.nil? || key == ""
|
|
18
|
-
return
|
|
27
|
+
return wrap(current_data)
|
|
19
28
|
end
|
|
20
29
|
|
|
21
30
|
result = fetch_value(current_data, key)
|
|
22
31
|
result = result.nil? ? default : result
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
default || scope_stack.
|
|
32
|
+
wrap(result)
|
|
33
|
+
rescue
|
|
34
|
+
default || scope_stack.last
|
|
26
35
|
end
|
|
27
36
|
|
|
37
|
+
# Only wrap Hash values — non-Hash values can't be confused with operators.
|
|
38
|
+
def self.wrap(value)
|
|
39
|
+
value.is_a?(::Hash) ? Utils::DataHash.wrap(value) : value
|
|
40
|
+
end
|
|
41
|
+
private_class_method :wrap
|
|
42
|
+
|
|
28
43
|
def self.fetch_value(obj, key)
|
|
29
44
|
return nil if obj.nil?
|
|
30
45
|
|
|
31
|
-
key_s = key.to_s
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
key_s = key.is_a?(String) ? key : key.to_s
|
|
47
|
+
|
|
48
|
+
# Fast path: no dot notation
|
|
49
|
+
dot_idx = key_s.index(".")
|
|
50
|
+
unless dot_idx
|
|
34
51
|
return Utils::HashFetch.fetch(obj, key_s)
|
|
35
52
|
end
|
|
36
53
|
|
|
37
|
-
|
|
54
|
+
# Dot notation: scan without split
|
|
55
|
+
current = obj
|
|
56
|
+
start = 0
|
|
57
|
+
len = key_s.length
|
|
58
|
+
while start < len
|
|
59
|
+
dot_idx = key_s.index(".", start)
|
|
60
|
+
segment = dot_idx ? key_s[start, dot_idx - start] : key_s[start, len - start]
|
|
38
61
|
return nil if current.nil?
|
|
39
|
-
Utils::HashFetch.fetch(current,
|
|
62
|
+
current = Utils::HashFetch.fetch(current, segment)
|
|
63
|
+
break unless dot_idx
|
|
64
|
+
start = dot_idx + 1
|
|
40
65
|
end
|
|
66
|
+
current
|
|
41
67
|
end
|
|
42
68
|
private_class_method :fetch_value
|
|
43
69
|
end
|
|
@@ -9,52 +9,56 @@ end
|
|
|
9
9
|
module ShinyJsonLogic
|
|
10
10
|
module OperatorSolver
|
|
11
11
|
SOLVERS = {
|
|
12
|
-
"var"
|
|
13
|
-
"missing"
|
|
12
|
+
"var" => Operations::Var,
|
|
13
|
+
"missing" => Operations::Missing,
|
|
14
14
|
"missing_some" => Operations::MissingSome,
|
|
15
|
-
"=="
|
|
16
|
-
"==="
|
|
17
|
-
"!="
|
|
18
|
-
"!=="
|
|
19
|
-
">"
|
|
20
|
-
">="
|
|
21
|
-
"<"
|
|
22
|
-
"<="
|
|
23
|
-
"!"
|
|
24
|
-
"or"
|
|
25
|
-
"and"
|
|
26
|
-
"in"
|
|
27
|
-
"cat"
|
|
28
|
-
"%"
|
|
29
|
-
"max"
|
|
30
|
-
"min"
|
|
31
|
-
"+"
|
|
32
|
-
"*"
|
|
33
|
-
"-"
|
|
34
|
-
"/"
|
|
35
|
-
"substr"
|
|
36
|
-
"merge"
|
|
37
|
-
"!!"
|
|
38
|
-
"val"
|
|
39
|
-
"??"
|
|
40
|
-
"exists"
|
|
41
|
-
"throw"
|
|
42
|
-
"try"
|
|
43
|
-
"if"
|
|
44
|
-
"?:"
|
|
45
|
-
"filter"
|
|
46
|
-
"map"
|
|
47
|
-
"reduce"
|
|
48
|
-
"all"
|
|
49
|
-
"none"
|
|
50
|
-
"some"
|
|
51
|
-
"preserve"
|
|
15
|
+
"==" => Operations::Equal,
|
|
16
|
+
"===" => Operations::StrictEqual,
|
|
17
|
+
"!=" => Operations::Different,
|
|
18
|
+
"!==" => Operations::StrictDifferent,
|
|
19
|
+
">" => Operations::Greater,
|
|
20
|
+
">=" => Operations::GreaterEqual,
|
|
21
|
+
"<" => Operations::Smaller,
|
|
22
|
+
"<=" => Operations::SmallerEqual,
|
|
23
|
+
"!" => Operations::Not,
|
|
24
|
+
"or" => Operations::Or,
|
|
25
|
+
"and" => Operations::And,
|
|
26
|
+
"in" => Operations::Inclusion,
|
|
27
|
+
"cat" => Operations::Concatenation,
|
|
28
|
+
"%" => Operations::Modulo,
|
|
29
|
+
"max" => Operations::Max,
|
|
30
|
+
"min" => Operations::Min,
|
|
31
|
+
"+" => Operations::Addition,
|
|
32
|
+
"*" => Operations::Product,
|
|
33
|
+
"-" => Operations::Subtraction,
|
|
34
|
+
"/" => Operations::Division,
|
|
35
|
+
"substr" => Operations::Substring,
|
|
36
|
+
"merge" => Operations::Merge,
|
|
37
|
+
"!!" => Operations::DoubleNot,
|
|
38
|
+
"val" => Operations::Val,
|
|
39
|
+
"??" => Operations::Coalesce,
|
|
40
|
+
"exists" => Operations::Exists,
|
|
41
|
+
"throw" => Operations::Throw,
|
|
42
|
+
"try" => Operations::Try,
|
|
43
|
+
"if" => Operations::If,
|
|
44
|
+
"?:" => Operations::If,
|
|
45
|
+
"filter" => Operations::Filter,
|
|
46
|
+
"map" => Operations::Map,
|
|
47
|
+
"reduce" => Operations::Reduce,
|
|
48
|
+
"all" => Operations::All,
|
|
49
|
+
"none" => Operations::None,
|
|
50
|
+
"some" => Operations::Some,
|
|
51
|
+
"preserve" => Operations::Preserve,
|
|
52
52
|
}.freeze
|
|
53
53
|
|
|
54
54
|
SOLVER_KEYS = Set.new(SOLVERS.keys).freeze
|
|
55
55
|
|
|
56
56
|
def self.operation?(value)
|
|
57
|
-
|
|
57
|
+
# Rules always have exactly 1 key — use each_key with early return
|
|
58
|
+
# instead of keys.any? which allocates an Array of keys first
|
|
59
|
+
value.each_key { |key| return SOLVER_KEYS.include?(key.to_s) }
|
|
60
|
+
false
|
|
58
61
|
end
|
|
59
62
|
end
|
|
60
63
|
end
|
|
64
|
+
|
|
@@ -3,73 +3,43 @@
|
|
|
3
3
|
require "shiny_json_logic/utils/hash_fetch"
|
|
4
4
|
|
|
5
5
|
module ShinyJsonLogic
|
|
6
|
-
#
|
|
6
|
+
# Helpers for navigating the scope stack in nested iterators.
|
|
7
7
|
#
|
|
8
|
-
# The scope stack
|
|
9
|
-
#
|
|
10
|
-
# - `resolve(n, *keys)` - go up n levels, then access keys via dig
|
|
11
|
-
# - `push(scope)` / `pop` - manage stack during iteration
|
|
8
|
+
# The scope stack is a plain Array passed as an argument — no object instantiation.
|
|
9
|
+
# Each entry is a scope (Hash or value). The last entry is the current scope.
|
|
12
10
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
11
|
+
# Entering/exiting a scope:
|
|
12
|
+
# scope_stack << item (push)
|
|
13
|
+
# scope_stack.pop (pop)
|
|
14
|
+
# scope_stack.last (current)
|
|
17
15
|
#
|
|
18
|
-
#
|
|
19
|
-
# so no indifferent access is needed here.
|
|
16
|
+
# Cross-level navigation (val + [[n]] syntax) uses ScopeStack.resolve.
|
|
20
17
|
#
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@data_stack = [root_data]
|
|
24
|
-
@index_stack = [0]
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Push a new scope onto the stack (when entering an iteration)
|
|
28
|
-
def push(data, index: 0)
|
|
29
|
-
@data_stack << data
|
|
30
|
-
@index_stack << index
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Pop the top scope (when exiting an iteration)
|
|
34
|
-
def pop
|
|
35
|
-
if @data_stack.size > 1
|
|
36
|
-
@data_stack.pop
|
|
37
|
-
@index_stack.pop
|
|
38
|
-
end
|
|
39
|
-
end
|
|
18
|
+
module ScopeStack
|
|
19
|
+
module_function
|
|
40
20
|
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# Resolve a value by going up n levels and then accessing keys
|
|
47
|
-
#
|
|
48
|
-
# @param levels [Integer] number of levels to go up (0 = current, 1 = parent, etc.)
|
|
49
|
-
# @param keys [Array] keys to dig into after reaching the target scope
|
|
50
|
-
# @return [Object] the resolved value
|
|
51
|
-
def resolve(levels, *keys)
|
|
52
|
-
target_index = @data_stack.size - 1 - levels
|
|
21
|
+
# Resolve a value by going up n levels and then accessing keys.
|
|
22
|
+
# Used by val.rb for {"val": [[n], "key"]} syntax.
|
|
23
|
+
def resolve(stack, levels, keys)
|
|
24
|
+
target_index = stack.size - 1 - levels
|
|
53
25
|
return nil if target_index < 0
|
|
54
26
|
|
|
55
|
-
data =
|
|
56
|
-
|
|
57
|
-
if keys.empty?
|
|
58
|
-
data
|
|
59
|
-
else
|
|
60
|
-
dig_value(data, keys)
|
|
61
|
-
end
|
|
27
|
+
data = stack[target_index]
|
|
28
|
+
keys.empty? ? data : dig_value(data, keys)
|
|
62
29
|
end
|
|
63
30
|
|
|
64
|
-
private
|
|
65
|
-
|
|
66
31
|
def dig_value(data, keys)
|
|
67
32
|
return nil if data.nil?
|
|
68
33
|
|
|
69
|
-
|
|
34
|
+
obj = data
|
|
35
|
+
i = 0
|
|
36
|
+
n = keys.size
|
|
37
|
+
while i < n
|
|
70
38
|
return nil if obj.nil?
|
|
71
|
-
Utils::HashFetch.fetch(obj,
|
|
39
|
+
obj = Utils::HashFetch.fetch(obj, keys[i].to_s)
|
|
40
|
+
i += 1
|
|
72
41
|
end
|
|
42
|
+
obj
|
|
73
43
|
end
|
|
74
44
|
end
|
|
75
45
|
end
|
|
@@ -9,9 +9,17 @@ module ShinyJsonLogic
|
|
|
9
9
|
when true, false then subject
|
|
10
10
|
when Numeric then !subject.zero?
|
|
11
11
|
when String, Hash then !subject.empty?
|
|
12
|
-
when Array then subject.any?
|
|
13
12
|
when NilClass then false
|
|
14
|
-
|
|
13
|
+
when Array
|
|
14
|
+
i = 0
|
|
15
|
+
n = subject.size
|
|
16
|
+
while i < n
|
|
17
|
+
return true if subject[i]
|
|
18
|
+
i += 1
|
|
19
|
+
end
|
|
20
|
+
false
|
|
21
|
+
else
|
|
22
|
+
true
|
|
15
23
|
end
|
|
16
24
|
end
|
|
17
25
|
end
|
|
@@ -8,16 +8,14 @@ module ShinyJsonLogic
|
|
|
8
8
|
def wrap(object)
|
|
9
9
|
return [] if object.nil?
|
|
10
10
|
return object if object.is_a?(::Array)
|
|
11
|
-
return object.to_ary || [object] if object.respond_to?(:to_ary)
|
|
12
11
|
|
|
13
12
|
[object]
|
|
14
13
|
end
|
|
15
14
|
|
|
16
15
|
def wrap_nil(object)
|
|
17
|
-
return [nil] if object.nil?
|
|
18
16
|
return object if object.is_a?(::Array)
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
[object]
|
|
21
19
|
end
|
|
22
20
|
end
|
|
23
21
|
end
|
data/lib/shiny_json_logic.rb
CHANGED