casegen 2.0.0 → 3.0.0
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/.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
|