dmn 0.0.3 → 0.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83ab9c509d78447fc0dcf11d993e79c9105baf97904af53e932e16ae7eaee2df
4
- data.tar.gz: be469b68cdeab12f0ffee64e6a1c884ee9b67d10e2d9b899f1f3ecfa7905a975
3
+ metadata.gz: 82afe806c546d827f5629bbf843320de290af2c0ab983054a7abb2ddbf90835a
4
+ data.tar.gz: dd43fd946d1506a3b80dd1634a8e6045051bae58ce6acf5786c6bf630fd57569
5
5
  SHA512:
6
- metadata.gz: a5b1b6a24a4c6d2f305d41e94ac83bcb951f1dbfe8fc4ddccc6d5675ee5f3af70529778f288db5c7dea989a5ef7af685250ad6b7abd5dea7b80bef1afe20d019
7
- data.tar.gz: 62e2cecb612379245854e09684e1632ae6fb670d0618577685d00e92c944aaf44c9e8a5e59173372280a5887879a190603bd1ec47e07f34bebb9e533f91d9a3b
6
+ metadata.gz: e2e1664964bff590b8b82658db0fe289a2be1b6bfd56b19f8645bdf3baba51e5d722bc3972fda9ef597327ae495ad2f288188b1bf7be38112cc374935843f75c
7
+ data.tar.gz: 2384486276b03d18280066ab756026b9e11a9caa2a0a784b7b5829383a86b85955fc7172fea3ffef72b0d62525fb8e05d37207cb21177ec241b6a02fec2de9f5
data/README.md CHANGED
@@ -1,69 +1,11 @@
1
- # Spot Feel
1
+ # DMN
2
2
 
3
- A light-weight DMN FEEL expression evaluator and business rule engine in Ruby.
3
+ A light-weight DMN (Decision Model and Notation) business rule ruby gem.
4
4
 
