casegen 1.3.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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