json-logic-rb-humanizable 0.0.1.beta1
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 +7 -0
- data/CHANGELOG.md +4 -0
- data/LICENSE +21 -0
- data/README.md +162 -0
- data/lib/json_logic/config.rb +24 -0
- data/lib/json_logic/humanizable.rb +103 -0
- data/lib/json_logic/rule.rb +33 -0
- data/lib/json_logic/version.rb +5 -0
- data/lib/json_logic.rb +6 -0
- metadata +56 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4585919e62954c6b514f1e0fc49d5791f67c862371ff6af1a39c3c42b6f11360
|
4
|
+
data.tar.gz: ad6b47848ed5f066b84158563ba1cb24dab8276557f918b0db42634775aa10ea
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 20713639565afa699738180627be24bc56d123401235acaf1d45864944652514f6190d74ecc6e0cc08941069837d6d8a66f88f2c36107392a725189d10014be7
|
7
|
+
data.tar.gz: 99e961177dafc594b9ce1a1eff29f65e1448af5b7434bda7c7a307a78104d12045c7cd766e376c1458df8ae92c734215286586f37bbb5e1892bd2e520f7e4f95
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
|
2
|
+
# json-logic-rb-humanizable
|
3
|
+
|
4
|
+
Translate [JsonLogic](https://jsonlogic.com/) rules into readable sentences. Extension for [JsonLogic](https://jsonlogic.com/).
|
5
|
+
|
6
|
+
⚙️ Zero deps · 🛠️ Configurable · 🧩 Mixin + Wrapper
|
7
|
+
|
8
|
+
[](https://rubygems.org/gems/json_logic_humanizable) [](https://www.rubydoc.info/gems/json_logic_humanizable) [](LICENSE)
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
## What
|
13
|
+
|
14
|
+
This gem converts JsonLogic Rules into readable sentences.
|
15
|
+
- A mixin for serializer/presenter classes.
|
16
|
+
- A small wrapper for one-off translations.
|
17
|
+
|
18
|
+
No dependencies. Works with a Ruby Hash.
|
19
|
+
|
20
|
+
## How
|
21
|
+
|
22
|
+
Config => operators map + variables map
|
23
|
+
Input => JsonLogic Rule (Hash)
|
24
|
+
Output => human-readable sentence
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
## Where
|
29
|
+
|
30
|
+
If you found this gem, you likely already know where to use it in your app — the abstract use case is simple: you want to read a Rule as text (e.g., show it in the UI, preview it, or share it elsewhere).
|
31
|
+
|
32
|
+
## Installation
|
33
|
+
|
34
|
+
### Plain Ruby
|
35
|
+
```bash
|
36
|
+
gem install json_logic_humanizable
|
37
|
+
```
|
38
|
+
|
39
|
+
### Rails (Bundler)
|
40
|
+
Add to your `Gemfile` if you need:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
gem "json_logic_humanizable"
|
44
|
+
```
|
45
|
+
|
46
|
+
Then install:
|
47
|
+
|
48
|
+
```bash
|
49
|
+
bundle install
|
50
|
+
```
|
51
|
+
|
52
|
+
Configuration (if needed)
|
53
|
+
```ruby
|
54
|
+
# config/initializers/json_logic.rb
|
55
|
+
|
56
|
+
require "json_logic"
|
57
|
+
|
58
|
+
# Configuration
|
59
|
+
```
|
60
|
+
|
61
|
+
## Examples
|
62
|
+
|
63
|
+
|
64
|
+
### Easy – Quick Start
|
65
|
+
```ruby
|
66
|
+
logic = {
|
67
|
+
"and" => [
|
68
|
+
{ ">=" => [ { "var" => "payment.amount" }, 50 ] },
|
69
|
+
{ "in" => [ { "var" => "payment.currency" }, %w[EUR USD] ] }
|
70
|
+
]
|
71
|
+
}
|
72
|
+
|
73
|
+
puts JsonLogic::Rule.new(logic).humanize
|
74
|
+
```
|
75
|
+
|
76
|
+
Example output:
|
77
|
+
```
|
78
|
+
payment.amount is greater than or equal to 50 AND payment.currency is in EUR, USD
|
79
|
+
```
|
80
|
+
|
81
|
+
### Complex – With Config
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
|
85
|
+
JsonLogic::Config.operators["in"] = "is one of"
|
86
|
+
|
87
|
+
JsonLogic::Config.vars[/([^\.]+)$/] = ->(m) { m[1].tr("_", " ").split.map(&:capitalize).join(" ") }
|
88
|
+
|
89
|
+
logic = {
|
90
|
+
"and" => [
|
91
|
+
{ ">=" => [ { "var" => "payment.amount" }, 50 ] },
|
92
|
+
{ "fact" => "payment.currency", "operator" => "in", "value" => %w[EUR USD] }
|
93
|
+
]
|
94
|
+
}
|
95
|
+
|
96
|
+
puts JsonLogic::Rule.new(logic).humanize
|
97
|
+
```
|
98
|
+
|
99
|
+
Example output:
|
100
|
+
```
|
101
|
+
Amount is greater than or equal to 50 AND Currency is one of ["EUR", "USD"]
|
102
|
+
```
|
103
|
+
|
104
|
+
### Mixin – Humanizable in serializers
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
class RuleSerializer
|
108
|
+
include JsonLogic::Humanizable
|
109
|
+
|
110
|
+
attr_reader :json_logic
|
111
|
+
|
112
|
+
def initialize(json_logic)
|
113
|
+
@json_logic = json_logic
|
114
|
+
end
|
115
|
+
|
116
|
+
def condition
|
117
|
+
humanize_logic(json_logic)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
s = RuleSerializer.new({
|
122
|
+
"and" => [
|
123
|
+
{ ">=" => [ { "var" => "payment.amount" }, 50 ] },
|
124
|
+
{ "in" => [ { "var" => "payment.currency" }, %w[EUR USD] ] }
|
125
|
+
]
|
126
|
+
})
|
127
|
+
|
128
|
+
puts s.condition
|
129
|
+
```
|
130
|
+
Example output:
|
131
|
+
```
|
132
|
+
payment.amount is greater than or equal to 50 AND payment.currency is in EUR, USD
|
133
|
+
```
|
134
|
+
|
135
|
+
## Configuration
|
136
|
+
|
137
|
+
### Operators mapping
|
138
|
+
|
139
|
+
Configure labels as you prefer:
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
JsonLogic::Config.operators["in"] = "is one of"
|
143
|
+
JsonLogic::Config.operators["=="] = "equals"
|
144
|
+
JsonLogic::Config.operators[">="] = "is at least"
|
145
|
+
JsonLogic::Config.operators["!"] = "NOT"
|
146
|
+
JsonLogic::Config.operators["and"] = "AND"
|
147
|
+
JsonLogic::Config.operators["or"] = "OR"
|
148
|
+
```
|
149
|
+
|
150
|
+
### Variables mapping
|
151
|
+
|
152
|
+
Each entry is key – **pattern**, value – **replacement**, where **pattern** is a `Regexp` or exact `String`, and **replacement** is a `String` or `Proc`.
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
JsonLogic::Config.vars[/([^\.]+)$/] = ->(m) { m[1].tr("_", " ").split.map(&:capitalize).join(" ") }
|
156
|
+
```
|
157
|
+
|
158
|
+
This maps `payment.amount` → `Amount`, `customer.country` → `Country`, etc.
|
159
|
+
|
160
|
+
## Links
|
161
|
+
|
162
|
+
- JsonLogic site: https://jsonlogic.com/
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JsonLogic
|
4
|
+
module Config
|
5
|
+
class << self
|
6
|
+
attr_accessor :vars, :operators
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
Config.vars = {}
|
11
|
+
|
12
|
+
Config.operators = {
|
13
|
+
"==" => "is equal to",
|
14
|
+
"!=" => "is not equal to",
|
15
|
+
">" => "is greater than",
|
16
|
+
"<" => "is less than",
|
17
|
+
">=" => "is greater than or equal to",
|
18
|
+
"<=" => "is less than or equal to",
|
19
|
+
"in" => "is in",
|
20
|
+
"!" => "NOT",
|
21
|
+
"and"=> "AND",
|
22
|
+
"or" => "OR"
|
23
|
+
}
|
24
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JsonLogic
|
4
|
+
module Humanizable
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def humanize_json_logic(*attrs, as: nil, suffix: "_human")
|
11
|
+
attrs.each do |attr|
|
12
|
+
method_name = (as || "#{attr}#{suffix}").to_sym
|
13
|
+
define_method(method_name) do
|
14
|
+
value = public_send(attr)
|
15
|
+
humanize_logic(value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def humanize_logic(value)
|
22
|
+
return nil if value.nil?
|
23
|
+
obj = value.is_a?(String) ? parse_json(value) : value
|
24
|
+
explain(obj)
|
25
|
+
rescue JSON::ParserError
|
26
|
+
value
|
27
|
+
end
|
28
|
+
|
29
|
+
def humanize(value)
|
30
|
+
humanize_logic(value)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def parse_json(str)
|
36
|
+
require "json"
|
37
|
+
JSON.parse(str)
|
38
|
+
end
|
39
|
+
|
40
|
+
AND_OPERATORS = %w[and all].freeze
|
41
|
+
OR_OPERATORS = %w[or any].freeze
|
42
|
+
LOGIC_OPERATORS = (AND_OPERATORS + OR_OPERATORS).freeze
|
43
|
+
NOT_OPERATOR = "!"
|
44
|
+
IN_OPERATOR = "in"
|
45
|
+
VARIABLE_OPERATOR = "var"
|
46
|
+
FACT_OPERATOR = "fact"
|
47
|
+
|
48
|
+
def explain(logic)
|
49
|
+
return logic.to_s unless logic.is_a?(Hash)
|
50
|
+
operator, operands = logic.first
|
51
|
+
operands ||= []
|
52
|
+
case operator
|
53
|
+
when *LOGIC_OPERATORS
|
54
|
+
joiner = label(normalize(operator))
|
55
|
+
operands.map { |operand| explain(operand) rescue operand.inspect }.join(" #{joiner} ")
|
56
|
+
when NOT_OPERATOR
|
57
|
+
"#{label(NOT_OPERATOR)} (#{explain(operands)})"
|
58
|
+
when IN_OPERATOR
|
59
|
+
variable, list = operands
|
60
|
+
"#{pretty(variable)} #{label(IN_OPERATOR)} #{Array(list).join(', ')}"
|
61
|
+
when VARIABLE_OPERATOR
|
62
|
+
pretty(operands)
|
63
|
+
when FACT_OPERATOR
|
64
|
+
"#{pretty(operands)} #{label(logic['operator'])} #{logic['value']}"
|
65
|
+
else
|
66
|
+
binary(operator, operands) || logic.inspect
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def binary(operator, operands)
|
71
|
+
return unless valid_operands?(operands)
|
72
|
+
"#{pretty(operands.first)} #{label(operator)} #{operands[1]}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def valid_operands?(operands)
|
76
|
+
operands.is_a?(Array) && operands.size == 2 && operands.first.is_a?(Hash) && operands.first[VARIABLE_OPERATOR]
|
77
|
+
end
|
78
|
+
|
79
|
+
def normalize(operator)
|
80
|
+
return "and" if AND_OPERATORS.include?(operator)
|
81
|
+
return "or" if OR_OPERATORS.include?(operator)
|
82
|
+
operator
|
83
|
+
end
|
84
|
+
|
85
|
+
def pretty(expression)
|
86
|
+
extract_variable_name(expression).tap do |base|
|
87
|
+
Array(Config.vars).each do |pattern, replacement|
|
88
|
+
if pattern.is_a?(Regexp) ? (match = pattern.match(base)) : base == pattern.to_s
|
89
|
+
return replacement.is_a?(Proc) ? replacement.call(match || base) : replacement
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def extract_variable_name(expression)
|
96
|
+
expression.is_a?(Hash) && expression[VARIABLE_OPERATOR] ? expression[VARIABLE_OPERATOR].to_s : expression.to_s
|
97
|
+
end
|
98
|
+
|
99
|
+
def label(operator)
|
100
|
+
Config.operators&.[](operator) || operator
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "humanizable"
|
4
|
+
|
5
|
+
module JsonLogic
|
6
|
+
class Rule
|
7
|
+
include Humanizable
|
8
|
+
|
9
|
+
attr_reader :logic
|
10
|
+
|
11
|
+
def initialize(value)
|
12
|
+
@logic = value
|
13
|
+
end
|
14
|
+
|
15
|
+
humanize_json_logic :logic
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
humanize_logic(@logic)
|
19
|
+
end
|
20
|
+
|
21
|
+
def humanize
|
22
|
+
to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def call(value)
|
27
|
+
new(value).humanize
|
28
|
+
end
|
29
|
+
alias [] call
|
30
|
+
alias text call
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/json_logic.rb
ADDED
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: json-logic-rb-humanizable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.beta1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tavrel Kate
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-10-06 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: An extension over JsonLogic that translates Rules to human text via a
|
14
|
+
mixin and a small Rule wrapper.
|
15
|
+
email:
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- CHANGELOG.md
|
21
|
+
- LICENSE
|
22
|
+
- README.md
|
23
|
+
- lib/json_logic.rb
|
24
|
+
- lib/json_logic/config.rb
|
25
|
+
- lib/json_logic/humanizable.rb
|
26
|
+
- lib/json_logic/rule.rb
|
27
|
+
- lib/json_logic/version.rb
|
28
|
+
homepage: https://github.com/tavrelkate/json_logic_humanizable
|
29
|
+
licenses:
|
30
|
+
- MIT
|
31
|
+
metadata:
|
32
|
+
documentation_uri: https://www.rubydoc.info/gems/json_logic_humanizable
|
33
|
+
homepage_uri: https://github.com/tavrelkate/json_logic_humanizable
|
34
|
+
source_code_uri: https://github.com/tavrelkate/json_logic_humanizable
|
35
|
+
changelog_uri: https://github.com/tavrelkate/json_logic_humanizable/blob/main/CHANGELOG.md
|
36
|
+
bug_tracker_uri: https://github.com/tavrelkate/json_logic_humanizable/issues
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '2.7'
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubygems_version: 3.5.22
|
53
|
+
signing_key:
|
54
|
+
specification_version: 4
|
55
|
+
summary: Translate JsonLogic rules into readable sentences.
|
56
|
+
test_files: []
|