dmn 0.0.1 → 0.0.3
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 +10 -10
- data/lib/{spot_feel → dmn}/configuration.rb +1 -1
- data/lib/dmn/decision.rb +48 -0
- data/lib/dmn/decision_table.rb +51 -0
- data/lib/dmn/definitions.rb +66 -0
- data/lib/{spot_feel/spot_feel.treetop → dmn/dmn.treetop} +1 -1
- data/lib/dmn/information_requirement.rb +27 -0
- data/lib/dmn/input.rb +26 -0
- data/lib/dmn/literal_expression.rb +372 -0
- data/lib/{spot_feel → dmn}/nodes.rb +17 -19
- data/lib/dmn/output.rb +27 -0
- data/lib/{spot_feel → dmn}/parser.rb +7 -7
- data/lib/dmn/rule.rb +61 -0
- data/lib/dmn/unary_tests.rb +25 -0
- data/lib/dmn/variable.rb +25 -0
- data/lib/dmn/version.rb +5 -0
- data/lib/{spot_feel.rb → dmn.rb} +22 -17
- metadata +29 -33
- data/lib/spot_feel/dmn/decision.rb +0 -50
- data/lib/spot_feel/dmn/decision_table.rb +0 -53
- data/lib/spot_feel/dmn/definitions.rb +0 -68
- data/lib/spot_feel/dmn/information_requirement.rb +0 -29
- data/lib/spot_feel/dmn/input.rb +0 -28
- data/lib/spot_feel/dmn/literal_expression.rb +0 -374
- data/lib/spot_feel/dmn/output.rb +0 -29
- data/lib/spot_feel/dmn/rule.rb +0 -63
- data/lib/spot_feel/dmn/unary_tests.rb +0 -27
- data/lib/spot_feel/dmn/variable.rb +0 -27
- data/lib/spot_feel/dmn.rb +0 -17
- data/lib/spot_feel/version.rb +0 -5
data/lib/spot_feel/dmn/input.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module SpotFeel
|
4
|
-
module Dmn
|
5
|
-
class Input
|
6
|
-
attr_reader :id, :label, :input_expression
|
7
|
-
|
8
|
-
def self.from_json(json)
|
9
|
-
input_expression = LiteralExpression.from_json(json[:input_expression]) if json[:input_expression]
|
10
|
-
Input.new(id: json[:id], label: json[:label], input_expression:)
|
11
|
-
end
|
12
|
-
|
13
|
-
def initialize(id:, label:, input_expression:)
|
14
|
-
@id = id
|
15
|
-
@label = label
|
16
|
-
@input_expression = input_expression
|
17
|
-
end
|
18
|
-
|
19
|
-
def as_json
|
20
|
-
{
|
21
|
-
id: id,
|
22
|
-
label: label,
|
23
|
-
input_expression: input_expression.as_json,
|
24
|
-
}
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,374 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module SpotFeel
|
4
|
-
module Dmn
|
5
|
-
class LiteralExpression
|
6
|
-
attr_reader :id, :text
|
7
|
-
|
8
|
-
def self.from_json(json)
|
9
|
-
LiteralExpression.new(id: json[:id], text: json[:text])
|
10
|
-
end
|
11
|
-
|
12
|
-
def initialize(id: nil, text:)
|
13
|
-
@id = id
|
14
|
-
@text = text&.strip
|
15
|
-
end
|
16
|
-
|
17
|
-
def tree
|
18
|
-
@tree ||= SpotFeel::Parser.parse(text)
|
19
|
-
end
|
20
|
-
|
21
|
-
def valid?
|
22
|
-
return false if text.blank?
|
23
|
-
tree.present?
|
24
|
-
end
|
25
|
-
|
26
|
-
def evaluate(variables = {})
|
27
|
-
tree.eval(functions.merge(variables))
|
28
|
-
end
|
29
|
-
|
30
|
-
def functions
|
31
|
-
builtins = LiteralExpression.builtin_functions
|
32
|
-
custom = (SpotFeel.config.functions || {})
|
33
|
-
ActiveSupport::HashWithIndifferentAccess.new(builtins.merge(custom))
|
34
|
-
end
|
35
|
-
|
36
|
-
def named_functions
|
37
|
-
# Initialize a set to hold the qualified names
|
38
|
-
function_names = Set.new
|
39
|
-
|
40
|
-
# Define a lambda for the recursive function
|
41
|
-
walk_tree = lambda do |node|
|
42
|
-
# If the node is a qualified name, add it to the set
|
43
|
-
if node.is_a?(SpotFeel::FunctionInvocation)
|
44
|
-
function_names << node.fn_name.text_value
|
45
|
-
end
|
46
|
-
|
47
|
-
# Recursively walk the child nodes
|
48
|
-
node.elements&.each do |child|
|
49
|
-
walk_tree.call(child)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# Start walking the tree from the root
|
54
|
-
walk_tree.call(tree)
|
55
|
-
|
56
|
-
# Return the array of functions
|
57
|
-
function_names.to_a
|
58
|
-
end
|
59
|
-
|
60
|
-
def named_variables
|
61
|
-
# Initialize a set to hold the qualified names
|
62
|
-
qualified_names = Set.new
|
63
|
-
|
64
|
-
# Define a lambda for the recursive function
|
65
|
-
walk_tree = lambda do |node|
|
66
|
-
# If the node is a qualified name, add it to the set
|
67
|
-
if node.is_a?(SpotFeel::QualifiedName)
|
68
|
-
qualified_names << node.text_value
|
69
|
-
end
|
70
|
-
|
71
|
-
# Recursively walk the child nodes
|
72
|
-
node.elements&.each do |child|
|
73
|
-
walk_tree.call(child)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# Start walking the tree from the root
|
78
|
-
walk_tree.call(tree)
|
79
|
-
|
80
|
-
# Return the array of qualified names
|
81
|
-
qualified_names.to_a
|
82
|
-
end
|
83
|
-
|
84
|
-
def self.builtin_functions
|
85
|
-
HashWithIndifferentAccess.new({
|
86
|
-
# Conversion functions
|
87
|
-
"string": ->(from) {
|
88
|
-
return if from.nil?
|
89
|
-
from.to_s
|
90
|
-
},
|
91
|
-
"number": ->(from) {
|
92
|
-
return if from.nil?
|
93
|
-
from.include?(".") ? from.to_f : from.to_i
|
94
|
-
},
|
95
|
-
# Boolean functions
|
96
|
-
"not": ->(value) {
|
97
|
-
if value == true || value == false
|
98
|
-
!value
|
99
|
-
end
|
100
|
-
},
|
101
|
-
"is defined": ->(value) {
|
102
|
-
return if value.nil?
|
103
|
-
!value.nil?
|
104
|
-
},
|
105
|
-
"get or else": ->(value, default) {
|
106
|
-
value.nil? ? default : value
|
107
|
-
},
|
108
|
-
# String functions
|
109
|
-
"substring": ->(string, start, length) {
|
110
|
-
return if string.nil? || start.nil?
|
111
|
-
return "" if length.nil?
|
112
|
-
string[start - 1, length]
|
113
|
-
},
|
114
|
-
"substring before": ->(string, match) {
|
115
|
-
return if string.nil? || match.nil?
|
116
|
-
string.split(match).first
|
117
|
-
},
|
118
|
-
"substring after": ->(string, match) {
|
119
|
-
return if string.nil? || match.nil?
|
120
|
-
string.split(match).last
|
121
|
-
},
|
122
|
-
"string length": ->(string) {
|
123
|
-
return if string.nil?
|
124
|
-
string.length
|
125
|
-
},
|
126
|
-
"upper case": ->(string) {
|
127
|
-
return if string.nil?
|
128
|
-
string.upcase
|
129
|
-
},
|
130
|
-
"lower case": -> (string) {
|
131
|
-
return if string.nil?
|
132
|
-
string.downcase
|
133
|
-
},
|
134
|
-
"contains": ->(string, match) {
|
135
|
-
return if string.nil? || match.nil?
|
136
|
-
string.include?(match)
|
137
|
-
},
|
138
|
-
"starts with": ->(string, match) {
|
139
|
-
return if string.nil? || match.nil?
|
140
|
-
string.start_with?(match)
|
141
|
-
},
|
142
|
-
"ends with": ->(string, match) {
|
143
|
-
return if string.nil? || match.nil?
|
144
|
-
string.end_with?(match)
|
145
|
-
},
|
146
|
-
"matches": ->(string, match) {
|
147
|
-
return if string.nil? || match.nil?
|
148
|
-
string.match?(match)
|
149
|
-
},
|
150
|
-
"replace": ->(string, match, replacement) {
|
151
|
-
return if string.nil? || match.nil? || replacement.nil?
|
152
|
-
string.gsub(match, replacement)
|
153
|
-
},
|
154
|
-
"split": ->(string, match) {
|
155
|
-
return if string.nil? || match.nil?
|
156
|
-
string.split(match)
|
157
|
-
},
|
158
|
-
"strip": -> (string) {
|
159
|
-
return if string.nil?
|
160
|
-
string.strip
|
161
|
-
},
|
162
|
-
"extract": -> (string, pattern) {
|
163
|
-
return if string.nil? || pattern.nil?
|
164
|
-
string.match(pattern).captures
|
165
|
-
},
|
166
|
-
# Numeric functions
|
167
|
-
"decimal": ->(n, scale) {
|
168
|
-
return if n.nil? || scale.nil?
|
169
|
-
n.round(scale)
|
170
|
-
},
|
171
|
-
"floor": ->(n) {
|
172
|
-
return if n.nil?
|
173
|
-
n.floor
|
174
|
-
},
|
175
|
-
"ceiling": ->(n) {
|
176
|
-
return if n.nil?
|
177
|
-
n.ceil
|
178
|
-
},
|
179
|
-
"round up": ->(n) {
|
180
|
-
return if n.nil?
|
181
|
-
n.ceil
|
182
|
-
},
|
183
|
-
"round down": ->(n) {
|
184
|
-
return if n.nil?
|
185
|
-
n.floor
|
186
|
-
},
|
187
|
-
"abs": ->(n) {
|
188
|
-
return if n.nil?
|
189
|
-
n.abs
|
190
|
-
},
|
191
|
-
"modulo": ->(n, divisor) {
|
192
|
-
return if n.nil? || divisor.nil?
|
193
|
-
n % divisor
|
194
|
-
},
|
195
|
-
"sqrt": ->(n) {
|
196
|
-
return if n.nil?
|
197
|
-
Math.sqrt(n)
|
198
|
-
},
|
199
|
-
"log": ->(n) {
|
200
|
-
return if n.nil?
|
201
|
-
Math.log(n)
|
202
|
-
},
|
203
|
-
"exp": ->(n) {
|
204
|
-
return if n.nil?
|
205
|
-
Math.exp(n)
|
206
|
-
},
|
207
|
-
"odd": ->(n) {
|
208
|
-
return if n.nil?
|
209
|
-
n.odd?
|
210
|
-
},
|
211
|
-
"even": ->(n) {
|
212
|
-
return if n.nil?
|
213
|
-
n.even?
|
214
|
-
},
|
215
|
-
"random number": ->(n) {
|
216
|
-
return if n.nil?
|
217
|
-
rand(n)
|
218
|
-
},
|
219
|
-
# List functions
|
220
|
-
"list contains": ->(list, match) {
|
221
|
-
return if list.nil?
|
222
|
-
return false if match.nil?
|
223
|
-
list.include?(match)
|
224
|
-
},
|
225
|
-
"count": ->(list) {
|
226
|
-
return if list.nil?
|
227
|
-
return 0 if list.empty?
|
228
|
-
list.length
|
229
|
-
},
|
230
|
-
"min": ->(list) {
|
231
|
-
return if list.nil?
|
232
|
-
list.min
|
233
|
-
},
|
234
|
-
"max": ->(list) {
|
235
|
-
return if list.nil?
|
236
|
-
list.max
|
237
|
-
},
|
238
|
-
"sum": ->(list) {
|
239
|
-
return if list.nil?
|
240
|
-
list.sum
|
241
|
-
},
|
242
|
-
"product": ->(list) {
|
243
|
-
return if list.nil?
|
244
|
-
list.inject(:*)
|
245
|
-
},
|
246
|
-
"mean": ->(list) {
|
247
|
-
return if list.nil?
|
248
|
-
list.sum / list.length
|
249
|
-
},
|
250
|
-
"median": ->(list) {
|
251
|
-
return if list.nil?
|
252
|
-
list.sort[list.length / 2]
|
253
|
-
},
|
254
|
-
"stddev": ->(list) {
|
255
|
-
return if list.nil?
|
256
|
-
mean = list.sum / list.length.to_f
|
257
|
-
Math.sqrt(list.map { |n| (n - mean)**2 }.sum / list.length)
|
258
|
-
},
|
259
|
-
"mode": ->(list) {
|
260
|
-
return if list.nil?
|
261
|
-
list.group_by(&:itself).values.max_by(&:size).first
|
262
|
-
},
|
263
|
-
"all": ->(list) {
|
264
|
-
return if list.nil?
|
265
|
-
list.all?
|
266
|
-
},
|
267
|
-
"any": ->(list) {
|
268
|
-
return if list.nil?
|
269
|
-
list.any?
|
270
|
-
},
|
271
|
-
"sublist": ->(list, start, length) {
|
272
|
-
return if list.nil? || start.nil?
|
273
|
-
return [] if length.nil?
|
274
|
-
list[start - 1, length]
|
275
|
-
},
|
276
|
-
"append": ->(list, item) {
|
277
|
-
return if list.nil?
|
278
|
-
list + [item]
|
279
|
-
},
|
280
|
-
"concatenate": ->(list1, list2) {
|
281
|
-
return [nil, nil] if list1.nil? && list2.nil?
|
282
|
-
return [nil] + list2 if list1.nil?
|
283
|
-
return list1 + [nil] if list2.nil?
|
284
|
-
Array.wrap(list1) + Array.wrap(list2)
|
285
|
-
},
|
286
|
-
"insert before": ->(list, position, item) {
|
287
|
-
return if list.nil? || position.nil?
|
288
|
-
list.insert(position - 1, item)
|
289
|
-
},
|
290
|
-
"remove": ->(list, position) {
|
291
|
-
return if list.nil? || position.nil?
|
292
|
-
list.delete_at(position - 1); list
|
293
|
-
},
|
294
|
-
"reverse": ->(list) {
|
295
|
-
return if list.nil?
|
296
|
-
list.reverse
|
297
|
-
},
|
298
|
-
"index of": ->(list, match) {
|
299
|
-
return if list.nil?
|
300
|
-
return [] if match.nil?
|
301
|
-
list.index(match) + 1
|
302
|
-
},
|
303
|
-
"union": ->(list1, list2) {
|
304
|
-
return if list1.nil? || list2.nil?
|
305
|
-
list1 | list2
|
306
|
-
},
|
307
|
-
"distinct values": ->(list) {
|
308
|
-
return if list.nil?
|
309
|
-
list.uniq
|
310
|
-
},
|
311
|
-
"duplicate values": ->(list) {
|
312
|
-
return if list.nil?
|
313
|
-
list.select { |e| list.count(e) > 1 }.uniq
|
314
|
-
},
|
315
|
-
"flatten": ->(list) {
|
316
|
-
return if list.nil?
|
317
|
-
list.flatten
|
318
|
-
},
|
319
|
-
"sort": ->(list) {
|
320
|
-
return if list.nil?
|
321
|
-
list.sort
|
322
|
-
},
|
323
|
-
"string join": ->(list, separator) {
|
324
|
-
return if list.nil?
|
325
|
-
list.join(separator)
|
326
|
-
},
|
327
|
-
# Context functions
|
328
|
-
"get value": ->(context, name) {
|
329
|
-
return if context.nil? || name.nil?
|
330
|
-
context[name]
|
331
|
-
},
|
332
|
-
"context put": ->(context, name, value) {
|
333
|
-
return if context.nil? || name.nil?
|
334
|
-
context[name] = value; context
|
335
|
-
},
|
336
|
-
"context merge": ->(context1, context2) {
|
337
|
-
return if context1.nil? || context2.nil?
|
338
|
-
context1.merge(context2)
|
339
|
-
},
|
340
|
-
"get entries": ->(context) {
|
341
|
-
return if context.nil?
|
342
|
-
context.entries
|
343
|
-
},
|
344
|
-
# Temporal functions
|
345
|
-
"now": ->() { Time.now },
|
346
|
-
"today": ->() { Date.today },
|
347
|
-
"day of week": ->(date) {
|
348
|
-
return if date.nil?
|
349
|
-
date.wday
|
350
|
-
},
|
351
|
-
"day of year": ->(date) {
|
352
|
-
return if date.nil?
|
353
|
-
date.yday
|
354
|
-
},
|
355
|
-
"week of year": ->(date) {
|
356
|
-
return if date.nil?
|
357
|
-
date.cweek
|
358
|
-
},
|
359
|
-
"month of year": ->(date) {
|
360
|
-
return if date.nil?
|
361
|
-
date.month
|
362
|
-
},
|
363
|
-
})
|
364
|
-
end
|
365
|
-
|
366
|
-
def as_json
|
367
|
-
{
|
368
|
-
id: id,
|
369
|
-
text: text,
|
370
|
-
}
|
371
|
-
end
|
372
|
-
end
|
373
|
-
end
|
374
|
-
end
|
data/lib/spot_feel/dmn/output.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module SpotFeel
|
4
|
-
module Dmn
|
5
|
-
class Output
|
6
|
-
attr_reader :id, :label, :name, :type_ref
|
7
|
-
|
8
|
-
def self.from_json(json)
|
9
|
-
Output.new(id: json[:id], label: json[:label], name: json[:name], type_ref: json[:type_ref])
|
10
|
-
end
|
11
|
-
|
12
|
-
def initialize(id:, label:, name:, type_ref:)
|
13
|
-
@id = id
|
14
|
-
@label = label
|
15
|
-
@name = name
|
16
|
-
@type_ref = type_ref
|
17
|
-
end
|
18
|
-
|
19
|
-
def as_json
|
20
|
-
{
|
21
|
-
id: id,
|
22
|
-
label: label,
|
23
|
-
name: name,
|
24
|
-
type_ref: type_ref,
|
25
|
-
}
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
data/lib/spot_feel/dmn/rule.rb
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module SpotFeel
|
4
|
-
module Dmn
|
5
|
-
class Rule
|
6
|
-
attr_accessor :id, :input_entries, :output_entries, :description
|
7
|
-
|
8
|
-
def self.from_json(json)
|
9
|
-
input_entries = Array.wrap(json[:input_entry]).map { |input_entry| UnaryTests.from_json(input_entry) }
|
10
|
-
output_entries = Array.wrap(json[:output_entry]).map { |output_entry| LiteralExpression.from_json(output_entry) }
|
11
|
-
Rule.new(id: json[:id], input_entries:, output_entries:, description: json[:description])
|
12
|
-
end
|
13
|
-
|
14
|
-
def initialize(id:, input_entries:, output_entries:, description: nil)
|
15
|
-
@id = id
|
16
|
-
@input_entries = input_entries
|
17
|
-
@output_entries = output_entries
|
18
|
-
@description = description
|
19
|
-
end
|
20
|
-
|
21
|
-
def evaluate(input_values = [], variables = {})
|
22
|
-
[].tap do |test_results|
|
23
|
-
input_entries.each_with_index do |input_entry, index|
|
24
|
-
test_results.push input_entry.test(input_values[index], variables)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def as_json
|
30
|
-
{
|
31
|
-
id: id,
|
32
|
-
input_entries: input_entries.map(&:as_json),
|
33
|
-
output_entries: output_entries.map(&:as_json),
|
34
|
-
description: description,
|
35
|
-
}
|
36
|
-
end
|
37
|
-
|
38
|
-
def output_value(outputs, variables)
|
39
|
-
HashWithIndifferentAccess.new.tap do |ov|
|
40
|
-
output_entries.each_with_index do |output_entry, index|
|
41
|
-
if output_entry.valid?
|
42
|
-
val = output_entry.evaluate(variables)
|
43
|
-
nested_hash_value(ov, outputs[index].name, val)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def nested_hash_value(hash, key_string, value)
|
52
|
-
keys = key_string.split('.')
|
53
|
-
current = hash
|
54
|
-
keys[0...-1].each do |key|
|
55
|
-
current[key] ||= {}
|
56
|
-
current = current[key]
|
57
|
-
end
|
58
|
-
current[keys.last] = value
|
59
|
-
hash
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module SpotFeel
|
4
|
-
module Dmn
|
5
|
-
class UnaryTests < LiteralExpression
|
6
|
-
attr_reader :id, :text
|
7
|
-
|
8
|
-
def self.from_json(json)
|
9
|
-
UnaryTests.new(id: json[:id], text: json[:text])
|
10
|
-
end
|
11
|
-
|
12
|
-
def tree
|
13
|
-
@tree ||= Parser.parse_test(text)
|
14
|
-
end
|
15
|
-
|
16
|
-
def valid?
|
17
|
-
return true if text.nil? || text == '-'
|
18
|
-
tree.present?
|
19
|
-
end
|
20
|
-
|
21
|
-
def test(input, variables = {})
|
22
|
-
return true if text.nil? || text == '-'
|
23
|
-
tree.eval(functions.merge(variables)).call(input)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module SpotFeel
|
4
|
-
module Dmn
|
5
|
-
class Variable
|
6
|
-
attr_reader :id, :name, :type_ref
|
7
|
-
|
8
|
-
def self.from_json(json)
|
9
|
-
Variable.new(id: json[:id], name: json[:name], type_ref: json[:type_ref])
|
10
|
-
end
|
11
|
-
|
12
|
-
def initialize(id:, name:, type_ref:)
|
13
|
-
@id = id
|
14
|
-
@name = name
|
15
|
-
@type_ref = type_ref
|
16
|
-
end
|
17
|
-
|
18
|
-
def as_json
|
19
|
-
{
|
20
|
-
id: id,
|
21
|
-
name: name,
|
22
|
-
type_ref: type_ref,
|
23
|
-
}
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
data/lib/spot_feel/dmn.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "dmn/variable"
|
4
|
-
require_relative "dmn/literal_expression"
|
5
|
-
require_relative "dmn/unary_tests"
|
6
|
-
require_relative "dmn/input"
|
7
|
-
require_relative "dmn/output"
|
8
|
-
require_relative "dmn/rule"
|
9
|
-
require_relative "dmn/decision_table"
|
10
|
-
require_relative "dmn/information_requirement"
|
11
|
-
require_relative "dmn/decision"
|
12
|
-
require_relative "dmn/definitions"
|
13
|
-
|
14
|
-
module SpotFeel
|
15
|
-
module Dmn
|
16
|
-
end
|
17
|
-
end
|