rulezilla 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,63 @@
1
+ module Rulezilla
2
+ class RuleBuilder
3
+ class DefaultCondition; end
4
+
5
+ class GherkinToConditionRule
6
+ include Rulezilla::DSL
7
+
8
+ define :'this is a "field"' do
9
+ condition { name =~ /^this is\s?a?n? \"(.*)\"$/i }
10
+
11
+ result do
12
+ field = name.scan(/^this is\s?a?n? \"(.*)\"$/i).flatten.first
13
+ "#{field}?"
14
+ end
15
+ end
16
+
17
+ define :'the "field" is "value"' do
18
+ condition { name =~ /^the \"(.*)\" is \"(.*)\"$/i }
19
+
20
+ result do
21
+ field, value = name.scan(/^the \"(.*)\" is \"(.*)\"$/i).flatten
22
+ field = field.gsub(/\s/, '_').downcase
23
+
24
+ "#{field} == #{ConditionValueEvaluateRule.apply(value: value)}"
25
+ end
26
+ end
27
+
28
+ define :'the "field" is in: {table}' do
29
+ condition { name =~ /^the \"(.*)\" is in:$/i }
30
+
31
+ result do
32
+ field = name.scan(/^the \"(.*)\" is in:$/i).flatten.first
33
+ field = field.gsub(/\s/, '_').downcase
34
+
35
+ values = rows.map{ |row| "#{ConditionValueEvaluateRule.apply(value: row['cells'].first)}" }.join(', ')
36
+
37
+ "[#{values}].include?(#{field})"
38
+ end
39
+ end
40
+
41
+ define :'none of the above' do
42
+ condition { name == 'none of the above' }
43
+ result(DefaultCondition)
44
+ end
45
+
46
+ default { raise "Condition steps is not recognised: #{name}" }
47
+ end
48
+
49
+
50
+
51
+ class ConditionValueEvaluateRule
52
+ include Rulezilla::DSL
53
+
54
+ define :blank do
55
+ condition { value == 'blank'}
56
+ result("''")
57
+ end
58
+
59
+ default { "\"#{value}\"" }
60
+ end
61
+ end
62
+
63
+ end
@@ -0,0 +1,49 @@
1
+ module Rulezilla
2
+ class RuleBuilder
3
+ class GherkinToResultRule
4
+ include Rulezilla::DSL
5
+
6
+ group :keyword_is do
7
+ condition { name =~ /^the #{step_keyword} is/i }
8
+
9
+ define :value_is do
10
+ condition { name =~ /^the #{step_keyword} is \"(.*)\"$/i }
11
+ result { "\"#{name.scan(/^the #{step_keyword} is \"(.*)\"$/i).flatten.first}\"" }
12
+ end
13
+
14
+ define :duration_is do
15
+ condition { name =~ /^the #{step_keyword} is \"(\d+)\" (days?|hours?|minutes?|seconds?)$/i }
16
+ result do
17
+ quantity, unit = name.scan(/^the #{step_keyword} is \"(\d+)\" (days?|hours?|minutes?|seconds?)$/i).flatten
18
+
19
+ multiplier = case unit
20
+ when /day/
21
+ 86400
22
+ when /hour/
23
+ 3600
24
+ when /minute/
25
+ 60
26
+ when /second/
27
+ 1
28
+ end
29
+
30
+ quantity.to_i * multiplier
31
+ end
32
+ end
33
+ end
34
+
35
+ define :start_with_this_is_a do
36
+ condition { name =~ /^this is an? #{step_keyword}$/i }
37
+ result("true")
38
+ end
39
+
40
+ define :start_with_this_is_not_a do
41
+ condition { name =~ /^this is not an? #{step_keyword}$/i }
42
+ result("false")
43
+ end
44
+
45
+ default { raise "Unrecognisable step: #{name}" }
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,76 @@
1
+ module Rulezilla
2
+ class Tree
3
+ attr_reader :current_node, :root_node
4
+
5
+ def initialize(node)
6
+ @root_node = node
7
+ @root_node.name = :root
8
+ @current_node = node
9
+ end
10
+
11
+ def go_up
12
+ @current_node = is_root? ? @root_node : @current_node.parent
13
+ end
14
+
15
+ def find_all(record, node=@root_node)
16
+ array = []
17
+ if node.applies?(record)
18
+ node.children.each do |child_node|
19
+ array += find_all(record, child_node)
20
+ end
21
+
22
+ return node.has_result? ? array + [node] : array
23
+ end
24
+ return array
25
+ end
26
+
27
+ def trace(record, node=@root_node)
28
+ if node.applies?(record)
29
+ node.children.each do |child_node|
30
+ array = trace(record, child_node)
31
+ return [node] + array unless array.empty?
32
+ end
33
+ return node.has_result? ? [node] : []
34
+ end
35
+ return []
36
+ end
37
+
38
+ def all_results(record, node=@root_node, results=[])
39
+ if node.has_result?
40
+ results << node.result(record) rescue NoMethodError
41
+ end
42
+
43
+ node.children.each do |child_node|
44
+ all_results(record, child_node, results)
45
+ end
46
+
47
+ return results
48
+ end
49
+
50
+ def create_and_move_to_child(name=nil)
51
+ node = Node.new
52
+ node.name = name
53
+ @current_node.add_child(node)
54
+ @current_node = node
55
+ node
56
+ end
57
+
58
+ def clone_and_append_children(children, node=@current_node)
59
+ children.each do |child_node|
60
+ child_node = child_node.dup
61
+ node.add_child(child_node)
62
+
63
+ if child_node.has_children?
64
+ children_nodes = child_node.children
65
+ child_node.children = []
66
+ clone_and_append_children(children_nodes, child_node)
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+ def is_root?
73
+ @current_node == @root_node
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,3 @@
1
+ module Rulezilla
2
+ VERSION = '0.1.4'
3
+ end
data/rulezilla.gemspec ADDED
@@ -0,0 +1,18 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require 'rulezilla/version'
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ['Peter Wu']
6
+ gem.email = ['peter.wu@simplybusiness.com']
7
+ gem.description = %q{Rules DSL}
8
+ gem.summary = %q{Rules DSL}
9
+ gem.homepage = %q{https://github.com/simplybusiness/rulezilla}
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.name = 'rulezilla'
13
+ gem.require_paths = ['lib']
14
+ gem.version = Rulezilla::VERSION
15
+ gem.license = 'MIT'
16
+
17
+ gem.add_runtime_dependency('gherkin')
18
+ end
@@ -0,0 +1,21 @@
1
+ Feature: Default Support methods
2
+
3
+ Scenario Outline: does_not?
4
+ Given the rule is:
5
+ """
6
+ define :not_sleep do
7
+ condition { does_not?(go_to_bed) }
8
+ result('Tired')
9
+ end
10
+
11
+ default('Refreshing')
12
+ """
13
+ When the record has attribute "go_to_bed" and returns "<value>"
14
+ Then the result is "<result>"
15
+
16
+ Examples:
17
+ | value | result |
18
+ | true | Refreshing |
19
+ | false | Tired |
20
+ | | Refreshing |
21
+ | nil | Refreshing |
@@ -0,0 +1,90 @@
1
+ Feature: Rulezilla Gherkin DSL
2
+
3
+ Scenario: Condition: something is something
4
+ Given the gherkin is:
5
+ """
6
+ Feature: Awesomeness Rule
7
+
8
+ Scenario: Robocop
9
+ When the "target" is "Robocop"
10
+ Then the awesomeness is "very awesome"
11
+ """
12
+ When the record has attribute "target" and returns "Robocop"
13
+ Then the result is "very awesome"
14
+
15
+
16
+ Scenario: Multiple Condition: something is something
17
+ Given the gherkin is:
18
+ """
19
+ Feature: Winner Rule
20
+
21
+ Scenario: Robocop vs Ironman
22
+ When the "target" is "Robocop"
23
+ And the "opponent" is "Ironman"
24
+ Then the winner is "Ironman"
25
+ """
26
+ When the record has attribute "target" and returns "Robocop"
27
+ And the record has attribute "opponent" and returns "Ironman"
28
+ Then the result is "Ironman"
29
+
30
+
31
+ Scenario: Default
32
+ Given the gherkin is:
33
+ """
34
+ Feature: Winner Rule
35
+
36
+ Scenario: Default
37
+ When none of the above
38
+ Then the winner is "Ironman"
39
+ """
40
+ Then the result is "Ironman"
41
+
42
+
43
+ Scenario: 'The :keyword is :value', Keyword mismatch
44
+ Given the incorrect gherkin is:
45
+ """
46
+ Feature: Winner Rule
47
+
48
+ Scenario: Default
49
+ When none of the above
50
+ Then the loser is "Hello kitty"
51
+ """
52
+ Then it raises exception "Unrecognisable step: the loser is 'Hello kitty'"
53
+
54
+
55
+ Scenario: True value
56
+ Given the gherkin is:
57
+ """
58
+ Feature: Dummy Rule
59
+
60
+ Scenario: Dummy
61
+ When the "name" is "007"
62
+ Then this is a dummy
63
+ """
64
+ When the record has attribute "name" and returns "007"
65
+ Then the result is "true"
66
+
67
+
68
+ Scenario: False value
69
+ Given the gherkin is:
70
+ """
71
+ Feature: Dummy Rule
72
+
73
+ Scenario: Not Dummy
74
+ When the "name" is "Tom"
75
+ Then this is not a dummy
76
+ """
77
+ When the record has attribute "name" and returns "Tom"
78
+ Then the result is "false"
79
+
80
+
81
+ Scenario: 'This is( not) a :keyword', Keyword mismatch
82
+ Given the incorrect gherkin is:
83
+ """
84
+ Feature: Winner Rule
85
+
86
+ Scenario: Not Dummy
87
+ When the "name" is "Tom"
88
+ Then this is not a cat
89
+ """
90
+ Then it raises exception "Unrecognisable step: this is not a 'cat'"
@@ -0,0 +1,16 @@
1
+ @rule_steps
2
+ Feature: Animal Rule
3
+
4
+ Scenario: entity is a cat
5
+ When this is a "cat"
6
+ Then this is an animal
7
+
8
+ Scenario: telephone number is dog or bird
9
+ When the "entity" is in:
10
+ | dog |
11
+ | bird |
12
+ Then this is an animal
13
+
14
+ Scenario: default
15
+ When none of the above
16
+ Then this is not an animal
@@ -0,0 +1,14 @@
1
+ @rule_steps
2
+ Feature: Duration Rule
3
+
4
+ Scenario: Maths class
5
+ When the "name of the class" is "Maths"
6
+ Then the duration is "1" minute
7
+
8
+ Scenario: Science class
9
+ When the "name of the class" is "Science"
10
+ Then the duration is "10" hours
11
+
12
+ Scenario: PE
13
+ When the "name of the class" is "PE"
14
+ Then the duration is "2" days
@@ -0,0 +1,299 @@
1
+ Feature: Rulezilla DSL
2
+
3
+ Scenario: To get all outcome from a rule
4
+ Given the rule is:
5
+ """
6
+ group :group_1 do
7
+ condition { false }
8
+
9
+ group :group_1_1 do
10
+ condition { true }
11
+
12
+ define :rule_1_1_1 do
13
+ condition { false }
14
+ result('A')
15
+ end
16
+
17
+ default('B')
18
+ end
19
+
20
+ define :rule_1_1 do
21
+ condition { true }
22
+ result('C')
23
+ end
24
+
25
+ define :rule_1_2 do
26
+ condition { true }
27
+ result('D')
28
+ end
29
+ end
30
+
31
+ define :rule_2 do
32
+ condition { false }
33
+ result('E')
34
+ end
35
+
36
+ default('F')
37
+ """
38
+ Then all the outcomes are "A, B, C, D, E, F"
39
+
40
+ Scenario: Rule is evaluated from top to bottom order
41
+ Given the rule is:
42
+ """
43
+ define :rule_1 do
44
+ condition { true }
45
+ result('Yes')
46
+ end
47
+
48
+ define :rule_2 do
49
+ condition { true }
50
+ result('May be')
51
+ end
52
+ """
53
+ Then the result is "Yes"
54
+
55
+
56
+ Scenario: Nesting in Rule DSL
57
+ Given the rule is:
58
+ """
59
+ group :group_1 do
60
+ condition { true }
61
+
62
+ define :good do
63
+ condition { true }
64
+ result('Good')
65
+ end
66
+ end
67
+ """
68
+ Then the result is "Good"
69
+
70
+
71
+ Scenario: If nothing is matched in a group, it will fall to default value of the group
72
+ Given the rule is:
73
+ """
74
+ group :group_1 do
75
+ condition { true }
76
+
77
+ define :good do
78
+ condition { false }
79
+ result('Good')
80
+ end
81
+
82
+ default('It is alright')
83
+ end
84
+ """
85
+ Then the result is "It is alright"
86
+
87
+
88
+ Scenario: If nothing is matched, and no default is define in the group, it will fall to the next default
89
+ Given the rule is:
90
+ """
91
+ group :group_1 do
92
+ condition { true }
93
+
94
+ define :good do
95
+ condition { false }
96
+ result('Good')
97
+ end
98
+ end
99
+
100
+ default('Everything is awesome')
101
+ """
102
+ Then the result is "Everything is awesome"
103
+
104
+
105
+ Scenario: If nothing is matched, it will continue to evaluate the next group
106
+ Given the rule is:
107
+ """
108
+ group :group_1 do
109
+ condition { true }
110
+
111
+ define :good do
112
+ condition { false }
113
+ result('Good')
114
+ end
115
+ end
116
+
117
+ group :group_2 do
118
+ condition { true }
119
+
120
+ define :bad do
121
+ condition { true }
122
+ result('Bad')
123
+ end
124
+ end
125
+ """
126
+ Then the result is "Bad"
127
+
128
+
129
+ Scenario Outline: It evaluate the rule against a record
130
+ Given the rule is:
131
+ """
132
+ define :fruit do
133
+ condition { fruit }
134
+ result('This is good!')
135
+ end
136
+
137
+ define :fruit do
138
+ condition { !fruit }
139
+ result('Oh, too bad')
140
+ end
141
+ """
142
+ When the record has attribute "fruit" and returns "<value>"
143
+ Then the result is "<result>"
144
+
145
+ Examples:
146
+ | value | result |
147
+ | true | This is good! |
148
+ | false | Oh, too bad |
149
+
150
+
151
+ Scenario: To get all matching outcomes from a rule
152
+ Given the rule is:
153
+ """
154
+ group :group_1 do
155
+ condition { true }
156
+
157
+ group :group_1_1 do
158
+ condition { true }
159
+
160
+ define :rule_1_1_1 do
161
+ condition { false }
162
+ result('A')
163
+ end
164
+
165
+ default('B')
166
+ end
167
+
168
+ define :rule_1_1 do
169
+ condition { true }
170
+ result('C')
171
+ end
172
+
173
+ define :rule_1_2 do
174
+ condition { true }
175
+ result('D')
176
+ end
177
+ end
178
+
179
+ define :rule_2 do
180
+ condition { false }
181
+ result('E')
182
+ end
183
+
184
+ default('F')
185
+ """
186
+ Then all the matching outcomes are "B, C, D, F"
187
+
188
+
189
+ Scenario: Support Module
190
+ Given the rule class name is "FruitRule"
191
+ And the support module called "FruitRuleSupport" has definition:
192
+ """
193
+ def is_fruit?
194
+ fruit == true
195
+ end
196
+ """
197
+ And the rule is:
198
+ """
199
+ define :fruit do
200
+ condition { is_fruit? }
201
+ result('This is good!')
202
+ end
203
+
204
+ define :fruit do
205
+ condition { !is_fruit? }
206
+ result('Oh, too bad')
207
+ end
208
+ """
209
+ When the record has attribute "fruit" and returns "true"
210
+ Then the result is "This is good!"
211
+
212
+
213
+ Scenario Outline: Validate the presence of attributes
214
+ Given the rule is:
215
+ """
216
+ validate_attributes_presence :apple, :orange
217
+
218
+ default(true)
219
+ """
220
+ When the record has attribute "<attributes>"
221
+ Then "<does or does not>" raise the exception "<exception>"
222
+
223
+ Examples:
224
+ | attributes | does or does not | exception |
225
+ | apple | does | Missing orange attributes |
226
+ | orange | does | Missing apple attributes |
227
+ | apple, orange | does not | |
228
+
229
+
230
+ Scenario: Rule return nil if no rule is defined Given the rule is:
231
+ Given the rule is:
232
+ """
233
+ """
234
+ Then the result is "nil"
235
+
236
+ Scenario Outline: Trace the path to the result
237
+ Given the rule is:
238
+ """
239
+ group :group_1 do
240
+ condition { group_1_condition }
241
+
242
+ group :group_2 do
243
+ condition { group_2_condition }
244
+
245
+ define :rule_1 do
246
+ condition { false }
247
+ result('A')
248
+ end
249
+
250
+ define :rule_2 do
251
+ condition { rule_2_condition }
252
+ result('B')
253
+ end
254
+
255
+ default('C')
256
+ end
257
+
258
+ default('D')
259
+ end
260
+
261
+ define :rule_3 do
262
+ condition { rule_3_condition }
263
+ result('E')
264
+ end
265
+
266
+ default('F')
267
+ """
268
+ When the record has attribute "group_1_condition" and returns "<group_1_condition>"
269
+ And the record has attribute "group_2_condition" and returns "<group_2_condition>"
270
+ And the record has attribute "rule_2_condition" and returns "<rule_2_condition>"
271
+ And the record has attribute "rule_3_condition" and returns "<rule_3_condition>"
272
+ Then the trace is "<trace>"
273
+
274
+ Examples:
275
+ | group_1_condition | group_2_condition | rule_2_condition | rule_3_condition | trace |
276
+ | true | true | true | true | root -> group_1 -> group_2 -> rule_2 |
277
+ | true | false | true | true | root -> group_1 |
278
+ | false | true | true | true | root -> rule_3 |
279
+ | false | true | true | false | root |
280
+
281
+ Scenario: Include rule
282
+ Given there is a rule called "CommonRule":
283
+ """
284
+ define :a do
285
+ condition { true }
286
+ result { 'A' }
287
+ end
288
+
289
+ define :b do
290
+ condition { false }
291
+ result { 'B' }
292
+ end
293
+ """
294
+ And our rule is:
295
+ """
296
+ include_rule CommonRule
297
+ """
298
+ Then all the outcomes are "A, B"
299
+ And the result is "A"