casegen 1.3.0 → 2.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ee14f6a724a837cba03b3ec269718713400ad45e2b49b11c3e9022abe56e4587
4
+ data.tar.gz: 1b2ccea7698217314e4722f4c3543e2b54566e6d2c003d3f00e7942b46802962
5
+ SHA512:
6
+ metadata.gz: a3b8a1f42c2bff9fa37a147e96807123afa94941c41588f00903f8cb82541bd90ca0ab72b4fdf53dd319d9fb7e557cbf39485bcaa38b24b69870eb1e193c5470
7
+ data.tar.gz: 7fb847d1589799ef522a3fde0ff1f8d6322c67045fb555ed809baa98432b66f86b07cb339ddfc703e232e3a905b71721b85eb1add2bf8bada8704e1a7106b78f
@@ -0,0 +1,4 @@
1
+ .bundle/
2
+ .idea/
3
+ bin/
4
+ pkg/
@@ -0,0 +1 @@
1
+ 2.6.1
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,25 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ casegen (2.0.0)
5
+ tablesmith
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ minitest (5.11.3)
11
+ rake (12.3.1)
12
+ tablesmith (0.4.1)
13
+ text-table
14
+ text-table (1.2.4)
15
+
16
+ PLATFORMS
17
+ ruby
18
+
19
+ DEPENDENCIES
20
+ casegen!
21
+ minitest
22
+ rake
23
+
24
+ BUNDLED WITH
25
+ 1.17.3
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Chris Morris
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,132 @@
1
+ # CaseGen
2
+
3
+ CaseGen is a small Ruby external DSL for generating combinations of variables,
4
+ optionally restricted by a set of rules.
5
+
6
+ ## Usage
7
+
8
+ This input file:
9
+
10
+ sets
11
+ ------------
12
+ payment: Credit, Check, Online Bank
13
+ amount: 100, 1,000, 10,000
14
+ shipping: Ground, Air
15
+ ship to country: US, Outside US
16
+ bill to country: US, Outside US
17
+
18
+
19
+ rules(sets)
20
+ ---------------------------
21
+ exclude shipping = Ground AND ship to country = Outside US
22
+ Our ground shipper will only ship things within the US.
23
+
24
+ exclude payment = Check AND bill to country == Outside US
25
+ Our bank will not accept checks written from banks outside the US.
26
+
27
+ exclude payment = Online Bank AND amount == 1,000
28
+ exclude payment = Online Bank AND amount == 10,000
29
+ While the online bank will process amounts > $1,000, we've experienced
30
+ occasional problems with their services and have had to write off some
31
+ transactions, so we no longer allow this payment option for amounts greater
32
+ than $1,000
33
+
34
+ exclude ship to country = US AND bill to country = Outside US
35
+ If we're shipping to the US, billing party cannot be outside US
36
+
37
+
38
+ console(rules)
39
+ ----------
40
+
41
+
42
+ produces this output:
43
+
44
+ +-------------+--------+----------+-----------------+-----------------+
45
+ | payment | amount | shipping | ship to country | bill to country |
46
+ +-------------+--------+----------+-----------------+-----------------+
47
+ | Credit | 100 | Ground | US | US |
48
+ | Credit | 100 | Air | US | US |
49
+ | Credit | 100 | Air | Outside US | US |
50
+ | Credit | 100 | Air | Outside US | Outside US |
51
+ | Credit | 1,000 | Ground | US | US |
52
+ | Credit | 1,000 | Air | US | US |
53
+ | Credit | 1,000 | Air | Outside US | US |
54
+ | Credit | 1,000 | Air | Outside US | Outside US |
55
+ | Credit | 10,000 | Ground | US | US |
56
+ | Credit | 10,000 | Air | US | US |
57
+ | Credit | 10,000 | Air | Outside US | US |
58
+ | Credit | 10,000 | Air | Outside US | Outside US |
59
+ | Check | 100 | Ground | US | US |
60
+ | Check | 100 | Air | US | US |
61
+ | Check | 100 | Air | Outside US | US |
62
+ | Check | 1,000 | Ground | US | US |
63
+ | Check | 1,000 | Air | US | US |
64
+ | Check | 1,000 | Air | Outside US | US |
65
+ | Check | 10,000 | Ground | US | US |
66
+ | Check | 10,000 | Air | US | US |
67
+ | Check | 10,000 | Air | Outside US | US |
68
+ | Online Bank | 100 | Ground | US | US |
69
+ | Online Bank | 100 | Air | US | US |
70
+ | Online Bank | 100 | Air | Outside US | US |
71
+ | Online Bank | 100 | Air | Outside US | Outside US |
72
+ +-------------+--------+----------+-----------------+-----------------+
73
+
74
+ exclude shipping = Ground AND ship to country = Outside US
75
+ Our ground shipper will only ship things within the US.
76
+
77
+ exclude payment = Check AND bill to country == Outside US
78
+ Our bank will not accept checks written from banks outside the US.
79
+
80
+ exclude payment = Online Bank AND amount == 1,000
81
+
82
+ exclude payment = Online Bank AND amount == 10,000
83
+ While the online bank will process amounts > $1,000, we've experienced
84
+ occasional problems with their services and have had to write off some
85
+ transactions, so we no longer allow this payment option for amounts greater
86
+ than $1,000
87
+
88
+ exclude ship to country = US AND bill to country = Outside US
89
+ If we're shipping to the US, billing party cannot be outside US
90
+
91
+ If you pull the source locally, you can execute this:
92
+
93
+ casegen doc/cart.sample.txt
94
+
95
+ ## FAQ
96
+
97
+ How can I use this lib inside another Ruby file, instead of having a separate
98
+ input file?
99
+
100
+ sample.rb:
101
+
102
+ require "bundler/inline"
103
+
104
+ gemfile do
105
+ source "https://rubygems.org"
106
+ gem "casegen", "~> 2.0"
107
+ end
108
+
109
+ require 'casegen'
110
+
111
+ CLabs::CaseGen::CaseGen.new(DATA.read)
112
+
113
+ __END__
114
+
115
+ sets
116
+ ----
117
+ a: 1, 2
118
+ b: 3, 4
119
+
120
+ rules(sets)
121
+ -----------
122
+ exclude a = 1
123
+
124
+ console(rules)
125
+ --------------
126
+
127
+
128
+ ### Are there other tools similar to CaseGen?
129
+
130
+ <a href="http://code.google.com/p/tcases/">tcases</a> is one to check out.
131
+ Another is <a href="http://www.satisfice.com/tools.shtml">AllPairs</a> by James
132
+ Bach.
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rake/testtask'
4
+
5
+ desc 'Run tests'
6
+ Rake::TestTask.new do |t|
7
+ t.pattern = 'test/**/*test*.rb'
8
+ # t.verbose = true
9
+ end
10
+
11
+ task default: :test
data/TODO ADDED
@@ -0,0 +1,26 @@
1
+ - OR not supported yet.
2
+ - Nested booleans not supported yet.
3
+ - performance problems
4
+
5
+ two problems: formatting table is very slow, too many iterations. Sets agent can get
6
+ col widths easy, but it may not be passed to the console agent, so may need to support
7
+ "console(rules, sets)" syntax
8
+
9
+ the actual enum product call is fairly quick, but the rules are 3x as slow. Need a way
10
+ to maybe hold off on generating the combinations and then hook in with a block to filter
11
+ them as they're being made - save another run through the whole set.
12
+
13
+ - support for "assign ... when ..."
14
+ - if condition specified has an incorrect value, notify user
15
+ - don't require comma AND SPACE in set lists ... ? (This is currently that way to allow thousands separators in values)
16
+
17
+ - this case:
18
+ exclude Cl = Ser AND Rec = K12RF(1.01)
19
+ exclude Cl = Ser AND Rec = G2IR(white)
20
+ exclude Cl = Num AND Herf(11)
21
+
22
+ blows up without a meaningful error
23
+
24
+ - meaningful exceptions still are exceptions, and you have a full stacktrace - make it purty.
25
+
26
+ - LICENSE of enum lib contents.
@@ -1,11 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- begin
4
- require 'rubygems'
5
- gem 'casegen'
6
- rescue LoadError
7
- end
8
-
3
+ gem 'casegen'
9
4
  require 'casegen'
