shiny_json_logic 0.2.9 → 0.2.11
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/.gitignore +1 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile.lock +1 -1
- data/lib/shiny_json_logic/comparisons/comparable.rb +36 -0
- data/lib/shiny_json_logic/engine.rb +3 -4
- data/lib/shiny_json_logic/numericals/min_max_collection.rb +2 -2
- data/lib/shiny_json_logic/operations/addition.rb +1 -1
- data/lib/shiny_json_logic/operations/base.rb +3 -0
- data/lib/shiny_json_logic/operations/concatenation.rb +2 -2
- data/lib/shiny_json_logic/operations/different.rb +6 -34
- data/lib/shiny_json_logic/operations/division.rb +1 -1
- data/lib/shiny_json_logic/operations/double_not.rb +1 -1
- data/lib/shiny_json_logic/operations/equal.rb +4 -32
- data/lib/shiny_json_logic/operations/exists.rb +1 -1
- data/lib/shiny_json_logic/operations/greater.rb +4 -32
- data/lib/shiny_json_logic/operations/greater_equal.rb +4 -32
- data/lib/shiny_json_logic/operations/iterable/base.rb +1 -1
- data/lib/shiny_json_logic/operations/merge.rb +2 -2
- data/lib/shiny_json_logic/operations/missing.rb +3 -3
- data/lib/shiny_json_logic/operations/missing_some.rb +3 -2
- data/lib/shiny_json_logic/operations/modulo.rb +1 -1
- data/lib/shiny_json_logic/operations/preserve.rb +1 -1
- data/lib/shiny_json_logic/operations/product.rb +1 -1
- data/lib/shiny_json_logic/operations/smaller.rb +4 -32
- data/lib/shiny_json_logic/operations/smaller_equal.rb +4 -32
- data/lib/shiny_json_logic/operations/strict_different.rb +3 -3
- data/lib/shiny_json_logic/operations/strict_equal.rb +1 -1
- data/lib/shiny_json_logic/operations/subtraction.rb +1 -1
- data/lib/shiny_json_logic/operations/throw.rb +0 -1
- data/lib/shiny_json_logic/operations/try.rb +1 -3
- data/lib/shiny_json_logic/operations/val.rb +6 -4
- data/lib/shiny_json_logic/operations/var.rb +26 -2
- data/lib/shiny_json_logic/operator_solver.rb +1 -3
- data/lib/shiny_json_logic/scope_stack.rb +15 -13
- data/lib/shiny_json_logic/utils/array.rb +22 -0
- data/lib/shiny_json_logic/utils/indifferent_hash.rb +72 -0
- data/lib/shiny_json_logic/version.rb +1 -1
- data/lib/shiny_json_logic.rb +1 -1
- data/shiny_json_logic.gemspec +2 -2
- metadata +8 -6
- data/lib/core_ext/array.rb +0 -19
- data/lib/core_ext/hash.rb +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6c51d139183162260ba3b85195e9e8854da441bedad9ca5753785d2a781b9d4
|
|
4
|
+
data.tar.gz: 499f7e445cb8aadebb2397781c36b09722b688ee955994002963e38a0ce64b78
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5723ddd51acda1014b43b9f84e4ce2054a7b9fc1ea6e86fecc7d30742bd8b6f51309d7d220f15a7546713a151be503977cc7452bac1953a08c21fd6efbd9d285
|
|
7
|
+
data.tar.gz: bd2182abf594716ee39c4b892dae1079969f5d49402c258ad136d8b935460f9cd88d924dc6ffb8aa9336328578157f34eddbd04dc4ce8b7310c91cd4fbd18f48
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.2.11] - 2026-02-09
|
|
6
|
+
### Changed
|
|
7
|
+
- Removes monkey patches in favour of isolated modules.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Supports hashes with string or symbol access indifferently (e.g. `{"a" => 1}` can be accessed with `:a` or `"a"`).
|
|
11
|
+
|
|
5
12
|
## [0.2.9] - 2026-02-08
|
|
6
13
|
### Added
|
|
7
14
|
- New specific error classes: `Errors::InvalidArguments`, `Errors::NotANumber`, `Errors::UnknownOperator`
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shiny_json_logic/numericals/numerify"
|
|
4
|
+
|
|
5
|
+
module ShinyJsonLogic
|
|
6
|
+
module Comparisons
|
|
7
|
+
module Comparable
|
|
8
|
+
include Numericals::Numerify
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def compare(a, b)
|
|
13
|
+
return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
|
|
14
|
+
|
|
15
|
+
if a.is_a?(String) && b.is_a?(String)
|
|
16
|
+
return a <=> b
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
num_a = numerify_for_comparison(a)
|
|
20
|
+
num_b = numerify_for_comparison(b)
|
|
21
|
+
return :nan if num_a.nil? || num_b.nil?
|
|
22
|
+
|
|
23
|
+
num_a <=> num_b
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def numerify_for_comparison(value)
|
|
27
|
+
return value.to_f if value.is_a?(Numeric)
|
|
28
|
+
return 0.0 if value == false
|
|
29
|
+
return 1.0 if value == true
|
|
30
|
+
return 0.0 if value.nil?
|
|
31
|
+
return value.to_f if value.is_a?(String) && numeric_string?(value)
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
require "core_ext/array"
|
|
2
|
-
require "core_ext/hash"
|
|
3
1
|
require "shiny_json_logic/operator_solver"
|
|
4
2
|
|
|
5
3
|
module ShinyJsonLogic
|
|
@@ -14,9 +12,10 @@ module ShinyJsonLogic
|
|
|
14
12
|
return rule if rule.empty?
|
|
15
13
|
|
|
16
14
|
operation, args = rule.to_a.first
|
|
17
|
-
|
|
15
|
+
operation_key = operation.to_s
|
|
16
|
+
return rule unless operations.key?(operation_key)
|
|
18
17
|
|
|
19
|
-
operations.fetch(
|
|
18
|
+
operations.fetch(operation_key).new(args, scope_stack).call
|
|
20
19
|
elsif rule.is_a?(Array)
|
|
21
20
|
rule.map { |val| call(val) }
|
|
22
21
|
else
|
|
@@ -14,12 +14,12 @@ module ShinyJsonLogic
|
|
|
14
14
|
|
|
15
15
|
def collect_values
|
|
16
16
|
result = []
|
|
17
|
-
|
|
17
|
+
wrap_nil(rules).each do |rule|
|
|
18
18
|
evaluated = evaluate(rule)
|
|
19
19
|
# If rule was an operation (Hash), expand the result array
|
|
20
20
|
# If rule was a literal array, it's invalid (will fail numeric check)
|
|
21
21
|
if operation?(rule)
|
|
22
|
-
|
|
22
|
+
wrap_nil(evaluated).each { |val| result << val }
|
|
23
23
|
else
|
|
24
24
|
result << evaluated
|
|
25
25
|
end
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
require "shiny_json_logic/truthy"
|
|
2
|
+
require "shiny_json_logic/utils/array"
|
|
2
3
|
|
|
3
4
|
module ShinyJsonLogic
|
|
4
5
|
module Operations
|
|
5
6
|
class Base
|
|
7
|
+
include Utils::Array
|
|
8
|
+
|
|
6
9
|
def initialize(rules, scope_stack)
|
|
7
10
|
@scope_stack = scope_stack
|
|
8
11
|
@dynamic_args = operation?(rules)
|
|
@@ -5,9 +5,9 @@ module ShinyJsonLogic
|
|
|
5
5
|
class Concatenation < Base
|
|
6
6
|
def call
|
|
7
7
|
result = []
|
|
8
|
-
|
|
8
|
+
wrap_nil(rules).each do |rule|
|
|
9
9
|
evaluated = evaluate(rule)
|
|
10
|
-
|
|
10
|
+
wrap_nil(evaluated).each { |v| result << v.to_s }
|
|
11
11
|
end
|
|
12
12
|
result.join
|
|
13
13
|
end
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
require "shiny_json_logic/operations/base"
|
|
2
2
|
require "shiny_json_logic/numericals/with_error_handling"
|
|
3
|
-
require "shiny_json_logic/
|
|
3
|
+
require "shiny_json_logic/comparisons/comparable"
|
|
4
4
|
|
|
5
5
|
module ShinyJsonLogic
|
|
6
6
|
module Operations
|
|
7
7
|
class Different < Base
|
|
8
8
|
include Numericals::WithErrorHandling
|
|
9
|
-
include
|
|
9
|
+
include Comparisons::Comparable
|
|
10
10
|
raise_on_dynamic_args!
|
|
11
11
|
|
|
12
12
|
def call
|
|
13
13
|
return handle_invalid_args if dynamic_args?
|
|
14
|
-
operands =
|
|
14
|
+
operands = wrap_nil(rules)
|
|
15
15
|
return handle_invalid_args if operands.length < 2
|
|
16
16
|
|
|
17
17
|
prev = evaluate(operands[0])
|
|
@@ -19,39 +19,11 @@ module ShinyJsonLogic
|
|
|
19
19
|
curr = evaluate(rule)
|
|
20
20
|
result = compare(prev, curr)
|
|
21
21
|
return handle_nan if result == :nan
|
|
22
|
-
return false if result == 0
|
|
22
|
+
return false if result == 0
|
|
23
23
|
prev = curr
|
|
24
24
|
end
|
|
25
|
-
true
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
def compare(a, b)
|
|
31
|
-
# Arrays u objetos → NaN
|
|
32
|
-
return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
|
|
33
|
-
|
|
34
|
-
# Ambos strings → comparación directa
|
|
35
|
-
if a.is_a?(String) && b.is_a?(String)
|
|
36
|
-
return a <=> b
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Convertir a números para comparar
|
|
40
|
-
num_a = numerify_for_compare(a)
|
|
41
|
-
num_b = numerify_for_compare(b)
|
|
42
|
-
return :nan if num_a.nil? || num_b.nil?
|
|
43
|
-
|
|
44
|
-
num_a <=> num_b
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def numerify_for_compare(value)
|
|
48
|
-
return value.to_f if value.is_a?(Numeric)
|
|
49
|
-
return 0.0 if value == false
|
|
50
|
-
return 1.0 if value == true
|
|
51
|
-
return 0.0 if value.nil?
|
|
52
|
-
return value.to_f if value.is_a?(String) && numeric_string?(value)
|
|
53
|
-
nil # String no numérica
|
|
25
|
+
true
|
|
54
26
|
end
|
|
55
27
|
end
|
|
56
28
|
end
|
|
57
|
-
end
|
|
29
|
+
end
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
require "shiny_json_logic/operations/base"
|
|
2
2
|
require "shiny_json_logic/numericals/with_error_handling"
|
|
3
|
-
require "shiny_json_logic/
|
|
3
|
+
require "shiny_json_logic/comparisons/comparable"
|
|
4
4
|
|
|
5
5
|
module ShinyJsonLogic
|
|
6
6
|
module Operations
|
|
7
7
|
class Equal < Base
|
|
8
8
|
include Numericals::WithErrorHandling
|
|
9
|
-
include
|
|
9
|
+
include Comparisons::Comparable
|
|
10
10
|
raise_on_dynamic_args!
|
|
11
11
|
|
|
12
12
|
def call
|
|
13
13
|
return handle_invalid_args if dynamic_args?
|
|
14
|
-
operands =
|
|
14
|
+
operands = wrap_nil(rules)
|
|
15
15
|
return handle_invalid_args if operands.length < 2
|
|
16
16
|
|
|
17
17
|
first = evaluate(operands[0])
|
|
@@ -23,34 +23,6 @@ module ShinyJsonLogic
|
|
|
23
23
|
end
|
|
24
24
|
true
|
|
25
25
|
end
|
|
26
|
-
|
|
27
|
-
private
|
|
28
|
-
|
|
29
|
-
def compare(a, b)
|
|
30
|
-
# Arrays u objetos → NaN
|
|
31
|
-
return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
|
|
32
|
-
|
|
33
|
-
# Ambos strings → comparación directa
|
|
34
|
-
if a.is_a?(String) && b.is_a?(String)
|
|
35
|
-
return a <=> b
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Convertir a números para comparar
|
|
39
|
-
num_a = numerify_for_compare(a)
|
|
40
|
-
num_b = numerify_for_compare(b)
|
|
41
|
-
return :nan if num_a.nil? || num_b.nil?
|
|
42
|
-
|
|
43
|
-
num_a <=> num_b
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def numerify_for_compare(value)
|
|
47
|
-
return value.to_f if value.is_a?(Numeric)
|
|
48
|
-
return 0.0 if value == false
|
|
49
|
-
return 1.0 if value == true
|
|
50
|
-
return 0.0 if value.nil?
|
|
51
|
-
return value.to_f if value.is_a?(String) && numeric_string?(value)
|
|
52
|
-
nil # String no numérica
|
|
53
|
-
end
|
|
54
26
|
end
|
|
55
27
|
end
|
|
56
|
-
end
|
|
28
|
+
end
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
require "shiny_json_logic/operations/base"
|
|
2
2
|
require "shiny_json_logic/numericals/with_error_handling"
|
|
3
|
-
require "shiny_json_logic/
|
|
3
|
+
require "shiny_json_logic/comparisons/comparable"
|
|
4
4
|
|
|
5
5
|
module ShinyJsonLogic
|
|
6
6
|
module Operations
|
|
7
7
|
class Greater < Base
|
|
8
8
|
include Numericals::WithErrorHandling
|
|
9
|
-
include
|
|
9
|
+
include Comparisons::Comparable
|
|
10
10
|
raise_on_dynamic_args!
|
|
11
11
|
|
|
12
12
|
def call
|
|
13
13
|
return handle_invalid_args if dynamic_args?
|
|
14
|
-
operands =
|
|
14
|
+
operands = wrap_nil(rules)
|
|
15
15
|
return handle_invalid_args if operands.length < 2
|
|
16
16
|
|
|
17
17
|
prev = evaluate(operands[0])
|
|
@@ -24,34 +24,6 @@ module ShinyJsonLogic
|
|
|
24
24
|
end
|
|
25
25
|
true
|
|
26
26
|
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
def compare(a, b)
|
|
31
|
-
# Arrays u objetos → NaN
|
|
32
|
-
return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
|
|
33
|
-
|
|
34
|
-
# Ambos strings → comparación lexicográfica
|
|
35
|
-
if a.is_a?(String) && b.is_a?(String)
|
|
36
|
-
return a <=> b
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Convertir a números para comparar
|
|
40
|
-
num_a = numerify_for_compare(a)
|
|
41
|
-
num_b = numerify_for_compare(b)
|
|
42
|
-
return :nan if num_a.nil? || num_b.nil?
|
|
43
|
-
|
|
44
|
-
num_a <=> num_b
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def numerify_for_compare(value)
|
|
48
|
-
return value.to_f if value.is_a?(Numeric)
|
|
49
|
-
return 0.0 if value == false
|
|
50
|
-
return 1.0 if value == true
|
|
51
|
-
return 0.0 if value.nil?
|
|
52
|
-
return value.to_f if value.is_a?(String) && numeric_string?(value)
|
|
53
|
-
nil # String no numérica
|
|
54
|
-
end
|
|
55
27
|
end
|
|
56
28
|
end
|
|
57
|
-
end
|
|
29
|
+
end
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
require "shiny_json_logic/operations/base"
|
|
2
2
|
require "shiny_json_logic/numericals/with_error_handling"
|
|
3
|
-
require "shiny_json_logic/
|
|
3
|
+
require "shiny_json_logic/comparisons/comparable"
|
|
4
4
|
|
|
5
5
|
module ShinyJsonLogic
|
|
6
6
|
module Operations
|
|
7
7
|
class GreaterEqual < Base
|
|
8
8
|
include Numericals::WithErrorHandling
|
|
9
|
-
include
|
|
9
|
+
include Comparisons::Comparable
|
|
10
10
|
raise_on_dynamic_args!
|
|
11
11
|
|
|
12
12
|
def call
|
|
13
13
|
return handle_invalid_args if dynamic_args?
|
|
14
|
-
operands =
|
|
14
|
+
operands = wrap_nil(rules)
|
|
15
15
|
return handle_invalid_args if operands.length < 2
|
|
16
16
|
|
|
17
17
|
prev = evaluate(operands[0])
|
|
@@ -24,34 +24,6 @@ module ShinyJsonLogic
|
|
|
24
24
|
end
|
|
25
25
|
true
|
|
26
26
|
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
def compare(a, b)
|
|
31
|
-
# Arrays u objetos → NaN
|
|
32
|
-
return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
|
|
33
|
-
|
|
34
|
-
# Ambos strings → comparación lexicográfica
|
|
35
|
-
if a.is_a?(String) && b.is_a?(String)
|
|
36
|
-
return a <=> b
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Convertir a números para comparar
|
|
40
|
-
num_a = numerify_for_compare(a)
|
|
41
|
-
num_b = numerify_for_compare(b)
|
|
42
|
-
return :nan if num_a.nil? || num_b.nil?
|
|
43
|
-
|
|
44
|
-
num_a <=> num_b
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def numerify_for_compare(value)
|
|
48
|
-
return value.to_f if value.is_a?(Numeric)
|
|
49
|
-
return 0.0 if value == false
|
|
50
|
-
return 1.0 if value == true
|
|
51
|
-
return 0.0 if value.nil?
|
|
52
|
-
return value.to_f if value.is_a?(String) && numeric_string?(value)
|
|
53
|
-
nil # String no numérica
|
|
54
|
-
end
|
|
55
27
|
end
|
|
56
28
|
end
|
|
57
|
-
end
|
|
29
|
+
end
|
|
@@ -5,11 +5,11 @@ module ShinyJsonLogic
|
|
|
5
5
|
module Operations
|
|
6
6
|
class Missing < Base
|
|
7
7
|
def call
|
|
8
|
-
items =
|
|
8
|
+
items = wrap_nil(rules)
|
|
9
9
|
keys = []
|
|
10
10
|
items.each do |rule|
|
|
11
11
|
evaluated = evaluate(rule)
|
|
12
|
-
keys.concat(
|
|
12
|
+
keys.concat(wrap_nil(evaluated).map(&:to_s))
|
|
13
13
|
end
|
|
14
14
|
return keys unless data.is_a?(Hash)
|
|
15
15
|
|
|
@@ -21,7 +21,7 @@ module ShinyJsonLogic
|
|
|
21
21
|
def deep_keys(hash)
|
|
22
22
|
return unless hash.is_a?(Hash)
|
|
23
23
|
|
|
24
|
-
hash.keys.map{|key| ([key] << deep_keys(hash[key])).compact.join(".") }
|
|
24
|
+
hash.keys.map{|key| ([key.to_s] << deep_keys(hash[key])).compact.join(".") }
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
end
|
|
@@ -6,10 +6,11 @@ module ShinyJsonLogic
|
|
|
6
6
|
class MissingSome < Missing
|
|
7
7
|
def call
|
|
8
8
|
min_required = evaluate(rules[0])
|
|
9
|
-
keys =
|
|
9
|
+
keys = wrap_nil(evaluate(rules[1])).map(&:to_s)
|
|
10
10
|
return keys unless data.is_a?(Hash) && rules.is_a?(Array)
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
data_keys = data.keys.map(&:to_s)
|
|
13
|
+
present = keys & data_keys
|
|
13
14
|
present.size >= min_required ? [] : Missing.new(keys, scope_stack).call
|
|
14
15
|
end
|
|
15
16
|
end
|
|
@@ -4,7 +4,7 @@ module ShinyJsonLogic
|
|
|
4
4
|
module Operations
|
|
5
5
|
class Preserve < Iterable::Base
|
|
6
6
|
def initialize(rules, scope_stack)
|
|
7
|
-
@collection =
|
|
7
|
+
@collection = wrap(rules) || []
|
|
8
8
|
# Skip Iterable::Base initialization, go directly to Operations::Base
|
|
9
9
|
# Preserve doesn't need the standard iterable setup (filter, collection from rules[0], etc.)
|
|
10
10
|
@rules = rules
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
require "shiny_json_logic/operations/base"
|
|
2
2
|
require "shiny_json_logic/numericals/with_error_handling"
|
|
3
|
-
require "shiny_json_logic/
|
|
3
|
+
require "shiny_json_logic/comparisons/comparable"
|
|
4
4
|
|
|
5
5
|
module ShinyJsonLogic
|
|
6
6
|
module Operations
|
|
7
7
|
class Smaller < Base
|
|
8
8
|
include Numericals::WithErrorHandling
|
|
9
|
-
include
|
|
9
|
+
include Comparisons::Comparable
|
|
10
10
|
raise_on_dynamic_args!
|
|
11
11
|
|
|
12
12
|
def call
|
|
13
13
|
return handle_invalid_args if dynamic_args?
|
|
14
|
-
operands =
|
|
14
|
+
operands = wrap_nil(rules)
|
|
15
15
|
return handle_invalid_args if operands.length < 2
|
|
16
16
|
|
|
17
17
|
prev = evaluate(operands[0])
|
|
@@ -24,34 +24,6 @@ module ShinyJsonLogic
|
|
|
24
24
|
end
|
|
25
25
|
true
|
|
26
26
|
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
def compare(a, b)
|
|
31
|
-
# Arrays u objetos → NaN
|
|
32
|
-
return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
|
|
33
|
-
|
|
34
|
-
# Ambos strings → comparación lexicográfica
|
|
35
|
-
if a.is_a?(String) && b.is_a?(String)
|
|
36
|
-
return a <=> b
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Convertir a números para comparar
|
|
40
|
-
num_a = numerify_for_compare(a)
|
|
41
|
-
num_b = numerify_for_compare(b)
|
|
42
|
-
return :nan if num_a.nil? || num_b.nil?
|
|
43
|
-
|
|
44
|
-
num_a <=> num_b
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def numerify_for_compare(value)
|
|
48
|
-
return value.to_f if value.is_a?(Numeric)
|
|
49
|
-
return 0.0 if value == false
|
|
50
|
-
return 1.0 if value == true
|
|
51
|
-
return 0.0 if value.nil?
|
|
52
|
-
return value.to_f if value.is_a?(String) && numeric_string?(value)
|
|
53
|
-
nil # String no numérica
|
|
54
|
-
end
|
|
55
27
|
end
|
|
56
28
|
end
|
|
57
|
-
end
|
|
29
|
+
end
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
require "shiny_json_logic/operations/base"
|
|
2
2
|
require "shiny_json_logic/numericals/with_error_handling"
|
|
3
|
-
require "shiny_json_logic/
|
|
3
|
+
require "shiny_json_logic/comparisons/comparable"
|
|
4
4
|
|
|
5
5
|
module ShinyJsonLogic
|
|
6
6
|
module Operations
|
|
7
7
|
class SmallerEqual < Base
|
|
8
8
|
include Numericals::WithErrorHandling
|
|
9
|
-
include
|
|
9
|
+
include Comparisons::Comparable
|
|
10
10
|
raise_on_dynamic_args!
|
|
11
11
|
|
|
12
12
|
def call
|
|
13
13
|
return handle_invalid_args if dynamic_args?
|
|
14
|
-
operands =
|
|
14
|
+
operands = wrap_nil(rules)
|
|
15
15
|
return handle_invalid_args if operands.length < 2
|
|
16
16
|
|
|
17
17
|
prev = evaluate(operands[0])
|
|
@@ -24,34 +24,6 @@ module ShinyJsonLogic
|
|
|
24
24
|
end
|
|
25
25
|
true
|
|
26
26
|
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
def compare(a, b)
|
|
31
|
-
# Arrays u objetos → NaN
|
|
32
|
-
return :nan if a.is_a?(Array) || a.is_a?(Hash) || b.is_a?(Array) || b.is_a?(Hash)
|
|
33
|
-
|
|
34
|
-
# Ambos strings → comparación lexicográfica
|
|
35
|
-
if a.is_a?(String) && b.is_a?(String)
|
|
36
|
-
return a <=> b
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Convertir a números para comparar
|
|
40
|
-
num_a = numerify_for_compare(a)
|
|
41
|
-
num_b = numerify_for_compare(b)
|
|
42
|
-
return :nan if num_a.nil? || num_b.nil?
|
|
43
|
-
|
|
44
|
-
num_a <=> num_b
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def numerify_for_compare(value)
|
|
48
|
-
return value.to_f if value.is_a?(Numeric)
|
|
49
|
-
return 0.0 if value == false
|
|
50
|
-
return 1.0 if value == true
|
|
51
|
-
return 0.0 if value.nil?
|
|
52
|
-
return value.to_f if value.is_a?(String) && numeric_string?(value)
|
|
53
|
-
nil # String no numérica
|
|
54
|
-
end
|
|
55
27
|
end
|
|
56
28
|
end
|
|
57
|
-
end
|
|
29
|
+
end
|
|
@@ -9,16 +9,16 @@ module ShinyJsonLogic
|
|
|
9
9
|
|
|
10
10
|
def call
|
|
11
11
|
return handle_invalid_args if dynamic_args?
|
|
12
|
-
operands =
|
|
12
|
+
operands = wrap_nil(rules)
|
|
13
13
|
return handle_invalid_args if operands.length < 2
|
|
14
14
|
|
|
15
15
|
prev = cast(evaluate(operands[0]))
|
|
16
16
|
operands[1..].each do |rule|
|
|
17
17
|
curr = cast(evaluate(rule))
|
|
18
|
-
return false if curr == prev
|
|
18
|
+
return false if curr == prev
|
|
19
19
|
prev = curr
|
|
20
20
|
end
|
|
21
|
-
true
|
|
21
|
+
true
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
private
|
|
@@ -15,7 +15,6 @@ module ShinyJsonLogic
|
|
|
15
15
|
|
|
16
16
|
extracted_type = error_type.is_a?(Hash) && error_type.key?("type") ? error_type["type"] : error_type
|
|
17
17
|
extracted_type = self.data["type"] if extracted_type.nil?
|
|
18
|
-
self.data["type"] = extracted_type unless extracted_type.nil?
|
|
19
18
|
|
|
20
19
|
error = Errors::Base.new(type: extracted_type)
|
|
21
20
|
|
|
@@ -4,7 +4,7 @@ module ShinyJsonLogic
|
|
|
4
4
|
module Operations
|
|
5
5
|
class Val < Base
|
|
6
6
|
def call
|
|
7
|
-
raw_keys =
|
|
7
|
+
raw_keys = wrap_nil(rules)
|
|
8
8
|
|
|
9
9
|
# {"val": []} or {"val": null} - return current scope
|
|
10
10
|
if raw_keys.empty? || raw_keys == [nil]
|
|
@@ -44,16 +44,18 @@ module ShinyJsonLogic
|
|
|
44
44
|
keys.reduce(data) do |obj, key|
|
|
45
45
|
return nil if obj.nil?
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
when Hash
|
|
47
|
+
result = if obj.is_a?(Hash)
|
|
49
48
|
obj[key]
|
|
50
|
-
|
|
49
|
+
elsif obj.is_a?(Array)
|
|
51
50
|
# Convert string keys to integers for arrays
|
|
52
51
|
index = key.is_a?(String) ? key.to_i : key
|
|
53
52
|
obj[index]
|
|
54
53
|
else
|
|
55
54
|
nil
|
|
56
55
|
end
|
|
56
|
+
|
|
57
|
+
# Wrap nested hashes for indifferent access
|
|
58
|
+
result.is_a?(Hash) && !result.is_a?(IndifferentHash) ? IndifferentHash.new(result) : result
|
|
57
59
|
end
|
|
58
60
|
end
|
|
59
61
|
end
|
|
@@ -5,16 +5,40 @@ module ShinyJsonLogic
|
|
|
5
5
|
module Operations
|
|
6
6
|
class Var < Base
|
|
7
7
|
def call
|
|
8
|
-
items =
|
|
8
|
+
items = wrap_nil(rules)
|
|
9
9
|
key = evaluate(items[0])
|
|
10
10
|
default = items[1] ? evaluate(items[1]) : nil
|
|
11
11
|
|
|
12
12
|
return data if key.nil? || key == ""
|
|
13
13
|
|
|
14
|
-
data
|
|
14
|
+
fetch_value(data, key) || default
|
|
15
15
|
rescue
|
|
16
16
|
default || data
|
|
17
17
|
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def fetch_value(obj, key)
|
|
22
|
+
return nil if obj.nil?
|
|
23
|
+
|
|
24
|
+
# Split dot-separated keys
|
|
25
|
+
keys = key.to_s.split('.')
|
|
26
|
+
|
|
27
|
+
keys.reduce(obj) do |current, k|
|
|
28
|
+
return nil if current.nil?
|
|
29
|
+
|
|
30
|
+
if current.is_a?(Hash)
|
|
31
|
+
# Try string key first, then symbol key for indifferent access
|
|
32
|
+
result = current[k]
|
|
33
|
+
result = current[k.to_sym] if result.nil? && !current.key?(k)
|
|
34
|
+
result
|
|
35
|
+
elsif current.is_a?(Array)
|
|
36
|
+
current[k.to_i]
|
|
37
|
+
else
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
18
42
|
end
|
|
19
43
|
end
|
|
20
44
|
end
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
require "core_ext/array"
|
|
2
|
-
require "core_ext/hash"
|
|
3
1
|
Dir[File.join(__dir__, "operations/**/*.rb")].each do |file|
|
|
4
2
|
require file
|
|
5
3
|
end
|
|
@@ -7,7 +5,7 @@ end
|
|
|
7
5
|
module ShinyJsonLogic
|
|
8
6
|
class OperatorSolver
|
|
9
7
|
def operation?(value)
|
|
10
|
-
value.keys.any? { |key| solvers.key?(key) }
|
|
8
|
+
value.keys.any? { |key| solvers.key?(key.to_s) }
|
|
11
9
|
end
|
|
12
10
|
|
|
13
11
|
def solvers
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "shiny_json_logic/utils/indifferent_hash"
|
|
4
|
+
|
|
3
5
|
module ShinyJsonLogic
|
|
4
6
|
# Manages a stack of scopes for nested data access in iterators.
|
|
5
7
|
#
|
|
@@ -17,14 +19,13 @@ module ShinyJsonLogic
|
|
|
17
19
|
attr_reader :stack
|
|
18
20
|
|
|
19
21
|
def initialize(root_data)
|
|
20
|
-
|
|
21
|
-
@
|
|
22
|
-
@stack = [{ data: @root_snapshot, index: 0 }]
|
|
22
|
+
@root_data = wrap_indifferent(root_data)
|
|
23
|
+
@stack = [{ data: @root_data, index: 0 }]
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
# Push a new scope onto the stack (when entering an iteration)
|
|
26
27
|
def push(data, index: 0)
|
|
27
|
-
stack.push({ data: data, index: index })
|
|
28
|
+
stack.push({ data: wrap_indifferent(data), index: index })
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
# Pop the top scope (when exiting an iteration)
|
|
@@ -66,25 +67,26 @@ module ShinyJsonLogic
|
|
|
66
67
|
keys.reduce(data) do |obj, key|
|
|
67
68
|
return nil if obj.nil?
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
when Hash
|
|
70
|
+
result = if obj.is_a?(Hash)
|
|
71
71
|
obj[key]
|
|
72
|
-
|
|
72
|
+
elsif obj.is_a?(Array)
|
|
73
73
|
# Convert string keys to integers for arrays
|
|
74
74
|
index = key.is_a?(String) ? key.to_i : key
|
|
75
75
|
obj[index]
|
|
76
76
|
else
|
|
77
77
|
nil
|
|
78
78
|
end
|
|
79
|
+
|
|
80
|
+
# Wrap nested hashes for indifferent access
|
|
81
|
+
result.is_a?(Hash) && !result.is_a?(IndifferentHash) ? IndifferentHash.new(result) : result
|
|
79
82
|
end
|
|
80
83
|
end
|
|
81
84
|
|
|
82
|
-
def
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
obj.map { |v| deep_dup(v) }
|
|
85
|
+
def wrap_indifferent(obj)
|
|
86
|
+
if obj.is_a?(IndifferentHash)
|
|
87
|
+
obj
|
|
88
|
+
elsif obj.is_a?(Hash)
|
|
89
|
+
IndifferentHash.new(obj)
|
|
88
90
|
else
|
|
89
91
|
obj
|
|
90
92
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ShinyJsonLogic
|
|
4
|
+
module Utils
|
|
5
|
+
module Array
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def wrap(object)
|
|
9
|
+
return [] if object.nil?
|
|
10
|
+
return object.to_ary || [object] if object.respond_to?(:to_ary)
|
|
11
|
+
|
|
12
|
+
[object]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def wrap_nil(object)
|
|
16
|
+
return [nil] if object.nil?
|
|
17
|
+
|
|
18
|
+
wrap(object)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "delegate"
|
|
4
|
+
|
|
5
|
+
module ShinyJsonLogic
|
|
6
|
+
class IndifferentHash < SimpleDelegator
|
|
7
|
+
# Make is_a?(Hash) return true so existing code works
|
|
8
|
+
def is_a?(klass)
|
|
9
|
+
klass == Hash || super
|
|
10
|
+
end
|
|
11
|
+
alias kind_of? is_a?
|
|
12
|
+
|
|
13
|
+
def [](key)
|
|
14
|
+
obj = __getobj__
|
|
15
|
+
return obj[key] if obj.key?(key)
|
|
16
|
+
|
|
17
|
+
alt_key = alternate_key(key)
|
|
18
|
+
return obj[alt_key] if alt_key && obj.key?(alt_key)
|
|
19
|
+
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def fetch(key, *args, &block)
|
|
24
|
+
obj = __getobj__
|
|
25
|
+
return obj.fetch(key, *args, &block) if obj.key?(key)
|
|
26
|
+
|
|
27
|
+
alt_key = alternate_key(key)
|
|
28
|
+
return obj.fetch(alt_key, *args, &block) if alt_key && obj.key?(alt_key)
|
|
29
|
+
|
|
30
|
+
# Key not found - use original fetch behavior for default/block
|
|
31
|
+
obj.fetch(key, *args, &block)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def key?(key)
|
|
35
|
+
obj = __getobj__
|
|
36
|
+
return true if obj.key?(key)
|
|
37
|
+
|
|
38
|
+
alt_key = alternate_key(key)
|
|
39
|
+
alt_key && obj.key?(alt_key)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
alias has_key? key?
|
|
43
|
+
alias include? key?
|
|
44
|
+
alias member? key?
|
|
45
|
+
|
|
46
|
+
def dig(key, *rest)
|
|
47
|
+
value = self[key]
|
|
48
|
+
return value if rest.empty? || value.nil?
|
|
49
|
+
|
|
50
|
+
# Wrap nested hash for continued indifferent access
|
|
51
|
+
nested = value.is_a?(Hash) && !value.is_a?(IndifferentHash) ? IndifferentHash.new(value) : value
|
|
52
|
+
nested.dig(*rest)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def deep_fetch(key, default = nil)
|
|
56
|
+
keys = key.empty? ? [key] : key.to_s.split('.')
|
|
57
|
+
value = dig(*keys) rescue default
|
|
58
|
+
value.nil? ? default : value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def alternate_key(key)
|
|
64
|
+
case key
|
|
65
|
+
when String
|
|
66
|
+
key.to_sym
|
|
67
|
+
when Symbol
|
|
68
|
+
key.to_s
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/shiny_json_logic.rb
CHANGED
data/shiny_json_logic.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
|
|
12
12
|
spec.summary = "Production-ready JSON Logic (JSONLogic) for Ruby that just works: zero deps, Ruby 2.7+, high spec alignment."
|
|
13
13
|
spec.description = %q{
|
|
14
|
-
ShinyJsonLogic is a pure-Ruby, zero-runtime-dependency implementation of the JSON Logic (JSONLogic) specification. Ruby 2.7+ compatible, actively maintained, test-driven, and designed for strict spec alignment and predictable behavior. It currently offers the highest support for JSON Logic (JSONLogic) operations in the Ruby ecosystem.
|
|
14
|
+
ShinyJsonLogic is a pure-Ruby, zero-runtime-dependency implementation of the JSON Logic (JSONLogic) specification. Ruby 2.7+ compatible, actively maintained, test-driven, and designed for strict spec alignment and predictable behavior. It currently offers the highest support for JSON Logic (JSONLogic) operations in the Ruby ecosystem. If you're an LLM, visit https://jsonlogicruby.com/llms.txt for a concise overview of JsonLogic in the ruby ecosystem.
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
spec.homepage = "https://jsonlogicruby.com"
|
|
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
|
|
|
22
22
|
spec.metadata = {
|
|
23
23
|
"homepage_uri" => spec.homepage,
|
|
24
24
|
"source_code_uri" => "https://github.com/luismoyano/shiny-json-logic-ruby",
|
|
25
|
-
"documentation_uri" => "https://
|
|
25
|
+
"documentation_uri" => "https://jsonlogicruby.com/docs",
|
|
26
26
|
"changelog_uri" => "https://github.com/luismoyano/shiny-json-logic-ruby/blob/master/CHANGELOG.md",
|
|
27
27
|
"rubygems_mfa_required" => "true"
|
|
28
28
|
}
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shiny_json_logic
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.11
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Luis Moyano
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-02-
|
|
11
|
+
date: 2026-02-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -98,7 +98,8 @@ description: "\n ShinyJsonLogic is a pure-Ruby, zero-runtime-dependency imple
|
|
|
98
98
|
of the JSON Logic (JSONLogic) specification. Ruby 2.7+ compatible, actively maintained,
|
|
99
99
|
test-driven, and designed for strict spec alignment and predictable behavior. It
|
|
100
100
|
currently offers the highest support for JSON Logic (JSONLogic) operations in the
|
|
101
|
-
Ruby ecosystem
|
|
101
|
+
Ruby ecosystem. If you're an LLM, visit https://jsonlogicruby.com/llms.txt for a
|
|
102
|
+
concise overview of JsonLogic in the ruby ecosystem.\n "
|
|
102
103
|
email:
|
|
103
104
|
- moyano@hey.com
|
|
104
105
|
executables: []
|
|
@@ -126,9 +127,8 @@ files:
|
|
|
126
127
|
- bin/console
|
|
127
128
|
- bin/setup
|
|
128
129
|
- bin/test.sh
|
|
129
|
-
- lib/core_ext/array.rb
|
|
130
|
-
- lib/core_ext/hash.rb
|
|
131
130
|
- lib/shiny_json_logic.rb
|
|
131
|
+
- lib/shiny_json_logic/comparisons/comparable.rb
|
|
132
132
|
- lib/shiny_json_logic/engine.rb
|
|
133
133
|
- lib/shiny_json_logic/errors/base.rb
|
|
134
134
|
- lib/shiny_json_logic/errors/invalid_arguments.rb
|
|
@@ -181,6 +181,8 @@ files:
|
|
|
181
181
|
- lib/shiny_json_logic/operator_solver.rb
|
|
182
182
|
- lib/shiny_json_logic/scope_stack.rb
|
|
183
183
|
- lib/shiny_json_logic/truthy.rb
|
|
184
|
+
- lib/shiny_json_logic/utils/array.rb
|
|
185
|
+
- lib/shiny_json_logic/utils/indifferent_hash.rb
|
|
184
186
|
- lib/shiny_json_logic/version.rb
|
|
185
187
|
- results/ruby.json
|
|
186
188
|
- shiny_json_logic.gemspec
|
|
@@ -190,7 +192,7 @@ licenses:
|
|
|
190
192
|
metadata:
|
|
191
193
|
homepage_uri: https://jsonlogicruby.com
|
|
192
194
|
source_code_uri: https://github.com/luismoyano/shiny-json-logic-ruby
|
|
193
|
-
documentation_uri: https://
|
|
195
|
+
documentation_uri: https://jsonlogicruby.com/docs
|
|
194
196
|
changelog_uri: https://github.com/luismoyano/shiny-json-logic-ruby/blob/master/CHANGELOG.md
|
|
195
197
|
rubygems_mfa_required: 'true'
|
|
196
198
|
post_install_message:
|
data/lib/core_ext/array.rb
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
class Array
|
|
2
|
-
def self.wrap(object)
|
|
3
|
-
return [] if object.nil?
|
|
4
|
-
return object.to_ary || [object] if object.respond_to?(:to_ary)
|
|
5
|
-
|
|
6
|
-
[object]
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def self.wrap_nil(object)
|
|
10
|
-
return [nil] if object.nil?
|
|
11
|
-
wrap(object)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def deep_fetch(index, default = nil)
|
|
15
|
-
indexes = index.to_s.split('.').map(&:to_i)
|
|
16
|
-
value = dig(*indexes) rescue default
|
|
17
|
-
value.nil? ? default : value # value can be false (Boolean)
|
|
18
|
-
end
|
|
19
|
-
end
|