rulezilla 0.1.4 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|