10
5
 
11
- CLabs::CaseGen::Console.new
6
+ CLabs::CaseGen::CaseGen.new(File.read(ARGV[0]))
@@ -0,0 +1,26 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'casegen'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = 'casegen'
7
+ gem.version = CLabs::CaseGen::CaseGen.version
8
+ gem.authors = ['chrismo']
9
+ gem.email = 'chrismo@clabs.org'
10
+ gem.description = 'Simple Ruby DSL to generate use cases restricted by sets of rules'
11
+ gem.summary = 'Simple Ruby DSL to generate use cases restricted by sets of rules'
12
+ gem.homepage = 'https://github.com/chrismo/casegen'
13
+ gem.license = 'MIT'
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = 'casegen'
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ['lib']
19
+
20
+ gem.required_ruby_version = '~> 2.3'
21
+
22
+ gem.add_dependency 'tablesmith'
23
+
24
+ gem.add_development_dependency 'minitest'
25
+ gem.add_development_dependency 'rake'
26
+ end
File without changes
@@ -0,0 +1,3 @@
1
+ require_relative '../lib/casegen'
2
+
3
+ CLabs::CaseGen::CaseGen.new(File.read(File.join(__dir__, 'cart.sample.txt')))
File without changes
@@ -1,3 +1,9 @@
1
+ require_relative '../lib/casegen'
2
+
3
+ CLabs::CaseGen::CaseGen.new(DATA.read)
4
+
5
+ __END__
6
+
1
7
  sets
