rulezilla 0.1.4 → 0.4.1
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 +5 -5
- data/.github/workflows/ci.yaml +36 -0
- data/.github/workflows/codeql-analysis.yaml +70 -0
- data/.github/workflows/dobby-actions.yml +29 -0
- data/.github/workflows/gem-publish.yaml +46 -0
- data/.github/workflows/version-forget-me-not.yaml +19 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +61 -0
- data/.ruby-version +1 -0
- data/CODEOWNERS +3 -0
- data/Gemfile +2 -4
- data/LICENSE.txt +9 -0
- data/README.md +34 -28
- data/catalog-info.yaml +8 -0
- data/lib/rulezilla/basic_support.rb +3 -3
- data/lib/rulezilla/dsl.rb +27 -14
- data/lib/rulezilla/node.rb +10 -11
- data/lib/rulezilla/tree.rb +23 -14
- data/lib/rulezilla/version.rb +3 -1
- data/lib/rulezilla.rb +2 -31
- data/rulezilla.gemspec +15 -8
- data/spec/features/{rulezilla_dsl_framwork.feature → rulezilla_dsl_framework.feature} +50 -3
- data/spec/features/step_definitions/rulezilla_dsl_framework_steps.rb +36 -54
- data/spec/spec_helper.rb +33 -13
- metadata +74 -17
- data/Gemfile.lock +0 -44
- data/lib/rulezilla/rule_builder/gherkin_to_condition_rule.rb +0 -63
- data/lib/rulezilla/rule_builder/gherkin_to_result_rule.rb +0 -49
- data/lib/rulezilla/rule_builder.rb +0 -101
- data/spec/features/gherkin_dsl_framework.feature +0 -90
- data/spec/features/gherkin_rules/animal_rule.feature +0 -16
- data/spec/features/gherkin_rules/duration_rule.feature +0 -14
- data/spec/features/step_definitions/rule_steps.rb +0 -64
@@ -1,49 +0,0 @@
|
|
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
|
@@ -1,101 +0,0 @@
|
|
1
|
-
require 'gherkin/parser/parser'
|
2
|
-
require 'gherkin/formatter/json_formatter'
|
3
|
-
require 'stringio'
|
4
|
-
require 'json'
|
5
|
-
require 'rulezilla/rule_builder/gherkin_to_result_rule'
|
6
|
-
require 'rulezilla/rule_builder/gherkin_to_condition_rule'
|
7
|
-
|
8
|
-
module Rulezilla
|
9
|
-
class RuleBuilder
|
10
|
-
|
11
|
-
def self.from_file(name, file)
|
12
|
-
new(name, IO.read(file))
|
13
|
-
end
|
14
|
-
|
15
|
-
attr_reader :name, :content
|
16
|
-
|
17
|
-
def initialize(name, content)
|
18
|
-
@name = name
|
19
|
-
@content = content
|
20
|
-
end
|
21
|
-
|
22
|
-
def build
|
23
|
-
klass_definition = rules.map do |rule|
|
24
|
-
rule = RuleDefinition.new(rule, step_keyword)
|
25
|
-
|
26
|
-
condition_definition = rule.conditions.empty? ? "" : "condition { #{rule.conditions} }"
|
27
|
-
|
28
|
-
"""
|
29
|
-
define \"#{rule.name}\" do
|
30
|
-
#{condition_definition}
|
31
|
-
result(#{rule.result})
|
32
|
-
end
|
33
|
-
"""
|
34
|
-
end.join("\n")
|
35
|
-
|
36
|
-
klass = Rulezilla.const_set(name, Class.new)
|
37
|
-
|
38
|
-
klass.class_eval('include Rulezilla::DSL')
|
39
|
-
klass.class_eval(klass_definition)
|
40
|
-
|
41
|
-
klass
|
42
|
-
end
|
43
|
-
|
44
|
-
|
45
|
-
private
|
46
|
-
|
47
|
-
def step_keyword
|
48
|
-
gherkin_json.first['name'].gsub(/\s?rule/i, '')
|
49
|
-
end
|
50
|
-
|
51
|
-
def rules
|
52
|
-
gherkin_json.first['elements']
|
53
|
-
end
|
54
|
-
|
55
|
-
def gherkin_json
|
56
|
-
@gherkin_json ||= begin
|
57
|
-
io = StringIO.new
|
58
|
-
formatter = Gherkin::Formatter::JSONFormatter.new(io)
|
59
|
-
parser = Gherkin::Parser::Parser.new(formatter)
|
60
|
-
|
61
|
-
parser.parse(content, content, 0)
|
62
|
-
formatter.done
|
63
|
-
|
64
|
-
JSON.parse(io.string)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
|
69
|
-
class RuleDefinition
|
70
|
-
def initialize(gherkin_json, step_keyword)
|
71
|
-
@gherkin_json = gherkin_json
|
72
|
-
@step_keyword = step_keyword
|
73
|
-
end
|
74
|
-
|
75
|
-
def name
|
76
|
-
@gherkin_json['name']
|
77
|
-
end
|
78
|
-
|
79
|
-
def conditions
|
80
|
-
condition_steps = steps.reject{|step| step['keyword'].strip.downcase == 'then'}
|
81
|
-
conditions = condition_steps.map do |step|
|
82
|
-
::Rulezilla::RuleBuilder::GherkinToConditionRule.apply(record(step))
|
83
|
-
end.reject{|condition| condition == Rulezilla::RuleBuilder::DefaultCondition}.join(' && ')
|
84
|
-
end
|
85
|
-
|
86
|
-
def result
|
87
|
-
::Rulezilla::RuleBuilder::GherkinToResultRule.apply record(steps.detect{|step| step['keyword'].strip.downcase == 'then'})
|
88
|
-
end
|
89
|
-
|
90
|
-
private
|
91
|
-
|
92
|
-
def steps
|
93
|
-
@steps ||= @gherkin_json['steps']
|
94
|
-
end
|
95
|
-
|
96
|
-
def record(step)
|
97
|
-
step.merge(step_keyword: @step_keyword)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
@@ -1,90 +0,0 @@
|
|
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'"
|
@@ -1,16 +0,0 @@
|
|
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
|
@@ -1,14 +0,0 @@
|
|
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
|
@@ -1,64 +0,0 @@
|
|
1
|
-
steps_for :rule_steps do
|
2
|
-
|
3
|
-
step 'this is a "cat"' do
|
4
|
-
@records = [{cat?: true}]
|
5
|
-
end
|
6
|
-
|
7
|
-
step 'the :field is :value' do |field, value|
|
8
|
-
record ||= {}
|
9
|
-
field = field.gsub(/\s/,'_').downcase
|
10
|
-
|
11
|
-
value = case value
|
12
|
-
when 'blank'
|
13
|
-
""
|
14
|
-
when /^less than \d+ digits$/i
|
15
|
-
value.scan(/^less than \d+ digits$/i).flatten.first.to_i.times.map{Random.rand(10)}.join
|
16
|
-
else
|
17
|
-
value
|
18
|
-
end
|
19
|
-
|
20
|
-
record[field] = value
|
21
|
-
@records = [record]
|
22
|
-
end
|
23
|
-
|
24
|
-
step 'the :field is in:' do |field, values|
|
25
|
-
@records = []
|
26
|
-
field = field.gsub(/\s/,'_').downcase
|
27
|
-
values.raw.flatten.each do |telephone_number|
|
28
|
-
@records << { field => telephone_number }
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
step 'this is an animal' do
|
33
|
-
@records.each do |record|
|
34
|
-
expect(Rulezilla::AnimalRule.apply(record)).to eq true
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
step 'this is not an animal' do
|
39
|
-
@records.each do |record|
|
40
|
-
expect(Rulezilla::AnimalRule.apply(record)).to eq false
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
step 'none of the above' do
|
45
|
-
@records = [{telephone_number: '09827364755'}]
|
46
|
-
end
|
47
|
-
|
48
|
-
step 'the duration is :day days' do |day|
|
49
|
-
send 'the duration is :second seconds', day.to_i * 86400
|
50
|
-
end
|
51
|
-
|
52
|
-
step 'the duration is :hour hours' do |hour|
|
53
|
-
send 'the duration is :second seconds', hour.to_i * 3600
|
54
|
-
end
|
55
|
-
|
56
|
-
step 'the duration is :minute minute' do |minute|
|
57
|
-
send 'the duration is :second seconds', minute.to_i * 60
|
58
|
-
end
|
59
|
-
|
60
|
-
step 'the duration is :second seconds' do |second|
|
61
|
-
expect(Rulezilla::DurationRule.apply(@records.first)).to eq second.to_i
|
62
|
-
end
|
63
|
-
|
64
|
-
end
|