5
- This gem implements a subset of FEEL (Friendly Enough Expression Language) as defined in the [DMN 1.3 specification](https://www.omg.org/spec/DMN/1.3/PDF) with some additional extensions.
6
-
7
- FEEL expressions are parsed into an abstract syntax tree (AST) and then evaluated in a context. The context is a hash of variables and functions to be resolved inside the expression.
8
-
9
- Expressions are safe, side-effect free, and deterministic. They are ideal for capturing business logic for storage in a database or embedded in DMN, BPMN, or Form documents for execution in a workflow engine like [Spot Flow](https://github.com/connectedbits/spot-flow).
10
-
11
- This project was inspired by these excellent libraries:
12
-
13
- - [feelin](https://github.com/nikku/feelin)
14
- - [dmn-eval-js](https://github.com/mineko-io/dmn-eval-js)
5
+ This gem depends on the [FEEL](https://github.com/connectedbits/bpmn/tree/main/feel) gem to evaluate DMN decision tables and expressions.
15
6
 
16
7
  ## Usage
17
8
 
18
- To evaluate an expression:
19
-
20
- ```ruby
21
- DMN.evaluate('"👋 Hello " + name', variables: { name: "World" })
22
- # => "👋 Hello World"
23
- ```
24
-
25
- A slightly more complex example:
26
-
27
- ```ruby
28
- variables = {
29
- person: {
30
- name: "Eric",
31
- age: 59,
32
- }
33
- }
34
- DMN.evaluate('if person.age >= 18 then "adult" else "minor"', variables:)
35
- # => "adult"
36
- ```
37
-
38
- Calling a built-in function:
39
-
40
- ```ruby
41
- DMN.evaluate('sum([1, 2, 3])')
42
- # => 6
43
- ```
44
-
45
- Calling a user-defined function:
46
-
47
- ```ruby
48
- DMN.config.functions = {
49
- "reverse": ->(s) { s.reverse }
50
- }
51
- DMN.evaluate('reverse("Hello World!")', functions:)
52
- # => "!dlroW olleH"
53
- ```
54
-
55
- To evaluate a unary tests:
56
-
57
- ```ruby
58
- DMN.test(3, '<= 10, > 50'))
59
- # => true
60
- ```
61
-
62
- ```ruby
63
- DMN.test("Eric", '"Bob", "Holly", "Eric"')
64
- # => true
65
- ```
66
-
67
9
  ![Decision Table](docs/media/decision_table.png)
68
10
 
69
11
  To evaluate a DMN decision table:
@@ -80,71 +22,8 @@ result = DMN.decide('fine_decision', definitions_xml: fixture_source("fine.dmn")
80
22
  # => { "amount" => 1000, "points" => 7 })
81
23
  ```
82
24
 
83
- To get a list of variables or functions used in an expression:
84
-
85
- ```ruby
86
- LiteralExpression.new(text: 'person.first_name + " " + person.last_name').variable_names
87
- # => ["person.age, person.last_name"]
88
- ```
89
-
90
- ```ruby
91
- LiteralExpression.new(text: 'sum([1, 2, 3])').function_names
92
- # => ["sum"]
93
- ```
94
-
95
- ```ruby
96
- UnaryTests.new(text: '> speed - speed_limit').variable_names
97
- # => ["speed, speed_limit"]
98
- ```
99
-
100
25
  ## Supported Features
101
26
 
102
- ### Data Types
103
-
104
- - [x] Boolean (true, false)
105
- - [x] Number (integer, decimal)
106
- - [x] String (single and double quoted)
107
- - [x] Date, Time, Duration (ISO 8601)
108
- - [x] List (array)
109
- - [x] Context (hash)
110
-
111
- ### Expressions
112
-
113
- - [x] Literal
114
- - [x] Path
115
- - [x] Arithmetic
116
- - [x] Comparison
117
- - [x] Function Invocation
118
- - [x] Positional Parameters
119
- - [x] If Expression
120
- - [ ] For Expression
121
- - [ ] Quantified Expression
122
- - [ ] Filter Expression
123
- - [ ] Disjunction
124
- - [ ] Conjuction
125
- - [ ] Instance Of
126
- - [ ] Function Definition
127
-
128
- ### Unary Tests
129
-
130
- - [x] Comparison
131
- - [x] Interval/Range (inclusive and exclusive)
132
- - [x] Disjunction
133
- - [x] Negation
134
- - [ ] Expression
135
-
136
- ### Built-in Functions
137
-
138
- - [x] Conversion: `string`, `number`
139
- - [x] Boolean: `not`, `is defined`, `get or else`
140
- - [x] String: `substring`, `substring before`, `substring after`, `string length`, `upper case`, `lower case`, `contains`, `starts with`, `ends with`, `matches`, `replace`, `split`, `strip`, `extract`
141
- - [x] Numeric: `decimal`, `floor`, `ceiling`, `round`, `abs`, `modulo`, `sqrt`, `log`, `exp`, `odd`, `even`, `random number`
142
- - [x] List: `list contains`, `count`, `min`, `max`, `sum`, `product`, `mean`, `median`, `stddev`, `mode`, `all`, `any`, `sublist`, `append`, `concatenate`, `insert before`, `remove`, `reverse`, `index of`, `union`, `distinct values`, `duplicate values`, `flatten`, `sort`, `string join`
143
- - [x] Context: `get entries`, `get value`, `get keys`
144
- - [x] Temporal: `now`, `today`, `day of week`, `day of year`, `month of year`, `week of year`
145
-
146
- ### DMN
147
-
148
27
  - [x] Parse DMN XML documents
149
28
  - [x] Evaluate DMN Decision Tables
150
29
  - [x] Evaluate dependent DMN Decision Tables
@@ -169,13 +48,11 @@ $ gem install dmn
169
48
  ```bash
170
49
  $ git clone ...
171
50
  $ bin/setup
51
+ $ cd dmn
52
+ $ bin/rake
172
53
  $ bin/guard
173
54
  ```
174
55
 
175
- ## Development
176
-
177
- [Treetop Doumentation](https://cjheath.github.io/treetop/syntactic_recognition.html) is a good place to start learning about Treetop.
178
-
179
56
  ## License
180
57
 
181
58
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/lib/dmn/decision.rb CHANGED
@@ -7,9 +7,9 @@ module DMN
7
7
  def self.from_json(json)
8
8
  information_requirements = Array.wrap(json[:information_requirement]).map { |ir| InformationRequirement.from_json(ir) } if json[:information_requirement]
9
9
  decision_table = DecisionTable.from_json(json[:decision_table]) if json[:decision_table]
10
- literal_expression = LiteralExpression.from_json(json[:literal_expression]) if json[:literal_expression]
10
+ literal_expression = FEEL::LiteralExpression.from_json(json[:literal_expression]) if json[:literal_expression]
11
11
  variable = Variable.from_json(json[:variable]) if json[:variable]
12
- Decision.new(id: json[:id], name: json[:name], decision_table:, variable:, literal_expression:, information_requirements:)
12
+ Decision.new(id: json[:id], name: json[:name], decision_table: decision_table, variable: variable, literal_expression: literal_expression, information_requirements: information_requirements)
13
13
  end
14
14
 
15
15
  def initialize(id:, name:, decision_table:, variable:, literal_expression:, information_requirements:)
@@ -40,7 +40,7 @@ module DMN
40
40
  next if already_evaluated_decisions[required_decision_id]
41
41
  next if decisions.find { |d| d.id == required_decision_id }.nil?
42
42
 
43
- result = evaluate(required_decision_id, variables:, already_evaluated_decisions:)
43
+ result = evaluate(required_decision_id, variables: variables, already_evaluated_decisions: already_evaluated_decisions)
44
44
 
45
45
  variables.merge!(result) if result.is_a?(Hash)
46
46
 
data/lib/dmn/input.rb CHANGED
@@ -5,8 +5,9 @@ module DMN
5
5
  attr_reader :id, :label, :input_expression
6
6
 
7
7
  def self.from_json(json)
8
- input_expression = LiteralExpression.from_json(json[:input_expression]) if json[:input_expression]
9
- Input.new(id: json[:id], label: json[:label], input_expression:)
8
+ input_expression = FEEL::
9
+ LiteralExpression.from_json(json[:input_expression]) if json[:input_expression]
10
+ Input.new(id: json[:id], label: json[:label], input_expression: input_expression)
10
11
  end
11
12
 
12
13
  def initialize(id:, label:, input_expression:)
data/lib/dmn/rule.rb CHANGED
@@ -5,9 +5,9 @@ module DMN
5
5
  attr_accessor :id, :input_entries, :output_entries, :description
6
6
 
7
7
  def self.from_json(json)
8
- input_entries = Array.wrap(json[:input_entry]).map { |input_entry| UnaryTests.from_json(input_entry) }
9
- output_entries = Array.wrap(json[:output_entry]).map { |output_entry| LiteralExpression.from_json(output_entry) }
10
- Rule.new(id: json[:id], input_entries:, output_entries:, description: json[:description])
8
+ input_entries = Array.wrap(json[:input_entry]).map { |input_entry| FEEL::UnaryTests.from_json(input_entry) }
9
+ output_entries = Array.wrap(json[:output_entry]).map { |output_entry| FEEL::LiteralExpression.from_json(output_entry) }
10
+ Rule.new(id: json[:id], input_entries: input_entries, output_entries: output_entries, description: json[:description])
11
11
  end
12
12
 
13
13
  def initialize(id:, input_entries:, output_entries:, description: nil)
data/lib/dmn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DMN
4
- VERSION = "0.0.3"
4
+ VERSION = "0.1.0"
5
5
  end
data/lib/dmn.rb CHANGED
@@ -6,16 +6,13 @@ require "active_support"
6
6
  require "active_support/time"
7
7
  require "active_support/core_ext/hash"
8
8
 
9
- require "treetop"
9
+ require "feel"
10
+
10
11
  require "xmlhasher"
11
12
 
12
13
  require "dmn/configuration"
13
- require "dmn/nodes"
14
- require "dmn/parser"
15
14
 
16
15
  require "dmn/variable"
17
- require "dmn/literal_expression"
18
- require "dmn/unary_tests"
19
16
  require "dmn/input"
20
17
  require "dmn/output"
21
18
  require "dmn/rule"
@@ -30,13 +27,13 @@ module DMN
30
27
  class EvaluationError < StandardError; end
31
28
 
32
29
  def self.evaluate(expression_text, variables: {})
33
- literal_expression = DMN::LiteralExpression.new(text: expression_text)
30
+ literal_expression = FEEL::LiteralExpression.new(text: expression_text)
34
31
  raise SyntaxError, "Expression is not valid" unless literal_expression.valid?
35
32
  literal_expression.evaluate(variables)
36
33
  end
37
34
 
38
35
  def self.test(input, unary_tests_text, variables: {})
39
- unary_tests = DMN::UnaryTests.new(text: unary_tests_text)
36
+ unary_tests = FEEL::UnaryTests.new(text: unary_tests_text)
40
37
  raise SyntaxError, "Unary tests are not valid" unless unary_tests.valid?
41
38
  unary_tests.test(input, variables)
42
39
  end
metadata CHANGED
@@ -1,42 +1,56 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dmn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Connected Bits
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-03 00:00:00.000000000 Z
10
+ date: 2025-06-26 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: feel
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 0.0.4
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 0.0.4
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: activemodel
14
28
  requirement: !ruby/object:Gem::Requirement
15
29
  requirements:
16
30
  - - ">="
17
31
  - !ruby/object:Gem::Version
18
- version: 7.0.2.3
32
+ version: '6.0'
19
33
  type: :runtime
20
34
  prerelease: false
21
35
  version_requirements: !ruby/object:Gem::Requirement
22
36
  requirements:
23
37
  - - ">="
24
38
  - !ruby/object:Gem::Version
25
- version: 7.0.2.3
39
+ version: '6.0'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: activesupport
28
42
  requirement: !ruby/object:Gem::Requirement
29
43
  requirements:
30
44
  - - ">="
31
45
  - !ruby/object:Gem::Version
32
- version: 7.0.2.3
46
+ version: '6.0'
33
47
  type: :runtime
34
48
  prerelease: false
35
49
  version_requirements: !ruby/object:Gem::Requirement
36
50
  requirements:
37
51
  - - ">="
38
52
  - !ruby/object:Gem::Version
39
- version: 7.0.2.3
53
+ version: '6.0'
40
54
  - !ruby/object:Gem::Dependency
41
55
  name: ostruct
42
56
  requirement: !ruby/object:Gem::Requirement
@@ -275,15 +289,10 @@ files:
275
289
  - lib/dmn/decision.rb
276
290
  - lib/dmn/decision_table.rb
277
291
  - lib/dmn/definitions.rb
278
- - lib/dmn/dmn.treetop
279
292
  - lib/dmn/information_requirement.rb
280
293
  - lib/dmn/input.rb
281
- - lib/dmn/literal_expression.rb
282
- - lib/dmn/nodes.rb
283
294
  - lib/dmn/output.rb
284
- - lib/dmn/parser.rb
285
295
  - lib/dmn/rule.rb
286
- - lib/dmn/unary_tests.rb
287
296
  - lib/dmn/variable.rb
288
297
  - lib/dmn/version.rb
289
298
  homepage: https://www.connectedbits.com
@@ -299,14 +308,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
299
308
  requirements:
300
309
  - - ">="
301
310
  - !ruby/object:Gem::Version
302
- version: '3.1'
311
+ version: '3.0'
303
312
  required_rubygems_version: !ruby/object:Gem::Requirement
304
313
  requirements:
305
314
  - - ">="
306
315
  - !ruby/object:Gem::Version
307
316
  version: '0'
308
317
  requirements: []
309
- rubygems_version: 3.6.5
318
+ rubygems_version: 3.6.2
310
319
  specification_version: 4
311
320
  summary: A light-weight DMN FEEL expression evaluator and business rule engine in
312
321
  Ruby.