json_logic_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: 9b3d6d686007b6c7b9677f087f792802a19756964a3b2b234da07765d3c2623f
4
+ data.tar.gz: 449bddb8f9471d7f8090f9c0be0a8331ee224de27b927b039224c060d5d0b60a
5
+ SHA512:
6
+ metadata.gz: 5050c96999ce0016559b7a73f9b34ffef9b4bd2af8414b7145158c65536664c1a9c82d43439a1744167b41869c33cd150ece438abe654df6e70af75f5277ef66
7
+ data.tar.gz: 8b006202a2b17a4b48cbb08612a31b98d2e59c7adcfe9491c1ec6dd8f0e20f3a961e5804fb2ee0fe5cf6f1ab41f52f2d16841635600e789b6b39f661c21a6369
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+
3
+ ## 0.2.1 (2025-10-04)
4
+ - Rename gem to `json_logic_humanizable`
5
+ - Keep only Humanizable mixin and Expression class
6
+ - Improve docs with payments example and configuration details
data/CONTRIBUTION.md ADDED
@@ -0,0 +1,28 @@
1
+ # Contribution Guide
2
+
3
+ **Contributions are very welcome!** Whether it's a small typo fix, a new operator label, a better example, or a larger refactor — your help makes this gem better. If you're unsure where to start, open an issue and we can figure it out together.
4
+
5
+ ## Setup
6
+ - Ruby 2.7+
7
+ - No runtime dependencies
8
+
9
+ ## How to contribute
10
+ 1. Fork the repo and create a branch from `main`
11
+ 2. Make your change (code, docs, or tests)
12
+ 3. Include examples for any new operators or pretty mappings
13
+ 4. Update `README.md` if public behavior changes
14
+ 5. Run your tests
15
+ 6. Open a Pull Request and describe:
16
+ - What changed and why
17
+ - Any breaking impacts
18
+ - Before/after output if applicable
19
+
20
+ ## Code style
21
+ - Plain Ruby
22
+ - Public API stays stable:
23
+ - `JsonLogic::Rule.new(logic).humanize`
24
+ - `JsonLogic::Humanizable` mixin + `humanize_json_logic`
25
+
26
+ ## Releases
27
+ - Bump `lib/json_logic/version.rb` using SemVer
28
+ - Update `CHANGELOG.md`
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,139 @@
1
+
2
+
3
+ # json_logic_humanizable
4
+
5
+ **Translate JsonLogic rules into readable sentences**. Extension for [JsonLogic](https://jsonlogic.com/).
6
+
7
+
8
+ ## What
9
+
10
+ This gem converts JsonLogic Rules into readable sentences. It is framework-agnostic.
11
+ - A mixin for serializer/presenter classes.
12
+ - A small wrapper for one-off translations.
13
+
14
+ No dependencies. Works with a Ruby Hash or a Ruby Json string.
15
+
16
+ ## How
17
+
18
+ Input: a JsonLogic rule (as a Hash or JSON string).
19
+ Output: human-readable text that explains the JsonLogic Rule.
20
+
21
+ Where this is useful:
22
+
23
+ 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).
24
+
25
+ ## Installation
26
+
27
+ Add to your `Gemfile` if you need:
28
+
29
+ ```ruby
30
+ gem "json_logic_humanizable"
31
+ ```
32
+
33
+ Then install:
34
+
35
+ ```bash
36
+ bundle install
37
+ ```
38
+
39
+ ## Examples
40
+
41
+
42
+ ### Easy – Quick Start
43
+ ```ruby
44
+ logic = {
45
+ "and" => [
46
+ { ">=" => [ { "var" => "payment.amount" }, 50 ] },
47
+ { "in" => [ { "var" => "payment.currency" }, %w[EUR USD] ] }
48
+ ]
49
+ }
50
+
51
+ puts JsonLogic::Rule.new(logic).humanize
52
+ ```
53
+
54
+ ### Complex – With Config
55
+
56
+ ```ruby
57
+
58
+ JsonLogic::Config.operators["in"] = "is one of"
59
+
60
+ JsonLogic::Config.vars[/([^\.]+)$/] = ->(m) { m[1].tr("_", " ").split.map(&:capitalize).join(" ") }
61
+
62
+ logic = {
63
+ "and" => [
64
+ { ">=" => [ { "var" => "payment.amount" }, 50 ] },
65
+ { "fact" => "payment.currency", "operator" => "in", "value" => %w[EUR USD] },
66
+ {
67
+ "or" => [
68
+ { "in" => [ { "var" => "customer.country" }, %w[LT LV EE] ] },
69
+ { "==" => [ { "var" => "customer.vip" }, true ] }
70
+ ]
71
+ },
72
+ { "!" => { "==" => [ { "var" => "customer.blacklisted" }, true ] } },
73
+ { "<=" => [ { "var" => "risk.score" }, 300 ] },
74
+ { "fact" => "chargeback.history_count", "operator" => "<=", "value" => 1 }
75
+ ]
76
+ }
77
+
78
+ puts JsonLogic::Rule.new(logic).humanize
79
+ ```
80
+
81
+ Example output:
82
+ ```
83
+ Amount is greater than or equal to 50 AND Currency is one of ["EUR", "USD"] AND Country is one of LT, LV, EE OR Vip is equal to true AND NOT (Blacklisted is equal to true) AND Score is less than or equal to 300 AND History Count is less than or equal to 1
84
+ ```
85
+
86
+ ### Serializer example
87
+
88
+ ```ruby
89
+ class RuleSerializer
90
+ include JsonLogic::Humanizable
91
+
92
+ attr_reader :condition
93
+ humanize_json_logic :condition
94
+
95
+ def initialize(condition)
96
+ @condition = condition
97
+ end
98
+ end
99
+
100
+ s = RuleSerializer.new({
101
+ "and" => [
102
+ { ">=" => [ { "var" => "payment.amount" }, 50 ] },
103
+ { "in" => [ { "var" => "payment.currency" }, %w[EUR USD] ] }
104
+ ]
105
+ })
106
+
107
+ # RuleSerializer includes methods:
108
+ s.condition
109
+ s.condition_human
110
+ ```
111
+
112
+ ## Configuration
113
+
114
+ ### Operators mapping
115
+
116
+ Configure labels as you prefer:
117
+
118
+ ```ruby
119
+ JsonLogic::Config.operators["in"] = "is one of"
120
+ JsonLogic::Config.operators["=="] = "equals"
121
+ JsonLogic::Config.operators[">="] = "is at least"
122
+ JsonLogic::Config.operators["!"] = "NOT"
123
+ JsonLogic::Config.operators["and"] = "AND"
124
+ JsonLogic::Config.operators["or"] = "OR"
125
+ ```
126
+
127
+ ### Variables mapping
128
+
129
+ Each entry is `[pattern, replacement]` where `pattern` is a `Regexp` or exact `String`, and `replacement` is a `String` or `Proc`.
130
+
131
+ ```ruby
132
+ JsonLogic::Config.vars[/([^\.]+)$/] = ->(m) { m[1].tr("_", " ").split.map(&:capitalize).join(" ") }
133
+ ```
134
+
135
+ This maps `payment.amount` → `Amount`, `customer.country` → `Country`, etc.
136
+
137
+ ## Links
138
+
139
+ - 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).to_s
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,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: json_logic_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-04 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
+ - CONTRIBUTION.md
22
+ - LICENSE
23
+ - README.md
24
+ - lib/json_logic.rb
25
+ - lib/json_logic/config.rb
26
+ - lib/json_logic/humanizable.rb
27
+ - lib/json_logic/rule.rb
28
+ - lib/json_logic/version.rb
29
+ homepage:
30
+ licenses:
31
+ - MIT
32
+ metadata: {}
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '2.7'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubygems_version: 3.5.22
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: Translate JsonLogic rules into readable sentences.
52
+ test_files: []