2
8
  ----
3
9
  role: admin, standard
@@ -1,6 +1,7 @@
1
1
  require "#{File.dirname(__FILE__)}/../casegen"
2
2
  $LOAD_PATH << "#{File.expand_path(File.join(File.dirname(__FILE__), 'sets'))}"
3
3
  require 'enum/op'
4
+ require 'tablesmith'
4
5
 
5
6
  class String
6
7
  def to_u
@@ -11,35 +12,35 @@ end
11
12
  module CLabs::CaseGen
12
13
  class Set
13
14
  attr_reader :name, :data
14
-
15
+
15
16
  def initialize(name, data_array)
16
17
  @name = name
17
18
  @data = data_array
18
19
  strip_data
19
20
  end
20
-
21
+
21
22
  def strip_data
22
23
  @data.collect! do |datum| datum.strip end
23
24
  end
24
-
25
+
25
26
  def values
26
27
  @data
27
28
  end
28
29
  end
29
-
30
+
30
31
  class Sets < Agent
31
32
  attr_accessor :sets, :combinations, :set_titles
32
-
33
+
33
34
  def Sets.agent_id
34
35
  "casegen:sets"
35
36
  end
36
-
37
+
37
38
  def initialize(data, reference_agents=nil)
38
39
  @data = data
39
40
  @sets = []
40
41
  parse_sets
41
42
  end
42
-
43
+
43
44
  def parse_sets
44
45
  set_names = @data.scan(/^\s*(\w.*):/)
45
46
  set_data = @data.scan(/:(.*)/)
@@ -49,7 +50,7 @@ module CLabs::CaseGen
49
50
  end
50
51
  generate_combinations
51
52
  end
52
-
53
+
53
54
  def generate_combinations
54
55
  arrays = []
55
56
  @set_titles = []
@@ -66,30 +67,30 @@ module CLabs::CaseGen
66
67
  EnumerableOperator::Product.new(*args).each { |tuple|
67
68
  result << tuple
68
69
  }
69
- result
70
+ result
70
71
  end
71
-
72
+
72
73
  def set_names
73
74
  names = []
74
75
  @sets.each do |set| names << set.name end
75
76
  names
76
77
  end
77
-
78
+
78
79
  def set_by_name(setname)
79
80
  @sets.detect do |set| set.name =~ /#{Regexp.escape(setname)}/ end
80
81
  end
81
-
82
+
82
83
  end
83
-
84
+
84
85
  class Criteria
85
86
  attr_reader :set_names, :set_values, :equalities
86
-
87
+
87
88
  def initialize(data)
88
89
  @data = data
89
90
  @equalities = {}
90
91
  parse
91
92
  end
92
-
93
+
93
94
  def parse
94
95
  @data.split(/AND/).each do |bit|
95
96
  set_name, set_value = bit.split(/==|=/)
@@ -102,8 +103,8 @@ module CLabs::CaseGen
102
103
  @set_names = @equalities.keys
103
104
  @set_values = @equalities.values
104
105
  end
105
-
106
- # hash keys should be valid set names and hash values should be valid
106
+
107
+ # hash keys should be valid set names and hash values should be valid
107
108
  # set values in the named set
108
109
  def match(hash)
109
110
  # must match all equalities
@@ -114,20 +115,20 @@ module CLabs::CaseGen
114
115
  end
115
116
  return true
116
117
  end
117
-
118
+
118
119
  def to_s
119
120
  @data
120
121
  end
121
122
  end
122
-
123
+
123
124
  class Rule
124
125
  attr_reader :criteria, :description, :data
125
-
126
+
126
127
  def initialize(rule_data)
127
128
  @data = rule_data
128
129
  parse_rule
129
130
  end
130
-
131
+
131
132
  def parse_rule
132
133
  data = @data.sub(self.class.regexp, '')
133
134
  criteria_data, *@description = data.split(/\n/)
@@ -136,41 +137,41 @@ module CLabs::CaseGen
136
137
  @description = (@description.join("\n") + "\n").outdent.strip
137
138
  end
138
139
  end
139
-
140
+
140
141
  class ExcludeRule < Rule
141
142
  def type_description
142
143
  "exclude"
143
144
  end
144
-
145
+
145
146
  def ExcludeRule.regexp
146
147
  /^exclude/i
147
148
  end
148
-
149
+
149
150
  def ExcludeRule.create(rule_data)
150
151
  return ExcludeRule.new(rule_data) if rule_data =~ regexp
151
152
  return nil
152
153
  end
153
154
  end
154
-
155
+
155
156
  class Rules < Agent
156
157
  def Rules.agent_id
