shiny_json_logic 0.3.3 → 0.3.4
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 +4 -0
- data/Gemfile.lock +1 -1
- data/lib/shiny_json_logic/comparisons/comparable.rb +6 -3
- data/lib/shiny_json_logic/engine.rb +3 -2
- data/lib/shiny_json_logic/numericals/min_max_collection.rb +2 -5
- data/lib/shiny_json_logic/numericals/with_error_handling.rb +1 -1
- data/lib/shiny_json_logic/operations/if.rb +7 -3
- data/lib/shiny_json_logic/operations/iterable/base.rb +3 -1
- data/lib/shiny_json_logic/operations/missing.rb +13 -2
- data/lib/shiny_json_logic/operations/reduce.rb +7 -2
- data/lib/shiny_json_logic/operations/strict_different.rb +6 -3
- data/lib/shiny_json_logic/operations/strict_equal.rb +6 -3
- data/lib/shiny_json_logic/operations/val.rb +7 -3
- data/lib/shiny_json_logic/operator_solver.rb +4 -4
- data/lib/shiny_json_logic/scope_stack.rb +12 -13
- data/lib/shiny_json_logic/truthy.rb +6 -7
- data/lib/shiny_json_logic/utils/array.rb +2 -0
- data/lib/shiny_json_logic/utils/indifferent_hash.rb +72 -0
- data/lib/shiny_json_logic/version.rb +1 -1
- data/shiny_json_logic.gemspec +3 -3
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bf0275f81efb49af13061d1c54c2dc273fb4686ad647a43f3ba4eafb069176f5
|
|
4
|
+
data.tar.gz: d411aa52d5d3a235b5a9c783909acac800e10a620e0294f572ccbca9d7421223
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a926de52b731c63259ba2a6a954e35dd0d94c133d78cfb4bfe8c7d862cf8f5d5f9e6a33ad8b9d63028d9cc9cfa97473f6fca92683c27a219bc5838bcd3d626eb
|
|
7
|
+
data.tar.gz: 112ec835a660537716dd1c94cfa9c152584296a44b857d0161ac3a2dd69c6f2440fd2ccf15542907f2b7a83aeb38a99acce949ad06fb55be62ff20d4687ec55a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
|
+
## [0.3.4] - 2026-03-06
|
|
5
|
+
### Changed
|
|
6
|
+
- Reduces object allocations in hot paths for improved performance.
|
|
7
|
+
|
|
4
8
|
## [0.3.3] - 2026-03-06
|
|
5
9
|
### Changed
|
|
6
10
|
- Refactors internal architecture to lookup operations with a helper instead of running a normalization pass before calculations, thus improving performance a lot.
|
data/Gemfile.lock
CHANGED
|
@@ -42,15 +42,18 @@ module ShinyJsonLogic
|
|
|
42
42
|
# Returns true if all pairs pass, false otherwise. Raises on :nan or invalid args.
|
|
43
43
|
def compare_chain(rules, scope_stack)
|
|
44
44
|
operands = Utils::Array.wrap_nil(rules)
|
|
45
|
-
|
|
45
|
+
n = operands.length
|
|
46
|
+
raise Errors::InvalidArguments if n < 2
|
|
46
47
|
|
|
47
48
|
prev = Engine.call(operands[0], scope_stack)
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
i = 1
|
|
50
|
+
while i < n
|
|
51
|
+
curr = Engine.call(operands[i], scope_stack)
|
|
50
52
|
result = compare(prev, curr)
|
|
51
53
|
raise Errors::NotANumber if result == :nan
|
|
52
54
|
return false unless yield(result)
|
|
53
55
|
prev = curr
|
|
56
|
+
i += 1
|
|
54
57
|
end
|
|
55
58
|
true
|
|
56
59
|
end
|
|
@@ -15,8 +15,9 @@ module ShinyJsonLogic
|
|
|
15
15
|
|
|
16
16
|
raise Errors::UnknownOperator if rule.size > 1
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
operation_key = nil
|
|
19
|
+
args = nil
|
|
20
|
+
rule.each { |k, v| operation_key = k.to_s; args = v }
|
|
20
21
|
|
|
21
22
|
op = OPERATIONS[operation_key]
|
|
22
23
|
raise Errors::UnknownOperator unless op
|
|
@@ -20,11 +20,8 @@ module ShinyJsonLogic
|
|
|
20
20
|
return Utils::Array.wrap_nil(evaluated)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
result << Engine.call(rule, scope_stack)
|
|
26
|
-
end
|
|
27
|
-
result
|
|
23
|
+
wrapped = Utils::Array.wrap_nil(rules)
|
|
24
|
+
wrapped.map { |rule| Engine.call(rule, scope_stack) }
|
|
28
25
|
end
|
|
29
26
|
end
|
|
30
27
|
end
|
|
@@ -13,13 +13,17 @@ module ShinyJsonLogic
|
|
|
13
13
|
# Skip pre_process - spec requires static array, dynamic args should error
|
|
14
14
|
return handle_invalid_args unless rules.is_a?(Array)
|
|
15
15
|
|
|
16
|
-
rules.
|
|
16
|
+
n = rules.length
|
|
17
|
+
i = 0
|
|
18
|
+
while i < n
|
|
19
|
+
condition_rule = rules[i]
|
|
20
|
+
value_rule = rules[i + 1]
|
|
17
21
|
condition_result = Engine.call(condition_rule, scope_stack)
|
|
18
22
|
return condition_result if value_rule.nil?
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
return Engine.call(value_rule, scope_stack) if Truthy.call(condition_result)
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
i += 2
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
nil
|
|
@@ -19,9 +19,11 @@ module ShinyJsonLogic
|
|
|
19
19
|
|
|
20
20
|
collection, filter = setup_collection(rules, scope_stack)
|
|
21
21
|
|
|
22
|
+
index_scope = { "index" => 0 }
|
|
22
23
|
on_before(scope_stack)
|
|
23
24
|
results = collection.each_with_index.each_with_object([]) do |(item, index), acc|
|
|
24
|
-
|
|
25
|
+
index_scope["index"] = index
|
|
26
|
+
scope_stack.push(index_scope, index: index)
|
|
25
27
|
scope_stack.push(item, index: index)
|
|
26
28
|
begin
|
|
27
29
|
solved = on_each(item, filter, scope_stack)
|
|
@@ -19,10 +19,21 @@ module ShinyJsonLogic
|
|
|
19
19
|
keys - deep_keys(current_data)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def self.deep_keys(hash)
|
|
22
|
+
def self.deep_keys(hash, prefix = nil)
|
|
23
23
|
return unless hash.is_a?(Hash)
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
result = []
|
|
26
|
+
hash.each do |key, val|
|
|
27
|
+
key_s = key.to_s
|
|
28
|
+
full_key = prefix ? "#{prefix}.#{key_s}" : key_s
|
|
29
|
+
nested = deep_keys(val, full_key)
|
|
30
|
+
if nested
|
|
31
|
+
result.concat(nested)
|
|
32
|
+
else
|
|
33
|
+
result << full_key
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
result
|
|
26
37
|
end
|
|
27
38
|
private_class_method :deep_keys
|
|
28
39
|
end
|
|
@@ -18,9 +18,14 @@ module ShinyJsonLogic
|
|
|
18
18
|
# Evaluate initial accumulator (third argument)
|
|
19
19
|
accumulator = Engine.call(rules[2], scope_stack)
|
|
20
20
|
|
|
21
|
+
index_scope = { "index" => 0 }
|
|
22
|
+
reduce_scope = { "current" => nil, "accumulator" => nil }
|
|
23
|
+
|
|
21
24
|
collection.each_with_index do |item, index|
|
|
22
|
-
|
|
23
|
-
reduce_scope
|
|
25
|
+
index_scope["index"] = index
|
|
26
|
+
reduce_scope["current"] = item
|
|
27
|
+
reduce_scope["accumulator"] = accumulator
|
|
28
|
+
scope_stack.push(index_scope, index: index)
|
|
24
29
|
scope_stack.push(reduce_scope, index: index)
|
|
25
30
|
begin
|
|
26
31
|
accumulator = Engine.call(filter, scope_stack)
|
|
@@ -12,13 +12,16 @@ module ShinyJsonLogic
|
|
|
12
12
|
|
|
13
13
|
def self.execute(rules, scope_stack)
|
|
14
14
|
operands = Utils::Array.wrap_nil(rules)
|
|
15
|
-
|
|
15
|
+
n = operands.length
|
|
16
|
+
return handle_invalid_args if n < 2
|
|
16
17
|
|
|
17
18
|
prev = Comparisons::Comparable.cast(evaluate(operands[0], scope_stack))
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
i = 1
|
|
20
|
+
while i < n
|
|
21
|
+
curr = Comparisons::Comparable.cast(evaluate(operands[i], scope_stack))
|
|
20
22
|
return false if curr == prev
|
|
21
23
|
prev = curr
|
|
24
|
+
i += 1
|
|
22
25
|
end
|
|
23
26
|
true
|
|
24
27
|
end
|
|
@@ -12,11 +12,14 @@ module ShinyJsonLogic
|
|
|
12
12
|
|
|
13
13
|
def self.execute(rules, scope_stack)
|
|
14
14
|
operands = Utils::Array.wrap_nil(rules)
|
|
15
|
-
|
|
15
|
+
n = operands.length
|
|
16
|
+
return handle_invalid_args if n < 2
|
|
16
17
|
|
|
17
18
|
first = Comparisons::Comparable.cast(evaluate(operands[0], scope_stack))
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
i = 1
|
|
20
|
+
while i < n
|
|
21
|
+
return false unless Comparisons::Comparable.cast(evaluate(operands[i], scope_stack)) == first
|
|
22
|
+
i += 1
|
|
20
23
|
end
|
|
21
24
|
true
|
|
22
25
|
end
|
|
@@ -20,9 +20,13 @@ module ShinyJsonLogic
|
|
|
20
20
|
|
|
21
21
|
if first_key.is_a?(Array) && scope_stack
|
|
22
22
|
level_indicator = first_key.first.to_i
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
evaluated_keys = []
|
|
24
|
+
i = 1
|
|
25
|
+
n = raw_keys.length
|
|
26
|
+
while i < n
|
|
27
|
+
evaluated_keys << evaluate(raw_keys[i], scope_stack)
|
|
28
|
+
i += 1
|
|
29
|
+
end
|
|
26
30
|
|
|
27
31
|
levels = level_indicator.abs
|
|
28
32
|
return Utils::DataHash.wrap(scope_stack.resolve(levels, *evaluated_keys))
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
3
5
|
Dir[File.join(__dir__, "operations/**/*.rb")].each do |file|
|
|
4
6
|
require file
|
|
5
7
|
end
|
|
@@ -49,12 +51,10 @@ module ShinyJsonLogic
|
|
|
49
51
|
"preserve" => Operations::Preserve,
|
|
50
52
|
}.freeze
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
SOLVERS
|
|
54
|
-
end
|
|
54
|
+
SOLVER_KEYS = Set.new(SOLVERS.keys).freeze
|
|
55
55
|
|
|
56
56
|
def self.operation?(value)
|
|
57
|
-
value.keys.any? { |key|
|
|
57
|
+
value.keys.any? { |key| SOLVER_KEYS.include?(key.is_a?(String) ? key : key.to_s) }
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
end
|
|
@@ -19,41 +19,40 @@ module ShinyJsonLogic
|
|
|
19
19
|
# so no indifferent access is needed here.
|
|
20
20
|
#
|
|
21
21
|
class ScopeStack
|
|
22
|
-
attr_reader :stack
|
|
23
|
-
|
|
24
22
|
def initialize(root_data)
|
|
25
|
-
@
|
|
26
|
-
@
|
|
23
|
+
@data_stack = [root_data]
|
|
24
|
+
@index_stack = [0]
|
|
27
25
|
end
|
|
28
26
|
|
|
29
27
|
# Push a new scope onto the stack (when entering an iteration)
|
|
30
28
|
def push(data, index: 0)
|
|
31
|
-
|
|
29
|
+
@data_stack << data
|
|
30
|
+
@index_stack << index
|
|
32
31
|
end
|
|
33
32
|
|
|
34
33
|
# Pop the top scope (when exiting an iteration)
|
|
35
34
|
def pop
|
|
36
|
-
|
|
35
|
+
if @data_stack.size > 1
|
|
36
|
+
@data_stack.pop
|
|
37
|
+
@index_stack.pop
|
|
38
|
+
end
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
# Returns the current scope's data (top of stack)
|
|
40
42
|
def current
|
|
41
|
-
|
|
43
|
+
@data_stack.last
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
# Resolve a value by going up n levels and then accessing keys
|
|
45
|
-
#
|
|
47
|
+
#
|
|
46
48
|
# @param levels [Integer] number of levels to go up (0 = current, 1 = parent, etc.)
|
|
47
49
|
# @param keys [Array] keys to dig into after reaching the target scope
|
|
48
50
|
# @return [Object] the resolved value
|
|
49
51
|
def resolve(levels, *keys)
|
|
50
|
-
target_index =
|
|
52
|
+
target_index = @data_stack.size - 1 - levels
|
|
51
53
|
return nil if target_index < 0
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
return nil unless scope
|
|
55
|
-
|
|
56
|
-
data = scope[0]
|
|
55
|
+
data = @data_stack[target_index]
|
|
57
56
|
|
|
58
57
|
if keys.empty?
|
|
59
58
|
data
|
|
@@ -6,13 +6,12 @@ module ShinyJsonLogic
|
|
|
6
6
|
module Truthy
|
|
7
7
|
def self.call(subject)
|
|
8
8
|
case subject
|
|
9
|
-
when true, false
|
|
10
|
-
when Numeric
|
|
11
|
-
when String
|
|
12
|
-
when Array
|
|
13
|
-
when
|
|
14
|
-
|
|
15
|
-
else true
|
|
9
|
+
when true, false then subject
|
|
10
|
+
when Numeric then !subject.zero?
|
|
11
|
+
when String, Hash then !subject.empty?
|
|
12
|
+
when Array then subject.any?
|
|
13
|
+
when NilClass then false
|
|
14
|
+
else true
|
|
16
15
|
end
|
|
17
16
|
end
|
|
18
17
|
end
|
|
@@ -7,6 +7,7 @@ module ShinyJsonLogic
|
|
|
7
7
|
|
|
8
8
|
def wrap(object)
|
|
9
9
|
return [] if object.nil?
|
|
10
|
+
return object if object.is_a?(::Array)
|
|
10
11
|
return object.to_ary || [object] if object.respond_to?(:to_ary)
|
|
11
12
|
|
|
12
13
|
[object]
|
|
@@ -14,6 +15,7 @@ module ShinyJsonLogic
|
|
|
14
15
|
|
|
15
16
|
def wrap_nil(object)
|
|
16
17
|
return [nil] if object.nil?
|
|
18
|
+
return object if object.is_a?(::Array)
|
|
17
19
|
|
|
18
20
|
wrap(object)
|
|
19
21
|
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/shiny_json_logic.gemspec
CHANGED
|
@@ -8,15 +8,15 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.version = ShinyJsonLogic::VERSION
|
|
9
9
|
spec.authors = ["Luis Moyano"]
|
|
10
10
|
spec.email = [""]
|
|
11
|
-
spec.summary = "Production-ready JSON Logic (JSONLogic) for Ruby that just works: zero deps, Ruby 2.
|
|
11
|
+
spec.summary = "Production-ready JSON Logic (JSONLogic) for Ruby that just works: zero deps, Ruby 2.4+, full spec alignment (100%)."
|
|
12
12
|
spec.description = %q{
|
|
13
|
-
ShinyJsonLogic is a pure-Ruby, zero-runtime-dependency implementation of the JSON Logic (JSONLogic) specification. Ruby 2.
|
|
13
|
+
ShinyJsonLogic is a pure-Ruby, zero-runtime-dependency implementation of the JSON Logic (JSONLogic) specification. Ruby 2.4+ 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.
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
spec.homepage = "https://jsonlogicruby.com"
|
|
17
17
|
spec.license = "MIT"
|
|
18
18
|
|
|
19
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 2.
|
|
19
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
|
|
20
20
|
|
|
21
21
|
spec.metadata = {
|
|
22
22
|
"homepage_uri" => spec.homepage,
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shiny_json_logic
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Luis Moyano
|
|
@@ -95,7 +95,7 @@ dependencies:
|
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
96
|
version: '0'
|
|
97
97
|
description: "\n ShinyJsonLogic is a pure-Ruby, zero-runtime-dependency implementation
|
|
98
|
-
of the JSON Logic (JSONLogic) specification. Ruby 2.
|
|
98
|
+
of the JSON Logic (JSONLogic) specification. Ruby 2.4+ 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
101
|
Ruby ecosystem. If you're an LLM, visit https://jsonlogicruby.com/llms.txt for a
|
|
@@ -179,6 +179,7 @@ files:
|
|
|
179
179
|
- lib/shiny_json_logic/utils/array.rb
|
|
180
180
|
- lib/shiny_json_logic/utils/data_hash.rb
|
|
181
181
|
- lib/shiny_json_logic/utils/hash_fetch.rb
|
|
182
|
+
- lib/shiny_json_logic/utils/indifferent_hash.rb
|
|
182
183
|
- lib/shiny_json_logic/version.rb
|
|
183
184
|
- results/ruby.json
|
|
184
185
|
- shiny_json_logic.gemspec
|
|
@@ -199,7 +200,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
199
200
|
requirements:
|
|
200
201
|
- - ">="
|
|
201
202
|
- !ruby/object:Gem::Version
|
|
202
|
-
version: 2.
|
|
203
|
+
version: 2.4.0
|
|
203
204
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
204
205
|
requirements:
|
|
205
206
|
- - ">="
|
|
@@ -210,5 +211,5 @@ rubygems_version: 3.1.6
|
|
|
210
211
|
signing_key:
|
|
211
212
|
specification_version: 4
|
|
212
213
|
summary: 'Production-ready JSON Logic (JSONLogic) for Ruby that just works: zero deps,
|
|
213
|
-
Ruby 2.
|
|
214
|
+
Ruby 2.4+, full spec alignment (100%).'
|
|
214
215
|
test_files: []
|