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.
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