casegen 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.rubocop.yml +109 -0
  4. data/.ruby-version +1 -1
  5. data/Gemfile +3 -1
  6. data/Gemfile.lock +51 -6
  7. data/README.md +10 -119
  8. data/Rakefile +9 -7
  9. data/bin/casegen +2 -1
  10. data/casegen.gemspec +13 -9
  11. data/doc/bounding_box.rb +37 -0
  12. data/doc/cart.rb +43 -0
  13. data/doc/expect_only.rb +28 -0
  14. data/doc/pricing.rb +50 -0
  15. data/doc/ruby_array.rb +41 -0
  16. data/lib/case_gen/combination.rb +38 -0
  17. data/lib/case_gen/combo_matcher.rb +15 -0
  18. data/lib/case_gen/exclude_rule.rb +50 -0
  19. data/lib/case_gen/expect_rule.rb +24 -0
  20. data/lib/case_gen/generator.rb +40 -0
  21. data/lib/case_gen/output/exclude.rb +6 -0
  22. data/lib/case_gen/output/exclude_as_table.rb +13 -0
  23. data/lib/case_gen/output/exclude_as_text.rb +12 -0
  24. data/lib/case_gen/output/exclude_inline.rb +13 -0
  25. data/lib/case_gen/output/exclude_inline_footnotes.rb +20 -0
  26. data/lib/case_gen/output.rb +66 -0
  27. data/lib/case_gen/rule_description.rb +11 -0
  28. data/lib/case_gen/set.rb +16 -0
  29. data/lib/casegen.rb +15 -183
  30. data/spec/cart_sample_spec.rb +46 -0
  31. data/spec/case_gen/combination_spec.rb +11 -0
  32. data/spec/case_gen/exclude_rule_spec.rb +17 -0
  33. data/spec/exclude_as_table_spec.rb +39 -0
  34. data/spec/exclude_as_text_spec.rb +58 -0
  35. data/spec/exclude_inline_footnotes_spec.rb +58 -0
  36. data/spec/exclude_inline_spec.rb +50 -0
  37. data/spec/expect_only_spec.rb +30 -0
  38. data/spec/spec_helper.rb +113 -0
  39. metadata +101 -35
  40. data/.idea/encodings.xml +0 -5
  41. data/.idea/misc.xml +0 -5
  42. data/.idea/modules.xml +0 -9
  43. data/.idea/vcs.xml +0 -7
  44. data/doc/calc.sample.txt +0 -13
  45. data/doc/cart.sample.rb +0 -3
  46. data/doc/cart.sample.txt +0 -33
  47. data/doc/ruby_array.sample.rb +0 -26
  48. data/lib/agents/sets/enum/by.rb +0 -244
  49. data/lib/agents/sets/enum/cluster.rb +0 -164
  50. data/lib/agents/sets/enum/inject.rb +0 -50
  51. data/lib/agents/sets/enum/nest.rb +0 -117
  52. data/lib/agents/sets/enum/op.rb +0 -283
  53. data/lib/agents/sets/enum/pipe.rb +0 -160
  54. data/lib/agents/sets/enum/tree.rb +0 -442
  55. data/lib/agents/sets.rb +0 -313
  56. data/test/agents/console_output_test.rb +0 -27
  57. data/test/agents/sets.test.rb +0 -227
  58. data/test/agents_test.rb +0 -41
  59. data/test/casegen.tests.rb +0 -0
  60. data/test/parser_test.rb +0 -163
  61. data/test/test_helper.rb +0 -2
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CaseGen
4
+ module RuleDescription
5
+ def rule_description(rule)
6
+ keys = %i[description note reason]
7
+ key = (rule.keys & keys).first
8
+ rule[key]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CaseGen
4
+ class Set
5
+ attr_reader :title, :values
6
+
7
+ def initialize(title, values)
8
+ @title = title
9
+ @values = values
10
+ end
11
+
12
+ def hash_pairs
13
+ @values.map { |v| {@title => v} }
14
+ end
15
+ end
16
+ end
data/lib/casegen.rb CHANGED
@@ -1,185 +1,17 @@
1
- require 'singleton'
2
-
3
- class String
4
- def outdent
5
- a = $1 if match(/\A(\s*)(.*\n)(?:\1.*\n|\n)*\z/)
6
- gsub(/^#{a}/, '')
7
- end
8
- end
9
-
10
- module CLabs
11
- module CaseGen
12
- class AgentException < Exception
13
- end
14
-
15
- class Agent # base class
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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe CaseGen::Combination do
6
+ it 'append' do
7
+ combo = described_class.new([{a: 1}, {b: 2}])
8
+ combo.append(:c, 3)
9
+ expect(combo.hash_row).to eq({a: 1, b: 2, c: 3})
10
+ end
11
+ 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