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 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
@@ -0,0 +1,4 @@
1
+ # Changelog
2
+
3
+ ## 0.0.1 (2025-10-05)
4
+ - First stable release
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
+ [![Gem Version](https://img.shields.io/gem/v/json_logic_humanizable.svg)](https://rubygems.org/gems/json_logic_humanizable) [![Docs](https://img.shields.io/badge/docs-rubydoc.info-blue)](https://www.rubydoc.info/gems/json_logic_humanizable) [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonLogic
4
+ VERSION = "0.0.1.beta1"
5
+ end
data/lib/json_logic.rb ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "json_logic/version"
4
+ require_relative "json_logic/config"
5
+ require_relative "json_logic/humanizable"
6
+ require_relative "json_logic/rule"
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: []