casegen 2.0.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +109 -0
- data/.ruby-version +1 -1
- data/Gemfile +3 -1
- data/Gemfile.lock +51 -6
- data/README.md +10 -119
- data/Rakefile +9 -7
- data/bin/casegen +2 -1
- data/casegen.gemspec +13 -9
- data/doc/bounding_box.rb +37 -0
- data/doc/cart.rb +43 -0
- data/doc/expect_only.rb +28 -0
- data/doc/pricing.rb +50 -0
- data/doc/ruby_array.rb +41 -0
- data/lib/case_gen/combination.rb +38 -0
- data/lib/case_gen/combo_matcher.rb +15 -0
- data/lib/case_gen/exclude_rule.rb +50 -0
- data/lib/case_gen/expect_rule.rb +24 -0
- data/lib/case_gen/generator.rb +40 -0
- data/lib/case_gen/output/exclude.rb +6 -0
- data/lib/case_gen/output/exclude_as_table.rb +13 -0
- data/lib/case_gen/output/exclude_as_text.rb +12 -0
- data/lib/case_gen/output/exclude_inline.rb +13 -0
- data/lib/case_gen/output/exclude_inline_footnotes.rb +20 -0
- data/lib/case_gen/output.rb +66 -0
- data/lib/case_gen/rule_description.rb +11 -0
- data/lib/case_gen/set.rb +16 -0
- data/lib/casegen.rb +15 -183
- data/spec/cart_sample_spec.rb +46 -0
- data/spec/case_gen/combination_spec.rb +11 -0
- data/spec/case_gen/exclude_rule_spec.rb +17 -0
- data/spec/exclude_as_table_spec.rb +39 -0
- data/spec/exclude_as_text_spec.rb +58 -0
- data/spec/exclude_inline_footnotes_spec.rb +58 -0
- data/spec/exclude_inline_spec.rb +50 -0
- data/spec/expect_only_spec.rb +30 -0
- data/spec/spec_helper.rb +113 -0
- metadata +101 -35
- data/.idea/encodings.xml +0 -5
- data/.idea/misc.xml +0 -5
- data/.idea/modules.xml +0 -9
- data/.idea/vcs.xml +0 -7
- data/doc/calc.sample.txt +0 -13
- data/doc/cart.sample.rb +0 -3
- data/doc/cart.sample.txt +0 -33
- data/doc/ruby_array.sample.rb +0 -26
- data/lib/agents/sets/enum/by.rb +0 -244
- data/lib/agents/sets/enum/cluster.rb +0 -164
- data/lib/agents/sets/enum/inject.rb +0 -50
- data/lib/agents/sets/enum/nest.rb +0 -117
- data/lib/agents/sets/enum/op.rb +0 -283
- data/lib/agents/sets/enum/pipe.rb +0 -160
- data/lib/agents/sets/enum/tree.rb +0 -442
- data/lib/agents/sets.rb +0 -313
- data/test/agents/console_output_test.rb +0 -27
- data/test/agents/sets.test.rb +0 -227
- data/test/agents_test.rb +0 -41
- data/test/casegen.tests.rb +0 -0
- data/test/parser_test.rb +0 -163
- data/test/test_helper.rb +0 -2
data/lib/case_gen/set.rb
ADDED
data/lib/casegen.rb
CHANGED
@@ -1,185 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
attr_reader :data, :reference_agents
|
17
|
-
|
18
|
-
def initialize(data, agents)
|
19
|
-
@data = data
|
20
|
-
@reference_agents = agents
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
class Agents
|
25
|
-
include Singleton
|
26
|
-
|
27
|
-
def initialize
|
28
|
-
clear
|
29
|
-
end
|
30
|
-
|
31
|
-
def register(agent)
|
32
|
-
if agent.class == Class && agent.ancestors.include?(Agent)
|
33
|
-
@agents << agent
|
34
|
-
else
|
35
|
-
raise AgentException.new("To register an agent, you must pass in a Class instance that's a subclass of Agent")
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def method_missing(methid, *args, &block)
|
40
|
-
@agents.send(methid, *args, &block)
|
41
|
-
end
|
42
|
-
|
43
|
-
def get_agent_by_id(id)
|
44
|
-
@agents.each do |agent|
|
45
|
-
return agent if agent.agent_id =~ /#{id}/i
|
46
|
-
end
|
47
|
-
# rather than return nil and allow the client to get bumfuzzled if
|
48
|
-
# they forget to check for nil, let's blow up and tell them how to
|
49
|
-
# not let it happen again
|
50
|
-
raise AgentException.new("Requested an agent that does not exist. You can query for existance with .id_registered?")
|
51
|
-
end
|
52
|
-
|
53
|
-
def id_registered?(id)
|
54
|
-
begin
|
55
|
-
get_agent_by_id(id)
|
56
|
-
return true
|
57
|
-
rescue AgentException
|
58
|
-
return false
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
# removes all registered agents
|
63
|
-
def clear
|
64
|
-
@agents = []
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
class ParserException < Exception
|
69
|
-
end
|
70
|
-
|
71
|
-
class Parser
|
72
|
-
attr_reader :agents
|
73
|
-
|
74
|
-
def initialize(data)
|
75
|
-
@data = data
|
76
|
-
@agents = []
|
77
|
-
parse
|
78
|
-
end
|
79
|
-
|
80
|
-
def parse
|
81
|
-
lines = @data.split(/\n/)
|
82
|
-
while !lines.empty?
|
83
|
-
line = lines.shift
|
84
|
-
next if line.strip.empty?
|
85
|
-
|
86
|
-
data = nil
|
87
|
-
agent_class, reference_agents = parse_agent(line)
|
88
|
-
|
89
|
-
next_line = lines.shift
|
90
|
-
if next_line =~ /^-+/
|
91
|
-
data = parse_data(lines).join("\n")
|
92
|
-
else
|
93
|
-
raise ParserException.new("Expected hyphen line after the agent declaration for <#{agent_class}>")
|
94
|
-
end
|
95
|
-
|
96
|
-
@agents << agent_class.new(data, reference_agents)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def parse_data(lines)
|
101
|
-
end_index = -1
|
102
|
-
lines.each_with_index do |line, index|
|
103
|
-
if line =~ /^-+/
|
104
|
-
end_index = index - 2
|
105
|
-
return lines.slice!(0, end_index)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
return lines.slice!(0, lines.length)
|
109
|
-
end
|
110
|
-
|
111
|
-
def parse_agent(line)
|
112
|
-
agent_name, *reference_agent_names = line.split(/(?=\()/)
|
113
|
-
raise ParserException.new("Nested agents ( e.g. a(b(c)) ) not supported yet") if reference_agent_names.length > 1
|
114
|
-
if reference_agent_names.length > 0
|
115
|
-
reference_agent_names = reference_agent_names[0].gsub(/\(|\)/, '').split(/,/)
|
116
|
-
reference_agent_names.collect! do |name|
|
117
|
-
name.strip
|
118
|
-
end
|
119
|
-
else
|
120
|
-
reference_agent_names = []
|
121
|
-
end
|
122
|
-
|
123
|
-
[agent_name, reference_agent_names].flatten.each do |a_name|
|
124
|
-
raise ParserException.new("Unregistered agent <#{a_name}> in agent name data <#{line}>") if !Agents.instance.id_registered?(a_name)
|
125
|
-
end
|
126
|
-
|
127
|
-
reference_agents = []
|
128
|
-
reference_agent_names.each do |ref_name|
|
129
|
-
@agents.each do |agent|
|
130
|
-
reference_agents << agent if agent.class.agent_id =~ /#{ref_name}/i
|
131
|
-
end
|
132
|
-
end
|
133
|
-
agent_class = Agents.instance.get_agent_by_id(agent_name)
|
134
|
-
[agent_class, reference_agents]
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
class CaseGen
|
139
|
-
def CaseGen.version
|
140
|
-
'2.0.0'
|
141
|
-
end
|
142
|
-
|
143
|
-
def initialize(data)
|
144
|
-
load_agents
|
145
|
-
Parser.new(data)
|
146
|
-
end
|
147
|
-
|
148
|
-
def load_agents
|
149
|
-
agent_dir = "#{File.dirname(__FILE__)}/agents"
|
150
|
-
agent_fns = Dir[File.join(agent_dir, '*.rb')]
|
151
|
-
agent_fns.each do |fn|
|
152
|
-
require fn
|
153
|
-
end
|
154
|
-
ObjectSpace.each_object(Class) do |klass|
|
155
|
-
if klass.ancestors.include?(Agent) && (klass != Agent)
|
156
|
-
Agents.instance.register(klass)
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
class Console
|
163
|
-
def initialize
|
164
|
-
put_banner
|
165
|
-
|
166
|
-
if ARGV[0].nil? || !File.exists?(ARGV[0])
|
167
|
-
puts "Case file required: #{File.basename($0)} [case filename]. For example:"
|
168
|
-
puts " #{File.basename($0)} cases.txt"
|
169
|
-
puts
|
170
|
-
exit
|
171
|
-
end
|
172
|
-
|
173
|
-
CaseGen.new(File.read(ARGV[0]))
|
174
|
-
end
|
175
|
-
|
176
|
-
def put_banner
|
177
|
-
$stderr.puts "cLabs Casegen #{CaseGen.version}"
|
178
|
-
end
|
179
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'case_gen/rule_description'
|
4
|
+
require_relative 'case_gen/set'
|
5
|
+
require_relative 'case_gen/combo_matcher'
|
6
|
+
require_relative 'case_gen/exclude_rule'
|
7
|
+
require_relative 'case_gen/expect_rule'
|
8
|
+
require_relative 'case_gen/combination'
|
9
|
+
require_relative 'case_gen/generator'
|
10
|
+
require_relative 'case_gen/output'
|
11
|
+
|
12
|
+
module CaseGen
|
13
|
+
def self.generate(sets, rules, output_type = :exclude)
|
14
|
+
generator = CaseGen::Generator.new(sets, rules)
|
15
|
+
CaseGen::Output.create(generator, output_type).to_s
|
180
16
|
end
|
181
17
|
end
|
182
|
-
|
183
|
-
if __FILE__ == $0
|
184
|
-
CLabs::CaseGen::Console.new
|
185
|
-
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe 'Cart Sample' do
|
6
|
+
let(:expected) do
|
7
|
+
<<~_
|
8
|
+
+-------------+--------+----------+-----------------+-----------------+
|
9
|
+
| payment | amount | shipping | ship_to_country | bill_to_country |
|
10
|
+
+-------------+--------+----------+-----------------+-----------------+
|
11
|
+
| Credit | 100 | Ground | US | US |
|
12
|
+
| Credit | 100 | Air | US | US |
|
13
|
+
| Credit | 100 | Air | Outside US | US |
|
14
|
+
| Credit | 100 | Air | Outside US | Outside US |
|
15
|
+
| Credit | 1000 | Ground | US | US |
|
16
|
+
| Credit | 1000 | Air | US | US |
|
17
|
+
| Credit | 1000 | Air | Outside US | US |
|
18
|
+
| Credit | 1000 | Air | Outside US | Outside US |
|
19
|
+
| Credit | 10000 | Ground | US | US |
|
20
|
+
| Credit | 10000 | Air | US | US |
|
21
|
+
| Credit | 10000 | Air | Outside US | US |
|
22
|
+
| Credit | 10000 | Air | Outside US | Outside US |
|
23
|
+
| Check | 100 | Ground | US | US |
|
24
|
+
| Check | 100 | Air | US | US |
|
25
|
+
| Check | 100 | Air | Outside US | US |
|
26
|
+
| Check | 1000 | Ground | US | US |
|
27
|
+
| Check | 1000 | Air | US | US |
|
28
|
+
| Check | 1000 | Air | Outside US | US |
|
29
|
+
| Check | 10000 | Ground | US | US |
|
30
|
+
| Check | 10000 | Air | US | US |
|
31
|
+
| Check | 10000 | Air | Outside US | US |
|
32
|
+
| Online Bank | 100 | Ground | US | US |
|
33
|
+
| Online Bank | 100 | Air | US | US |
|
34
|
+
| Online Bank | 100 | Air | Outside US | US |
|
35
|
+
| Online Bank | 100 | Air | Outside US | Outside US |
|
36
|
+
+-------------+--------+----------+-----------------+-----------------+
|
37
|
+
_
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'works' do
|
41
|
+
require_relative '../doc/cart'
|
42
|
+
|
43
|
+
fix = Fixtures[:cart]
|
44
|
+
expect(CaseGen.generate(fix[:sets], fix[:rules])).to eq expected
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe CaseGen::ExcludeRule do
|
4
|
+
it 'removes expect if excluded inline' do
|
5
|
+
combo = CaseGen::Combination.new([{a: 1}, {b: :expect}])
|
6
|
+
rule = described_class.new({criteria: 'a == 1', note: 'nope'})
|
7
|
+
rule.apply([combo])
|
8
|
+
expect(combo).to be_excluded
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'marks combo as exclude on matching key/values in rule data' do
|
12
|
+
combo = CaseGen::Combination.new([{a: 1}, {b: 2}])
|
13
|
+
rule = described_class.new({b: 2, note: 'nope'})
|
14
|
+
rule.apply([combo])
|
15
|
+
expect(combo).to be_excluded
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require_relative '../doc/bounding_box'
|
5
|
+
|
6
|
+
RSpec.describe 'Exclude as table' do
|
7
|
+
let(:expected_combo_table) do
|
8
|
+
<<~_
|
9
|
+
+---------+---------+--------+---------+
|
10
|
+
| width | height | aspect | result |
|
11
|
+
+---------+---------+--------+---------+
|
12
|
+
| inside | inside | wide | 200x100 |
|
13
|
+
| inside | inside | tall | 100x200 |
|
14
|
+
| inside | outside | tall | 100x400 |
|
15
|
+
| outside | inside | wide | 400x100 |
|
16
|
+
| outside | outside | wide | 500x400 |
|
17
|
+
| outside | outside | tall | 400x500 |
|
18
|
+
+---------+---------+--------+---------+
|
19
|
+
_
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:expected_exclude_as_table) do
|
23
|
+
<<~_
|
24
|
+
+---------+---------+--------+--------+--------------------------------------------+
|
25
|
+
| width | height | aspect | result | exclude |
|
26
|
+
+---------+---------+--------+--------+--------------------------------------------+
|
27
|
+
| inside | outside | wide | | a narrower image cannot have a wide aspect |
|
28
|
+
| outside | inside | tall | | a shorter image cannot have a tall aspect |
|
29
|
+
+---------+---------+--------+--------+--------------------------------------------+
|
30
|
+
_
|
31
|
+
end
|
32
|
+
|
33
|
+
let(:fix) { Fixtures[:box] }
|
34
|
+
|
35
|
+
it 'output' do
|
36
|
+
output = CaseGen.generate(fix[:sets], fix[:rules], :exclude_as_table)
|
37
|
+
expect(output.to_s).to eq "#{expected_combo_table}\n#{expected_exclude_as_table}"
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require_relative '../doc/pricing'
|
5
|
+
|
6
|
+
RSpec.describe 'Exclude as text' do
|
7
|
+
let(:expected_combo_table) do
|
8
|
+
<<~_
|
9
|
+
+----------+----------+-------+--------+
|
10
|
+
| subtotal | discount | promo | total |
|
11
|
+
+----------+----------+-------+--------+
|
12
|
+
| 25 | 0% | none | 25.00 |
|
13
|
+
| 25 | 0% | apr | 17.50 |
|
14
|
+
| 25 | 0% | fall | 16.25 |
|
15
|
+
| 75 | 10% | none | 67.50 |
|
16
|
+
| 75 | 10% | apr | 47.25 |
|
17
|
+
| 75 | 10% | fall | 43.88 |
|
18
|
+
| 200 | 20% | none | 160.00 |
|
19
|
+
+----------+----------+-------+--------+
|
20
|
+
_
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:expected_exclude_as_text) do
|
24
|
+
<<~_
|
25
|
+
exclude
|
26
|
+
-------
|
27
|
+
subtotal < 100 && discount == '20%'
|
28
|
+
Total must be above $100 to apply the 20% discount
|
29
|
+
|
30
|
+
(subtotal < 50) && discount == '10%'
|
31
|
+
Total must be above $50 to apply the 10% discount
|
32
|
+
|
33
|
+
discount != '20%' && subtotal == 200
|
34
|
+
Orders over 100 automatically get 20% discount
|
35
|
+
|
36
|
+
discount != '10%' && subtotal == 75
|
37
|
+
Orders between 50 and 100 automatically get 10% discount
|
38
|
+
|
39
|
+
discount == '20%' && promo != 'none'
|
40
|
+
20% discount cannot be combined with promo
|
41
|
+
_
|
42
|
+
end
|
43
|
+
|
44
|
+
let(:generator) do
|
45
|
+
fix = Fixtures[:pricing]
|
46
|
+
CaseGen::Generator.new(fix[:sets], fix[:rules])
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'output only combos table' do
|
50
|
+
output = CaseGen::Exclude.new(generator)
|
51
|
+
expect(output.to_s).to eq expected_combo_table
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'outputs with exclude as text' do
|
55
|
+
output = CaseGen::ExcludeAsText.new(generator)
|
56
|
+
expect(output.to_s).to eq "#{expected_combo_table}\n#{expected_exclude_as_text}"
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require_relative '../doc/pricing'
|
5
|
+
|
6
|
+
RSpec.describe 'Exclude inline with footnotes' do
|
7
|
+
let(:expected_table) do
|
8
|
+
<<~_
|
9
|
+
+----------+----------+-------+--------+---------+
|
10
|
+
| subtotal | discount | promo | total | exclude |
|
11
|
+
+----------+----------+-------+--------+---------+
|
12
|
+
| 25 | 0% | none | 25.00 | |
|
13
|
+
| 25 | 0% | apr | 17.50 | |
|
14
|
+
| 25 | 0% | fall | 16.25 | |
|
15
|
+
| 25 | 10% | none | | [2] |
|
16
|
+
| 25 | 10% | apr | | [2] |
|
17
|
+
| 25 | 10% | fall | | [2] |
|
18
|
+
| 25 | 20% | none | | [1] |
|
19
|
+
| 25 | 20% | apr | | [1] |
|
20
|
+
| 25 | 20% | fall | | [1] |
|
21
|
+
| 75 | 0% | none | | [4] |
|
22
|
+
| 75 | 0% | apr | | [4] |
|
23
|
+
| 75 | 0% | fall | | [4] |
|
24
|
+
| 75 | 10% | none | 67.50 | |
|
25
|
+
| 75 | 10% | apr | 47.25 | |
|
26
|
+
| 75 | 10% | fall | 43.88 | |
|
27
|
+
| 75 | 20% | none | | [1] |
|
28
|
+
| 75 | 20% | apr | | [1] |
|
29
|
+
| 75 | 20% | fall | | [1] |
|
30
|
+
| 200 | 0% | none | | [3] |
|
31
|
+
| 200 | 0% | apr | | [3] |
|
32
|
+
| 200 | 0% | fall | | [3] |
|
33
|
+
| 200 | 10% | none | | [3] |
|
34
|
+
| 200 | 10% | apr | | [3] |
|
35
|
+
| 200 | 10% | fall | | [3] |
|
36
|
+
| 200 | 20% | none | 160.00 | |
|
37
|
+
| 200 | 20% | apr | | [5] |
|
38
|
+
| 200 | 20% | fall | | [5] |
|
39
|
+
+----------+----------+-------+--------+---------+
|
40
|
+
|
41
|
+
exclude
|
42
|
+
-------
|
43
|
+
[1] Total must be above $100 to apply the 20% discount
|
44
|
+
[2] Total must be above $50 to apply the 10% discount
|
45
|
+
[3] Orders over 100 automatically get 20% discount
|
46
|
+
[4] Orders between 50 and 100 automatically get 10% discount
|
47
|
+
[5] 20% discount cannot be combined with promo
|
48
|
+
_
|
49
|
+
end
|
50
|
+
|
51
|
+
let(:sets) { Fixtures[:pricing][:sets] }
|
52
|
+
let(:rules) { Fixtures[:pricing][:rules] }
|
53
|
+
|
54
|
+
it 'output' do
|
55
|
+
result = CaseGen.generate(sets, rules, :exclude_inline_footnotes)
|
56
|
+
expect(result).to eq expected_table.chomp
|
57
|
+
end
|
58
|
+
end
|