157
158
  "casegen:rules"
158
159
  end
159
-
160
+
160
161
  def initialize(data, reference_agents=[])
161
162
  @data = data
162
163
  @agents = reference_agents
163
164
  @rules = []
164
165
  @rule_classes = []
165
- ObjectSpace.each_object(Class) do |obj|
166
- @rule_classes << obj if obj.ancestors.include?(Rule) && obj != Rule
166
+ ObjectSpace.each_object(Class) do |obj|
167
+ @rule_classes << obj if obj.ancestors.include?(Rule) && obj != Rule
167
168
  end
168
169
  parse_data
169
170
  end
170
-
171
+
171
172
  def parse_data
172
173
  raw_rules = @data.split(/(?=^\S)/)
173
-
174
+
174
175
  raw_rules.each do |rule|
175
176
  @rule_classes.each do |rule_class|
176
177
  @rules << rule_class.create(rule.strip)
@@ -180,7 +181,7 @@ module CLabs::CaseGen
180
181
  @rules.flatten!
181
182
  validate_rules
182
183
  end
183
-
184
+
184
185
  def validate_rules
185
186
  @agents.each do |agent|
186
187
  if agent.class == Sets
@@ -188,7 +189,7 @@ module CLabs::CaseGen
188
189
  rule.criteria.equalities.each_pair do |set_name, set_value|
189
190
  set = agent.set_by_name(set_name)
190
191
  if set.nil?
191
- raise ParserException.new("Invalid set name <#{set_name}> " +
192
+ raise ParserException.new("Invalid set name <#{set_name}> " +
192
193
  "in rule <#{rule.criteria}>. Valid set names are <#{agent.set_names.join(', ')}>.")
193
194
  end
194
195
  if !set.values.include?(set_value)
@@ -201,19 +202,19 @@ module CLabs::CaseGen
201
202
  end
202
203
  end
203
204
  end
204
-
205
+
205
206
  def length
206
207
  @rules.length
207
208
  end
208
-
209
+
209
210
  def [](index)
210
211
  return @rules[index]
211
212
  end
212
-
213
+
213
214
  def each(&block)
214
215
  @rules.each(&block)
215
216
  end
216
-
217
+
217
218
  def combinations
218
219
  return @combinations if !@combinations.nil?
219
220
  if @agents[0].class == Sets
@@ -227,7 +228,7 @@ module CLabs::CaseGen
227
228
  # combo_hash will have set names matched with set values
228
229
  agent.set_titles.each do |title|
229
230
  combo_hash[title] = combo[i]
230
- i += 1
231
+ i += 1
231
232
  end
232
233
  @rules.each do |rule|
233
234
  delete |= rule.criteria.match(combo_hash)
@@ -236,60 +237,36 @@ module CLabs::CaseGen
236
237
  end
237
238
  return @combinations
238
239
  end
239
- return []
240
+ return []
240
241
  end
241
-
242
+
242
243
  def titles
243
244
  @agents[0].titles
244
245
  end
245
-
246
+
246
247
  def to_s
247
248
  puts @agents[0].combinations.inspect if !@agents[0].nil?
248
249
  puts
249
250
  puts @rules.inspect
250
251
  end
251
252
  end
252
-
253
+
253
254
  class ConsoleOutput < Agent
254
255
  def ConsoleOutput.agent_id
255
256
  "casegen:console"
256
257
  end
257
-
258
- def initialize(data, reference_agents)
258
+
259
+ def initialize(data, reference_agents, io=STDOUT)
259
260
  @data = data
260
261
  @agents = reference_agents
261
- table = formatted_table([@agents[0].titles] + @agents[0].combinations)
262
- table.each do |ary|
263
- puts ary.join(' | ')
264
- end
265
- puts
262
+ table = [@agents[0].titles] + @agents[0].combinations
263
+ io.puts table.to_table.pretty_inspect
264
+ io.puts
266
265
  @agents[0].each do |rule|
267
- puts rule.data
268
- puts
266
+ io.puts rule.data
267
+ io.puts
269
268
  end if @agents[0].is_a?(Rules)
270
269
  end
271
-
272
- def formatted_table(combinations)
273
- col_widths = []
274
- formatted_tuples = []
275
- combinations.each do |tuple|
276
- col = 0
277
- tuple.each do |item|
278
- col_widths[col] = item.to_s.length if col_widths[col].to_i < item.to_s.length
279
- col += 1
280
- end
281
- end
282
-
283
- combinations.each do |tuple|
284
- col = 0
285
- formatted_tuples << tuple.collect { |item|
286
- formatted = item.to_s.ljust(col_widths[col]) if !col_widths[col].nil?
287
- col += 1
288
- formatted
289
- }
290
- end
291
- formatted_tuples
292
- end
293
270
  end
294
271
 
295
272
  class RubyArrayOutput < Agent