dmn 0.0.3 → 0.0.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/README.md +3 -128
- data/lib/dmn/decision.rb +1 -1
- data/lib/dmn/input.rb +2 -1
- data/lib/dmn/rule.rb +2 -2
- data/lib/dmn/version.rb +1 -1
- data/lib/dmn.rb +4 -7
- metadata +16 -7
- data/lib/dmn/dmn.treetop +0 -654
- data/lib/dmn/literal_expression.rb +0 -372
- data/lib/dmn/nodes.rb +0 -682
- data/lib/dmn/parser.rb +0 -29
- data/lib/dmn/unary_tests.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 99f8ad17d713e6ce95941ee100f3c4046a2ffc93155ea6629a6761bb3745e1ca
|
4
|
+
data.tar.gz: ad53db39e8b2e96884644d177486b8cf1d65a8d608d5b09c136ee37b0d3daad5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8e8009211631110c50467c8ff2f8b857095ca497409171590d44135c2bd25ee5ced0d7d99c676f48a0137c3b7083c00e349f0853cca2cbfae0e45baa316b612e
|
7
|
+
data.tar.gz: bf0a15e9f42f288505314de24eec35a70fdc621238e6944bd46d69b72e82de83cddab12cc891e6f2542bf5a99c7b5c32aa17a216ba4c00ef9482692abac3289a
|
data/README.md
CHANGED
@@ -1,69 +1,11 @@
|
|
1
|
-
#
|
1
|
+
# DMN
|
2
2
|
|
3
|
-
A light-weight DMN
|
3
|
+
A light-weight DMN (Decision Model and Notation) business rule ruby gem.
|
4
4
|
|
5
|
-
This gem
|
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
|

|
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
|
@@ -172,10 +51,6 @@ $ bin/setup
|
|
172
51
|
$ bin/guard
|
173
52
|
```
|
174
53
|
|
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
54
|
## License
|
180
55
|
|
181
56
|
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,7 +7,7 @@ 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
12
|
Decision.new(id: json[:id], name: json[:name], decision_table:, variable:, literal_expression:, information_requirements:)
|
13
13
|
end
|
data/lib/dmn/input.rb
CHANGED
@@ -5,7 +5,8 @@ module DMN
|
|
5
5
|
attr_reader :id, :label, :input_expression
|
6
6
|
|
7
7
|
def self.from_json(json)
|
8
|
-
input_expression =
|
8
|
+
input_expression = FEEL::
|
9
|
+
LiteralExpression.from_json(json[:input_expression]) if json[:input_expression]
|
9
10
|
Input.new(id: json[:id], label: json[:label], input_expression:)
|
10
11
|
end
|
11
12
|
|
data/lib/dmn/rule.rb
CHANGED
@@ -5,8 +5,8 @@ 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) }
|
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
10
|
Rule.new(id: json[:id], input_entries:, output_entries:, description: json[:description])
|
11
11
|
end
|
12
12
|
|
data/lib/dmn/version.rb
CHANGED
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 "
|
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 =
|
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 =
|
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,14 +1,28 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dmn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Connected Bits
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-04-02 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
|
@@ -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
|