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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cb0d5385d70e0de02275f1a5a4ad0f8d6014c9f8030df64882cb7da1ff44942d
|
|
4
|
+
data.tar.gz: a608c50e2ef5acb46a348674df1ffeff928274f142f5d71ffbaf1101787b4149
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eef55803823c85357152265d6b9050394151111b782c233c264857837a35e822149c4f93c792fd1f79b9917c1c6956c7237949dd11f5ba057908b9b0a31372ae
|
|
7
|
+
data.tar.gz: 2dc4f7ee486216838700097498ff5b545307780ba274fd869a4c40accd46799f1111dcdeb12b55518926de4bce1339c57bdf5d42ba581fc3e17c452aa5e42aa1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
|
+
## [0.3.6] - 2026-03-06
|
|
5
|
+
### Changed
|
|
6
|
+
- Optimizes instantiation of scope stack for improved performance.
|
|
7
|
+
- Refactors min/max operations for improved performance.
|
|
8
|
+
|
|
9
|
+
## [0.3.5] - 2026-03-06
|
|
10
|
+
### Changed
|
|
11
|
+
- Reduces object allocations further in hot paths (iterator index tracking, early-exit via throw/catch, inline nil-wrapping, scope push simplification) for an additional ~12–18% throughput improvement over 0.3.4.
|
|
12
|
+
- Symbol values are now coerced to String before comparisons (`==`, `===`, `!=`, `!==`, `<`, `>`, `<=`, `>=`, `in`), so Ruby Symbol data round-trips cleanly through JSONLogic rules.
|
|
13
|
+
|
|
4
14
|
## [0.3.4] - 2026-03-06
|
|
5
15
|
### Changed
|
|
6
16
|
- Reduces object allocations in hot paths for improved performance.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
[](https://badge.fury.io/rb/shiny_json_logic)
|
|
5
|
-

|
|
6
6
|
|
|
7
7
|
> **A boring, correct and production-ready JSONLogic implementation for Ruby. ✨**
|
|
8
8
|
|
|
@@ -15,7 +15,7 @@ This gem focuses on predictable behavior, strict spec alignment, high compatibil
|
|
|
15
15
|
## Why ShinyJsonLogic?
|
|
16
16
|
|
|
17
17
|
- 🧩 **Zero runtime dependencies** (stdlib-only). Just plug & play!
|
|
18
|
-
- 🕰️ **Ruby 2.
|
|
18
|
+
- 🕰️ **Ruby 2.4+ compatible**, one of the lowest minimum versions supported in the Ruby ecosystem.
|
|
19
19
|
- 🔧 **Actively maintained** and continuously improved.
|
|
20
20
|
- 📊 **Highest JSONLogic compatibility in the Ruby ecosystem**, as measured against the official test suites.
|
|
21
21
|
|
|
@@ -10,7 +10,8 @@ module ShinyJsonLogic
|
|
|
10
10
|
module_function
|
|
11
11
|
|
|
12
12
|
def compare(a, b)
|
|
13
|
-
|
|
13
|
+
a = a.to_s if a.is_a?(Symbol)
|
|
14
|
+
b = b.to_s if b.is_a?(Symbol)
|
|
14
15
|
|
|
15
16
|
if a.is_a?(String) && b.is_a?(String)
|
|
16
17
|
return a <=> b
|
|
@@ -28,15 +29,24 @@ module ShinyJsonLogic
|
|
|
28
29
|
return 0.0 if value == false
|
|
29
30
|
return 1.0 if value == true
|
|
30
31
|
return 0.0 if value.nil?
|
|
31
|
-
|
|
32
|
+
|
|
33
|
+
value = value.to_s if value.is_a?(Symbol)
|
|
34
|
+
return value.to_f if Numericals::Numerify.numeric_string?(value)
|
|
35
|
+
|
|
32
36
|
nil
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
# Normalize numeric types for strict equality comparisons (=== semantics).
|
|
40
|
+
# Also coerces Symbol → String so :foo === "foo" holds.
|
|
36
41
|
def cast(value)
|
|
42
|
+
value = value.to_s if value.is_a?(Symbol)
|
|
37
43
|
value.is_a?(Numeric) ? value.to_f : value
|
|
38
44
|
end
|
|
39
45
|
|
|
46
|
+
def comparable_type?(value)
|
|
47
|
+
value.is_a?(Numeric) || value.is_a?(String) || value.is_a?(Symbol) || value == true || value == false || value.nil?
|
|
48
|
+
end
|
|
49
|
+
|
|
40
50
|
# Shared loop for all chain-comparison operators.
|
|
41
51
|
# Yields the compare result for each consecutive pair; block returns true to continue, false to short-circuit.
|
|
42
52
|
# Returns true if all pairs pass, false otherwise. Raises on :nan or invalid args.
|
|
@@ -46,9 +56,12 @@ module ShinyJsonLogic
|
|
|
46
56
|
raise Errors::InvalidArguments if n < 2
|
|
47
57
|
|
|
48
58
|
prev = Engine.call(operands[0], scope_stack)
|
|
59
|
+
raise Errors::NotANumber unless comparable_type?(prev)
|
|
60
|
+
|
|
49
61
|
i = 1
|
|
50
62
|
while i < n
|
|
51
63
|
curr = Engine.call(operands[i], scope_stack)
|
|
64
|
+
raise Errors::NotANumber unless comparable_type?(curr)
|
|
52
65
|
result = compare(prev, curr)
|
|
53
66
|
raise Errors::NotANumber if result == :nan
|
|
54
67
|
return false unless yield(result)
|
|
@@ -8,10 +8,9 @@ module ShinyJsonLogic
|
|
|
8
8
|
OPERATIONS = OperatorSolver::SOLVERS
|
|
9
9
|
|
|
10
10
|
def self.call(rule, scope_stack)
|
|
11
|
-
if rule.is_a?(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return rule if rule.empty?
|
|
11
|
+
if rule.is_a?(Hash)
|
|
12
|
+
# DataHash marks already-resolved user data — return as-is without dispatch
|
|
13
|
+
return rule if rule.is_a?(Utils::DataHash) || rule.empty?
|
|
15
14
|
|
|
16
15
|
raise Errors::UnknownOperator if rule.size > 1
|
|
17
16
|
|
|
@@ -24,7 +23,15 @@ module ShinyJsonLogic
|
|
|
24
23
|
|
|
25
24
|
op.call(args, scope_stack)
|
|
26
25
|
elsif rule.is_a?(Array)
|
|
27
|
-
|
|
26
|
+
# Use while loop instead of map — avoids Enumerator overhead on Ruby 3.4 no-YJIT
|
|
27
|
+
n = rule.size
|
|
28
|
+
result = Array.new(n)
|
|
29
|
+
i = 0
|
|
30
|
+
while i < n
|
|
31
|
+
result[i] = call(rule[i], scope_stack)
|
|
32
|
+
i += 1
|
|
33
|
+
end
|
|
34
|
+
result
|
|
28
35
|
else
|
|
29
36
|
rule
|
|
30
37
|
end
|
|
@@ -7,21 +7,29 @@ module ShinyJsonLogic
|
|
|
7
7
|
module MinMaxCollection
|
|
8
8
|
module_function
|
|
9
9
|
|
|
10
|
-
def
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def collect_values(rules, scope_stack)
|
|
18
|
-
if Operations::Base.op?(rules)
|
|
19
|
-
evaluated = Engine.call(rules, scope_stack)
|
|
20
|
-
return Utils::Array.wrap_nil(evaluated)
|
|
10
|
+
def resolve(rules, scope_stack, op)
|
|
11
|
+
if rules.is_a?(Hash) && !rules.empty? && Engine::OPERATIONS.key?(rules.first[0].to_s)
|
|
12
|
+
items = Utils::Array.wrap_nil(Engine.call(rules, scope_stack))
|
|
13
|
+
evaluated = true
|
|
14
|
+
else
|
|
15
|
+
items = Utils::Array.wrap_nil(rules)
|
|
16
|
+
evaluated = false
|
|
21
17
|
end
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
raise Errors::InvalidArguments if items.empty?
|
|
20
|
+
|
|
21
|
+
best = evaluated ? items[0] : Engine.call(items[0], scope_stack)
|
|
22
|
+
raise Errors::InvalidArguments unless best.is_a?(Numeric)
|
|
23
|
+
|
|
24
|
+
i = 1
|
|
25
|
+
n = items.size
|
|
26
|
+
while i < n
|
|
27
|
+
v = evaluated ? items[i] : Engine.call(items[i], scope_stack)
|
|
28
|
+
raise Errors::InvalidArguments unless v.is_a?(Numeric)
|
|
29
|
+
best = v if op == :min ? v < best : v > best
|
|
30
|
+
i += 1
|
|
31
|
+
end
|
|
32
|
+
best
|
|
25
33
|
end
|
|
26
34
|
end
|
|
27
35
|
end
|
|
@@ -11,10 +11,14 @@ module ShinyJsonLogic
|
|
|
11
11
|
|
|
12
12
|
def self.execute(rules, scope_stack)
|
|
13
13
|
safe_arithmetic do
|
|
14
|
+
operands = Utils::Array.wrap_nil(rules)
|
|
14
15
|
result = 0.0
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
i = 0
|
|
17
|
+
n = operands.size
|
|
18
|
+
while i < n
|
|
19
|
+
val = Numericals::Numerify.numerify(evaluate(operands[i], scope_stack))
|
|
17
20
|
result += val.nil? ? 0 : val
|
|
21
|
+
i += 1
|
|
18
22
|
end
|
|
19
23
|
result
|
|
20
24
|
end
|
|
@@ -8,10 +8,12 @@ module ShinyJsonLogic
|
|
|
8
8
|
class All < 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) unless Truthy.call(Engine.call(filter, scope_stack))
|
|
13
|
+
end
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
def self.on_after(results, _scope_stack)
|
|
16
|
+
results.empty? ? false : 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 unless Truthy.call(result)
|
|
23
|
+
i += 1
|
|
21
24
|
end
|
|
22
25
|
result
|
|
23
26
|
end
|
|
@@ -13,9 +13,14 @@ module ShinyJsonLogic
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def self.resolve_rules(rules, scope_stack)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
# Inline op? check to avoid extra method-call overhead on the hot path.
|
|
17
|
+
# Most rule args are primitives/arrays — the is_a?(Hash) guard is cheap.
|
|
18
|
+
if rules.is_a?(Hash) && !rules.empty? && !rules.is_a?(Utils::DataHash)
|
|
19
|
+
key = rules.first[0]
|
|
20
|
+
return rules unless Engine::OPERATIONS.key?(key.to_s)
|
|
21
|
+
raise Errors::InvalidArguments if raise_on_dynamic_args?
|
|
22
|
+
return Engine.call(rules, scope_stack)
|
|
23
|
+
end
|
|
19
24
|
rules
|
|
20
25
|
end
|
|
21
26
|
|
|
@@ -36,7 +41,9 @@ module ShinyJsonLogic
|
|
|
36
41
|
end
|
|
37
42
|
|
|
38
43
|
def self.op?(value)
|
|
39
|
-
return false unless value.is_a?(Hash)
|
|
44
|
+
return false unless value.is_a?(Hash)
|
|
45
|
+
return false if value.empty?
|
|
46
|
+
|
|
40
47
|
OperatorSolver.operation?(value)
|
|
41
48
|
end
|
|
42
49
|
end
|
|
@@ -6,9 +6,12 @@ module ShinyJsonLogic
|
|
|
6
6
|
module Operations
|
|
7
7
|
class Coalesce < Base
|
|
8
8
|
def self.execute(rules, scope_stack)
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
i = 0
|
|
10
|
+
n = rules.size
|
|
11
|
+
while i < n
|
|
12
|
+
result = evaluate(rules[i], scope_stack)
|
|
11
13
|
return result unless result.nil?
|
|
14
|
+
i += 1
|
|
12
15
|
end
|
|
13
16
|
nil
|
|
14
17
|
end
|
|
@@ -6,12 +6,25 @@ module ShinyJsonLogic
|
|
|
6
6
|
module Operations
|
|
7
7
|
class Concatenation < Base
|
|
8
8
|
def self.execute(rules, scope_stack)
|
|
9
|
-
result =
|
|
10
|
-
Utils::Array.wrap_nil(rules)
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
result = +""
|
|
10
|
+
operands = Utils::Array.wrap_nil(rules)
|
|
11
|
+
i = 0
|
|
12
|
+
n = operands.size
|
|
13
|
+
while i < n
|
|
14
|
+
evaluated = evaluate(operands[i], scope_stack)
|
|
15
|
+
if evaluated.is_a?(Array)
|
|
16
|
+
j = 0
|
|
17
|
+
m = evaluated.size
|
|
18
|
+
while j < m
|
|
19
|
+
result << evaluated[j].to_s
|
|
20
|
+
j += 1
|
|
21
|
+
end
|
|
22
|
+
else
|
|
23
|
+
result << evaluated.to_s
|
|
24
|
+
end
|
|
25
|
+
i += 1
|
|
13
26
|
end
|
|
14
|
-
result
|
|
27
|
+
result
|
|
15
28
|
end
|
|
16
29
|
end
|
|
17
30
|
end
|
|
@@ -15,14 +15,17 @@ module ShinyJsonLogic
|
|
|
15
15
|
|
|
16
16
|
result = nil
|
|
17
17
|
count = 0
|
|
18
|
+
i = 0
|
|
19
|
+
n = operands.size
|
|
18
20
|
|
|
19
21
|
begin
|
|
20
|
-
|
|
21
|
-
evaluated = evaluate(
|
|
22
|
+
while i < n
|
|
23
|
+
evaluated = evaluate(operands[i], scope_stack)
|
|
22
24
|
num = Numericals::Numerify.numerify(evaluated)
|
|
23
25
|
return handle_nan if num.nil?
|
|
24
26
|
count += 1
|
|
25
27
|
result = result.nil? ? num : result / num
|
|
28
|
+
i += 1
|
|
26
29
|
end
|
|
27
30
|
rescue TypeError
|
|
28
31
|
return handle_nan
|
|
@@ -6,17 +6,17 @@ module ShinyJsonLogic
|
|
|
6
6
|
module Operations
|
|
7
7
|
class Exists < Base
|
|
8
8
|
def self.execute(rules, scope_stack)
|
|
9
|
-
current = scope_stack.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
current = scope_stack.last
|
|
10
|
+
operands = Utils::Array.wrap_nil(rules)
|
|
11
|
+
i = 0
|
|
12
|
+
n = operands.size
|
|
13
|
+
while i < n
|
|
14
|
+
segment = evaluate(operands[i], scope_stack)
|
|
15
|
+
return false unless current.is_a?(Hash) && current.key?(segment)
|
|
14
16
|
current = current[segment]
|
|
17
|
+
i += 1
|
|
15
18
|
end
|
|
16
|
-
|
|
17
19
|
true
|
|
18
|
-
rescue StandardError
|
|
19
|
-
false
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -9,12 +9,25 @@ module ShinyJsonLogic
|
|
|
9
9
|
raise_on_nil_filter!
|
|
10
10
|
raise_on_dynamic_args!
|
|
11
11
|
|
|
12
|
-
def self.
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
def self.call(rules, scope_stack)
|
|
13
|
+
rules = resolve_rules(rules, scope_stack)
|
|
14
|
+
filter = setup_filter(rules)
|
|
15
|
+
collection = setup_collection(rules, scope_stack)
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
result = []
|
|
18
|
+
i = 0
|
|
19
|
+
n = collection.size
|
|
20
|
+
while i < n
|
|
21
|
+
item = collection[i]
|
|
22
|
+
scope_stack << item
|
|
23
|
+
begin
|
|
24
|
+
result << item if Truthy.call(Engine.call(filter, scope_stack))
|
|
25
|
+
ensure
|
|
26
|
+
scope_stack.pop
|
|
27
|
+
end
|
|
28
|
+
i += 1
|
|
29
|
+
end
|
|
30
|
+
result
|
|
18
31
|
end
|
|
19
32
|
end
|
|
20
33
|
end
|
|
@@ -8,7 +8,22 @@ module ShinyJsonLogic
|
|
|
8
8
|
def self.execute(rules, scope_stack)
|
|
9
9
|
needle = evaluate(rules.first, scope_stack)
|
|
10
10
|
haystack = evaluate(rules.last, scope_stack)
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
# Normalize Symbols to String so :foo matches "foo" and vice-versa
|
|
13
|
+
needle = needle.to_s if needle.is_a?(Symbol)
|
|
14
|
+
|
|
15
|
+
if haystack.is_a?(Array)
|
|
16
|
+
i = 0
|
|
17
|
+
n = haystack.size
|
|
18
|
+
while i < n
|
|
19
|
+
el = haystack[i]
|
|
20
|
+
return true if (el.is_a?(Symbol) ? el.to_s : el) == needle
|
|
21
|
+
i += 1
|
|
22
|
+
end
|
|
23
|
+
false
|
|
24
|
+
else
|
|
25
|
+
haystack.include?(needle)
|
|
26
|
+
end
|
|
12
27
|
end
|
|
13
28
|
end
|
|
14
29
|
end
|
|
@@ -14,46 +14,43 @@ module ShinyJsonLogic
|
|
|
14
14
|
@raise_on_nil_filter
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def self.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
index_scope = { "index" => 0 }
|
|
23
|
-
on_before(scope_stack)
|
|
24
|
-
results = collection.each_with_index.each_with_object([]) do |(item, index), acc|
|
|
25
|
-
index_scope["index"] = index
|
|
26
|
-
scope_stack.push(index_scope, index: index)
|
|
27
|
-
scope_stack.push(item, index: index)
|
|
28
|
-
begin
|
|
29
|
-
solved = on_each(item, filter, scope_stack)
|
|
30
|
-
acc << solved
|
|
31
|
-
scope_stack.pop
|
|
32
|
-
scope_stack.pop
|
|
33
|
-
rescue => e
|
|
34
|
-
scope_stack.pop # item scope
|
|
35
|
-
scope_stack.pop # iterator context scope
|
|
36
|
-
raise e
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
on_after(results, scope_stack)
|
|
17
|
+
def self.setup_filter(rules)
|
|
18
|
+
raise Errors::InvalidArguments unless rules.is_a?(Array)
|
|
19
|
+
filter = rules[1]
|
|
20
|
+
raise Errors::InvalidArguments if filter.nil? && raise_on_nil_filter?
|
|
21
|
+
filter
|
|
40
22
|
end
|
|
41
23
|
|
|
42
24
|
def self.setup_collection(rules, scope_stack)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
25
|
+
collection_rule = rules.size > 0 ? rules[0] : rules
|
|
26
|
+
raise Errors::InvalidArguments if collection_rule.nil?
|
|
27
|
+
Utils::Array.wrap(Engine.call(collection_rule, scope_stack))
|
|
28
|
+
end
|
|
47
29
|
|
|
48
|
-
|
|
49
|
-
|
|
30
|
+
def self.call(rules, scope_stack)
|
|
31
|
+
rules = resolve_rules(rules, scope_stack)
|
|
32
|
+
filter = setup_filter(rules)
|
|
33
|
+
collection = setup_collection(rules, scope_stack)
|
|
50
34
|
|
|
51
|
-
|
|
52
|
-
|
|
35
|
+
early = catch(:early_return) do
|
|
36
|
+
results = []
|
|
37
|
+
i = 0
|
|
38
|
+
n = collection.size
|
|
39
|
+
while i < n
|
|
40
|
+
item = collection[i]
|
|
41
|
+
scope_stack << item
|
|
42
|
+
begin
|
|
43
|
+
results << on_each(item, filter, scope_stack)
|
|
44
|
+
ensure
|
|
45
|
+
scope_stack.pop
|
|
46
|
+
end
|
|
47
|
+
i += 1
|
|
48
|
+
end
|
|
49
|
+
on_after(results, scope_stack)
|
|
50
|
+
end
|
|
51
|
+
early
|
|
53
52
|
end
|
|
54
53
|
|
|
55
|
-
def self.on_before(_scope_stack); end
|
|
56
|
-
|
|
57
54
|
def self.on_each(_item, filter, scope_stack)
|
|
58
55
|
Engine.call(filter, scope_stack)
|
|
59
56
|
end
|
|
@@ -61,10 +58,6 @@ module ShinyJsonLogic
|
|
|
61
58
|
def self.on_after(results, _scope_stack)
|
|
62
59
|
results
|
|
63
60
|
end
|
|
64
|
-
|
|
65
|
-
def self.handle_nil
|
|
66
|
-
raise Errors::InvalidArguments
|
|
67
|
-
end
|
|
68
61
|
end
|
|
69
62
|
end
|
|
70
63
|
end
|
|
@@ -7,7 +7,7 @@ module ShinyJsonLogic
|
|
|
7
7
|
module Operations
|
|
8
8
|
class Max < Base
|
|
9
9
|
def self.execute(rules, scope_stack)
|
|
10
|
-
Numericals::MinMaxCollection.
|
|
10
|
+
Numericals::MinMaxCollection.resolve(rules, scope_stack, :max)
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
end
|
|
@@ -6,9 +6,20 @@ module ShinyJsonLogic
|
|
|
6
6
|
module Operations
|
|
7
7
|
class Merge < Base
|
|
8
8
|
def self.execute(rules, scope_stack)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
result = []
|
|
10
|
+
operands = Utils::Array.wrap_nil(rules)
|
|
11
|
+
i = 0
|
|
12
|
+
n = operands.size
|
|
13
|
+
while i < n
|
|
14
|
+
evaluated = evaluate(operands[i], scope_stack)
|
|
15
|
+
if evaluated.is_a?(Array)
|
|
16
|
+
result.concat(evaluated)
|
|
17
|
+
else
|
|
18
|
+
result << evaluated
|
|
19
|
+
end
|
|
20
|
+
i += 1
|
|
21
|
+
end
|
|
22
|
+
result
|
|
12
23
|
end
|
|
13
24
|
end
|
|
14
25
|
end
|
|
@@ -7,7 +7,7 @@ module ShinyJsonLogic
|
|
|
7
7
|
module Operations
|
|
8
8
|
class Min < Base
|
|
9
9
|
def self.execute(rules, scope_stack)
|
|
10
|
-
Numericals::MinMaxCollection.
|
|
10
|
+
Numericals::MinMaxCollection.resolve(rules, scope_stack, :min)
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
end
|
|
@@ -7,34 +7,55 @@ module ShinyJsonLogic
|
|
|
7
7
|
module Operations
|
|
8
8
|
class Missing < Base
|
|
9
9
|
def self.execute(rules, scope_stack)
|
|
10
|
-
|
|
10
|
+
wrapped = Utils::Array.wrap_nil(rules)
|
|
11
11
|
keys = []
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
i = 0
|
|
13
|
+
n = wrapped.size
|
|
14
|
+
while i < n
|
|
15
|
+
evaluated = evaluate(wrapped[i], scope_stack)
|
|
16
|
+
if evaluated.is_a?(Array)
|
|
17
|
+
j = 0
|
|
18
|
+
m = evaluated.size
|
|
19
|
+
while j < m
|
|
20
|
+
keys << evaluated[j].to_s
|
|
21
|
+
j += 1
|
|
22
|
+
end
|
|
23
|
+
else
|
|
24
|
+
keys << evaluated.to_s
|
|
25
|
+
end
|
|
26
|
+
i += 1
|
|
15
27
|
end
|
|
16
|
-
|
|
28
|
+
|
|
29
|
+
current_data = scope_stack.last
|
|
17
30
|
return keys unless current_data.is_a?(Hash)
|
|
18
31
|
|
|
19
|
-
|
|
32
|
+
existing = {}
|
|
33
|
+
deep_keys(current_data, nil, existing)
|
|
34
|
+
|
|
35
|
+
result = []
|
|
36
|
+
i = 0
|
|
37
|
+
n = keys.size
|
|
38
|
+
while i < n
|
|
39
|
+
result << keys[i] unless existing.key?(keys[i])
|
|
40
|
+
i += 1
|
|
41
|
+
end
|
|
42
|
+
result
|
|
20
43
|
end
|
|
21
44
|
|
|
22
|
-
def self.deep_keys(hash, prefix
|
|
45
|
+
def self.deep_keys(hash, prefix, acc)
|
|
23
46
|
return unless hash.is_a?(Hash)
|
|
24
47
|
|
|
25
|
-
result = []
|
|
26
48
|
hash.each do |key, val|
|
|
27
49
|
key_s = key.to_s
|
|
28
50
|
full_key = prefix ? "#{prefix}.#{key_s}" : key_s
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
result.concat(nested)
|
|
51
|
+
if val.is_a?(Hash)
|
|
52
|
+
deep_keys(val, full_key, acc)
|
|
32
53
|
else
|
|
33
|
-
|
|
54
|
+
acc[full_key] = true
|
|
34
55
|
end
|
|
35
56
|
end
|
|
36
|
-
result
|
|
37
57
|
end
|
|
58
|
+
|
|
38
59
|
private_class_method :deep_keys
|
|
39
60
|
end
|
|
40
61
|
end
|
|
@@ -8,13 +8,29 @@ module ShinyJsonLogic
|
|
|
8
8
|
class MissingSome < Missing
|
|
9
9
|
def self.execute(rules, scope_stack)
|
|
10
10
|
min_required = evaluate(rules[0], scope_stack)
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
raw_keys = evaluate(rules[1], scope_stack)
|
|
12
|
+
raw_keys_arr = wrap_nil(raw_keys)
|
|
13
|
+
keys = Array.new(raw_keys_arr.size)
|
|
14
|
+
i = 0
|
|
15
|
+
n = raw_keys_arr.size
|
|
16
|
+
while i < n
|
|
17
|
+
keys[i] = raw_keys_arr[i].to_s
|
|
18
|
+
i += 1
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
current_data = scope_stack.last
|
|
13
22
|
return keys unless current_data.is_a?(Hash) && rules.is_a?(Array)
|
|
14
23
|
|
|
15
|
-
data_keys = current_data.keys
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
data_keys = current_data.keys
|
|
25
|
+
data_keys_s = Array.new(data_keys.size)
|
|
26
|
+
j = 0
|
|
27
|
+
m = data_keys.size
|
|
28
|
+
while j < m
|
|
29
|
+
data_keys_s[j] = data_keys[j].to_s
|
|
30
|
+
j += 1
|
|
31
|
+
end
|
|
32
|
+
present = keys & data_keys_s
|
|
33
|
+
present.size >= min_required ? [] : keys - present
|
|
18
34
|
end
|
|
19
35
|
end
|
|
20
36
|
end
|