casegen 2.0.0 → 3.0.1

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 +63 -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 +103 -40
  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
data/lib/agents/sets.rb DELETED
@@ -1,313 +0,0 @@
1
- require "#{File.dirname(__FILE__)}/../casegen"
2
- $LOAD_PATH << "#{File.expand_path(File.join(File.dirname(__FILE__), 'sets'))}"
3
- require 'enum/op'
4
- require 'tablesmith'
5
-
6
- class String
7
- def to_u
8
- self.gsub(/ /, '_')
9
- end
10
- end
11
-
12
- module CLabs::CaseGen
13
- class Set
14
- attr_reader :name, :data
15
-
16
- def initialize(name, data_array)
17
- @name = name
18
- @data = data_array
19
- strip_data
20
- end
21
-
22
- def strip_data
23
- @data.collect! do |datum| datum.strip end
24
- end
25
-
26
- def values
27
- @data
28
- end
29
- end
30
-
31
- class Sets < Agent
32
- attr_accessor :sets, :combinations, :set_titles
33
-
34
- def Sets.agent_id
35
- "casegen:sets"
36
- end
37
-
38
- def initialize(data, reference_agents=nil)
39
- @data = data
40
- @sets = []
41
- parse_sets
42
- end
43
-
44
- def parse_sets
45
- set_names = @data.scan(/^\s*(\w.*):/)
46
- set_data = @data.scan(/:(.*)/)
47
- sets = set_names.flatten.zip(set_data.flatten)
48
- sets.each do |set_array|
49
- @sets << Set.new(set_array[0], set_array[1].split(/, /))
50
- end
51
- generate_combinations
52
- end
53
-
54
- def generate_combinations
55
- arrays = []
56
- @set_titles = []
57
- @sets.each do |set| arrays << set.data; @set_titles << set.name end
58
- @combinations = all(*arrays)
59
- end
60
-
61
- def titles
62
- @set_titles
63
- end
64
-
65
- def all(*args)
66
- result = []
67
- EnumerableOperator::Product.new(*args).each { |tuple|
68
- result << tuple
69
- }
70
- result
71
- end
72
-
73
- def set_names
74
- names = []
75
- @sets.each do |set| names << set.name end
76
- names
77
- end
78
-
79
- def set_by_name(setname)
80
- @sets.detect do |set| set.name =~ /#{Regexp.escape(setname)}/ end
81
- end
82
-
83
- end
84
-
85
- class Criteria
86
- attr_reader :set_names, :set_values, :equalities
87
-
88
- def initialize(data)
89
- @data = data
90
- @equalities = {}
91
- parse
92
- end
93
-
94
- def parse
95
- @data.split(/AND/).each do |bit|
96
- set_name, set_value = bit.split(/==|=/)
97
- set_name.strip!; set_value.strip!
98
- if @equalities.keys.include?(set_name)
99
- raise ParserException.new("Rule cannot have the same set <#{set_name}> equal to different values <#{@equalities[set_name]}, #{set_value}>")
100
- end
101
- @equalities[set_name] = set_value
102
- end
103
- @set_names = @equalities.keys
104
- @set_values = @equalities.values
105
- end
106
-
107
- # hash keys should be valid set names and hash values should be valid
108
- # set values in the named set
109
- def match(hash)
110
- # must match all equalities
111
- @equalities.each_pair do |eq_name, eq_value|
112
- actual_value = hash[eq_name]
113
- return false if actual_value.nil?
114
- return false if actual_value != eq_value
115
- end
116
- return true
117
- end
118
-
119
- def to_s
120
- @data
121
- end
122
- end
123
-
124
- class Rule
125
- attr_reader :criteria, :description, :data
126
-
127
- def initialize(rule_data)
128
- @data = rule_data
129
- parse_rule
130
- end
131
-
132
- def parse_rule
133
- data = @data.sub(self.class.regexp, '')
134
- criteria_data, *@description = data.split(/\n/)
135
- criteria_data.strip!
136
- @criteria = Criteria.new(criteria_data)
137
- @description = (@description.join("\n") + "\n").outdent.strip
138
- end
139
- end
140
-
141
- class ExcludeRule < Rule
142
- def type_description
143
- "exclude"
144
- end
145
-
146
- def ExcludeRule.regexp
147
- /^exclude/i
148
- end
149
-
150
- def ExcludeRule.create(rule_data)
151
- return ExcludeRule.new(rule_data) if rule_data =~ regexp
152
- return nil
153
- end
154
- end
155
-
156
- class Rules < Agent
157
- def Rules.agent_id
158
- "casegen:rules"
159
- end
160
-
161
- def initialize(data, reference_agents=[])
162
- @data = data
163
- @agents = reference_agents
164
- @rules = []
165
- @rule_classes = []
166
- ObjectSpace.each_object(Class) do |obj|
167
- @rule_classes << obj if obj.ancestors.include?(Rule) && obj != Rule
168
- end
169
- parse_data
170
- end
171
-
172
- def parse_data
173
- raw_rules = @data.split(/(?=^\S)/)
174
-
175
- raw_rules.each do |rule|
176
- @rule_classes.each do |rule_class|
177
- @rules << rule_class.create(rule.strip)
178
- end
179
- end
180
- @rules.compact!
181
- @rules.flatten!
182
- validate_rules
183
- end
184
-
185
- def validate_rules
186
- @agents.each do |agent|
187
- if agent.class == Sets
188
- @rules.each do |rule|
189
- rule.criteria.equalities.each_pair do |set_name, set_value|
190
- set = agent.set_by_name(set_name)
191
- if set.nil?
192
- raise ParserException.new("Invalid set name <#{set_name}> " +
193
- "in rule <#{rule.criteria}>. Valid set names are <#{agent.set_names.join(', ')}>.")
194
- end
195
- if !set.values.include?(set_value)
196
- raise ParserException.new("Invalid set value <#{set_value}> " +
197
- "in rule <#{rule.criteria}>. Valid set values for <#{set.name}> " +
198
- "are <#{set.values.join(', ')}>.")
199
- end
200
- end
201
- end
202
- end
203
- end
204
- end
205
-
206
- def length
207
- @rules.length
208
- end
209
-
210
- def [](index)
211
- return @rules[index]
212
- end
213
-
214
- def each(&block)
215
- @rules.each(&block)
216
- end
217
-
218
- def combinations
219
- return @combinations if !@combinations.nil?
220
- if @agents[0].class == Sets
221
- agent = @agents[0]
222
- @combinations = []
223
- agent.combinations.each do |combo|
224
- delete = false
225
- combo_hash = {}
226
- i = 0
227
- # combo is an array of values, in the same order of the set_titles.
228
- # combo_hash will have set names matched with set values
229
- agent.set_titles.each do |title|
230
- combo_hash[title] = combo[i]
231
- i += 1
232
- end
233
- @rules.each do |rule|
234
- delete |= rule.criteria.match(combo_hash)
235
- end
236
- @combinations << combo if !delete
237
- end
238
- return @combinations
239
- end
240
- return []
241
- end
242
-
243
- def titles
244
- @agents[0].titles
245
- end
246
-
247
- def to_s
248
- puts @agents[0].combinations.inspect if !@agents[0].nil?
249
- puts
250
- puts @rules.inspect
251
- end
252
- end
253
-
254
- class ConsoleOutput < Agent
255
- def ConsoleOutput.agent_id
256
- "casegen:console"
257
- end
258
-
259
- def initialize(data, reference_agents, io=STDOUT)
260
- @data = data
261
- @agents = reference_agents
262
- table = [@agents[0].titles] + @agents[0].combinations
263
- io.puts table.to_table.pretty_inspect
264
- io.puts
265
- @agents[0].each do |rule|
266
- io.puts rule.data
267
- io.puts
268
- end if @agents[0].is_a?(Rules)
269
- end
270
- end
271
-
272
- class RubyArrayOutput < Agent
273
- def self.agent_id
274
- "casegen:ruby_array"
275
- end
276
-
277
- def initialize(data, reference_agents, io=STDOUT)
278
- @io = io
279
- @struct_name = "Case"
280
- @struct_name = data if !data.empty?
281
- @agents = reference_agents
282
- @agents.each do |agent| execute(agent) end
283
- end
284
-
285
- def execute(agent)
286
- struct_header = "#{@struct_name} = Struct.new("
287
- struct = ''
288
- agent.titles.each do |title|
289
- struct << ', ' if !struct.empty?
290
- struct << ":#{title.to_u.downcase}"
291
- end
292
- struct << ')'
293
-
294
- guts_header = 'cases = ['
295
- guts = ''
296
- agent.combinations.each do |combo|
297
- guts << ",\n#{' ' * guts_header.length}" if !guts.empty?
298
- guts << "#{@struct_name}.new#{combo.inspect.gsub(/\[/, '(').gsub(/\]/, ')')}"
299
- end
300
- @io.print(struct_header)
301
- @io.print(struct)
302
- @io.print("\n\n")
303
- @io.print(guts_header)
304
- @io.print(guts)
305
- @io.print("]\n")
306
- end
307
- end
308
- end
309
-
310
- if __FILE__ == $0
311
- sets = CLabs::CaseGen::Sets.new("a: 1, 2\nb: 3, 4")
312
- puts sets.combinations
313
- end
@@ -1,27 +0,0 @@
1
- require_relative '../test_helper'
2
- require_relative '../../lib/agents/sets.rb'
3
-
4
- FakeAgent = Struct.new(:titles, :combinations)
5
-
6
- include CLabs::CaseGen
7
-
8
- class TestConsoleOutput < Minitest::Test
9
- def test_simple_output
10
- data = nil
11
- agents = [
12
- FakeAgent.new(["col a", "col b"], [[1, 2], [3, 4]])
13
- ]
14
- sio = StringIO.new
15
- ConsoleOutput.new(data, agents, sio)
16
- expected = <<~_
17
- +-------+-------+
18
- | col a | col b |
19
- +-------+-------+
20
- | 1 | 2 |
21
- | 3 | 4 |
22
- +-------+-------+
23
-
24
- _
25
- assert_equal expected, sio.string
26
- end
27
- end
@@ -1,227 +0,0 @@
1
- $LOAD_PATH << "#{__dir__}/../../lib/agents"
2
- require 'minitest/autorun'
3
- require 'sets'
4
-
5
- include CLabs::CaseGen
6
-
7
- class TestParsing < Minitest::Test
8
- def test_sets_default
9
- data = <<~CASEDATA
10
- bar: 1, 2, 3,456.98
11
- foo list:a, b, c
12
- CASEDATA
13
- cases = Sets.new(data)
14
- assert_equal(2, cases.sets.length)
15
-
16
- assert_equal("bar", cases.sets[0].name)
17
- assert_equal(["1", "2", "3,456.98"], cases.sets[0].data)
18
-
19
- assert_equal("foo list", cases.sets[1].name)
20
- assert_equal(["a", "b", "c"], cases.sets[1].data)
21
- end
22
-
23
- def test_set_by_name_matching
24
- # may seem obvious, but the regex matching used at one point would get confused when a name was reused in sets
25
- sets = Sets.new("foo bar: 1, 2\nbar quux: 3, 4")
26
- set = sets.set_by_name('bar quux')
27
- assert_equal 'bar quux', set.name
28
- end
29
- end
30
-
31
- class TestCombinations < Minitest::Test
32
- def test_combos_2_by_2
33
- sets = Sets.new("a: 1, 2\nb:3, 4")
34
- assert_equal([['1', '3'], ['1', '4'], ['2', '3'], ['2', '4']], sets.combinations)
35
- assert_equal(['a', 'b'], sets.titles)
36
- end
37
-
38
- def test_combos_2_by_3
39
- sets = Sets.new("a: 1, 2\nb:3, 4, 5")
40
- assert_equal([['1', '3'], ['1', '4'], ['1', '5'],
41
- ['2', '3'], ['2', '4'], ['2', '5']], sets.combinations)
42
- assert_equal(['a', 'b'], sets.titles)
43
- end
44
- end
45
-
46
- class TestRulesParsing < Minitest::Test
47
- def test_rules_single
48
- data = <<~RULES
49
- exclude foo = bar
50
- RULES
51
- rules = Rules.new(data)
52
- assert_equal(1, rules.length)
53
- assert_equal(ExcludeRule, rules[0].class)
54
- assert_equal("foo = bar", rules[0].criteria.to_s)
55
- assert_equal("", rules[0].description)
56
- end
57
-
58
- def test_rules_two
59
- data = <<~RULES
60
- exclude foo = bar
61
- should foo equal bar, we want to exclude that combination
62
-
63
- exclude bar = foo
64
- as well, if bar equals foo,
65
- that case has got to go
66
- RULES
67
- rules = Rules.new(data)
68
- assert_equal(2, rules.length)
69
- assert_equal(ExcludeRule, rules[0].class)
70
- assert_equal("foo = bar", rules[0].criteria.to_s)
71
- assert_equal("should foo equal bar, we want to exclude that combination", rules[0].description)
72
-
73
- assert_equal(ExcludeRule, rules[1].class)
74
- assert_equal("bar = foo", rules[1].criteria.to_s)
75
- assert_equal("as well, if bar equals foo,\nthat case has got to go", rules[1].description)
76
- end
77
-
78
- def test_exclude_rule_parsing
79
- data = <<~RULES
80
- exclude foo = bar
81
- should foo equal bar, we want to exclude that combination
82
- RULES
83
- rule = ExcludeRule.new(data)
84
- assert_equal("foo = bar", rule.criteria.to_s)
85
- assert_equal(["foo"], rule.criteria.set_names)
86
- assert_equal(["bar"], rule.criteria.set_values)
87
- assert_equal("should foo equal bar, we want to exclude that combination", rule.description)
88
-
89
- end
90
-
91
- def test_rules_set_name_not_found
92
- sets = Sets.new("set.a: foo, bar\nset.b: fu, bahr")
93
- data = <<~RULES
94
- exclude set_a = bar AND set_b = barh
95
- should foo equal bar, we want to exclude that combination
96
- RULES
97
- begin
98
- Rules.new(data, [sets])
99
- fail('should throw')
100
- rescue ParserException => e
101
- assert_equal("Invalid set name <set_a> in rule <set_a = bar AND set_b = barh>. Valid set names are <set.a, set.b>.", e.message)
102
- end
103
- end
104
-
105
- def test_rules_set_value_not_found
106
- sets = Sets.new("set a: foo, bar\nset b: fu, bahr")
107
- data = <<~RULES
108
- exclude set a = bar AND set b = barh
109
- should foo equal bar, we want to exclude that combination
110
- RULES
111
- begin
112
- Rules.new(data, [sets])
113
- fail('should throw')
114
- rescue ParserException => e
115
- assert_equal("Invalid set value <barh> in rule <set a = bar AND set b = barh>. Valid set values for <set b> are <fu, bahr>.", e.message)
116
- end
117
- end
118
-
119
- class TestCriteria < Minitest::Test
120
- def test_simple_equality
121
- crit = Criteria.new("a = b")
122
- assert_equal(['a'], crit.set_names)
123
- assert_equal(['b'], crit.set_values)
124
- assert_equal(true, crit.match({'a' => 'b'}))
125
- assert_equal(false, crit.match({'a' => 'c'}))
126
- assert_equal(false, crit.match({'b' => 'a'}))
127
- assert_equal(true, crit.match({'a' => 'b', 'f' => 'g'}))
128
- end
129
-
130
- def test_boolean_and
131
- crit = Criteria.new("a = b AND c == d")
132
- assert_equal(['a', 'c'], crit.set_names)
133
- assert_equal(['b', 'd'], crit.set_values)
134
- assert_equal(true, crit.match({'a' => 'b', 'c' => 'd'}))
135
- assert_equal(false, crit.match({'a' => 'd', 'c' => 'b'}))
136
- assert_equal(true, crit.match({'c' => 'd', 'a' => 'b'}))
137
- assert_equal(false, crit.match({'a' => 'b'}))
138
- assert_equal(false, crit.match({'c' => 'd'}))
139
- assert_equal(false, crit.match({'a' => 'b', 'd' => 'c'}))
140
- assert_equal(false, crit.match({'c' => 'd', 'b' => 'a'}))
141
-
142
- # not case sensitive
143
- assert_equal(false, crit.match({'A' => 'b', 'c' => 'd'}))
144
- assert_equal(false, crit.match({'a' => 'B', 'c' => 'd'}))
145
- assert_equal(false, crit.match({'a' => 'b', 'C' => 'd'}))
146
- assert_equal(false, crit.match({'a' => 'b', 'c' => 'D'}))
147
- end
148
-
149
- def test_invalid_boolean_and
150
- begin
151
- Criteria.new("a = b AND a = d")
152
- fail("should throw")
153
- rescue ParserException => e
154
- assert_equal("Rule cannot have the same set <a> equal to different values <b, d>", e.message)
155
- end
156
-
157
- begin
158
- Criteria.new("a = b AND a = d AND a = c")
159
- fail("should throw")
160
- rescue ParserException => e
161
- # in this case, the exception is figured out before the a = c can be parsed
162
- assert_equal("Rule cannot have the same set <a> equal to different values <b, d>", e.message)
163
- end
164
- end
165
- end
166
-
167
- class TestRulesOnSets < Minitest::Test
168
- def test_simple
169
- sets = Sets.new("a: 1, 2\nb: 3, 4")
170
- rules = Rules.new("exclude a = 1\nexclude b=4", [sets])
171
- assert_equal([['2', '3']], rules.combinations)
172
- assert_equal(['a', 'b'], rules.titles)
173
- end
174
- end
175
-
176
- class TestRubyCaseArray < Minitest::Test
177
- def test_default_case_name
178
- sets = Sets.new("a: 1, 2\nb:3, 4")
179
- out = MockStdOut.new
180
- RubyArrayOutput.new("", [sets], out)
181
- expected = <<~TEXT
182
- Case = Struct.new(:a, :b)
183
-
184
- cases = [Case.new("1", "3"),
185
- Case.new("1", "4"),
186
- Case.new("2", "3"),
187
- Case.new("2", "4")]
188
- TEXT
189
- assert_equal(expected, out.to_s)
190
- end
191
-
192
- def test_specified_case_name
193
- sets = Sets.new("a: 1, 2\nb:3, 4")
194
- out = MockStdOut.new
195
- RubyArrayOutput.new("DataSubmitCase", [sets], out)
196
- expected = <<~TEXT
197
- DataSubmitCase = Struct.new(:a, :b)
198
-
199
- cases = [DataSubmitCase.new("1", "3"),
200
- DataSubmitCase.new("1", "4"),
201
- DataSubmitCase.new("2", "3"),
202
- DataSubmitCase.new("2", "4")]
203
- TEXT
204
- assert_equal(expected, out.to_s)
205
- end
206
- end
207
-
208
- class MockStdOut
209
- def initialize
210
- @s = ''
211
- end
212
-
213
- def puts(s)
214
- @s << s << "\n"
215
- end
216
-
217
- def print(s)
218
- @s << s
219
- end
220
-
221
- def to_s
222
- @s
223
- end
224
- end
225
- end
226
-
227
-
data/test/agents_test.rb DELETED
@@ -1,41 +0,0 @@
1
- $LOAD_PATH << "#{__dir__}/../lib"
2
- require 'minitest/autorun'
3
- require 'casegen'
4
-
5
- include CLabs::CaseGen
6
-
7
- class TestAgents < Minitest::Test
8
- def setup
9
- Agents.instance.clear
10
- end
11
-
12
- def teardown
13
- Agents.instance.clear
14
- end
15
-
16
- def test_register
17
- assert_equal(0, Agents.instance.length)
18
- begin
19
- Agents.instance.register("phil")
20
- fail("should have thrown")
21
- rescue AgentException
22
- # expected -- can't register a non subclass
23
- end
24
-
25
- Agents.instance.register(SampleAgent)
26
- assert_equal(1, Agents.instance.length)
27
- Agents.instance.each do |ag| assert_equal(SampleAgent, ag) end
28
- end
29
-
30
- def test_get_agent_by_id
31
- Agents.instance.register(SampleAgent)
32
- result = Agents.instance.get_agent_by_id("sample")
33
- assert_equal(SampleAgent, result)
34
- end
35
-
36
- def test_id_registered
37
- assert_equal(false, Agents.instance.id_registered?(SampleAgent.agent_id))
38
- Agents.instance.register(SampleAgent)
39
- assert_equal(true, Agents.instance.id_registered?(SampleAgent.agent_id))
40
- end
41
- end
File without changes