casegen 1.3.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.
data/bin/casegen ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'rubygems'
5
+ gem 'casegen'
6
+ rescue LoadError
7
+ end
8
+
9
+ require 'casegen'
10
+
11
+ CLabs::CaseGen::Console.new
@@ -0,0 +1,336 @@
1
+ require "#{File.dirname(__FILE__)}/../casegen"
2
+ $LOAD_PATH << "#{File.expand_path(File.join(File.dirname(__FILE__), 'sets'))}"
3
+ require 'enum/op'
4
+
5
+ class String
6
+ def to_u
7
+ self.gsub(/ /, '_')
8
+ end
9
+ end
10
+
11
+ module CLabs::CaseGen
12
+ class Set
13
+ attr_reader :name, :data
14
+
15
+ def initialize(name, data_array)
16
+ @name = name
17
+ @data = data_array
18
+ strip_data
19
+ end
20
+
21
+ def strip_data
22
+ @data.collect! do |datum| datum.strip end
23
+ end
24
+
25
+ def values
26
+ @data
27
+ end
28
+ end
29
+
30
+ class Sets < Agent
31
+ attr_accessor :sets, :combinations, :set_titles
32
+
33
+ def Sets.agent_id
34
+ "casegen:sets"
35
+ end
36
+
37
+ def initialize(data, reference_agents=nil)
38
+ @data = data
39
+ @sets = []
40
+ parse_sets
41
+ end
42
+
43
+ def parse_sets
44
+ set_names = @data.scan(/^\s*(\w.*):/)
45
+ set_data = @data.scan(/:(.*)/)
46
+ sets = set_names.flatten.zip(set_data.flatten)
47
+ sets.each do |set_array|
48
+ @sets << Set.new(set_array[0], set_array[1].split(/, /))
49
+ end
50
+ generate_combinations
51
+ end
52
+
53
+ def generate_combinations
54
+ arrays = []
55
+ @set_titles = []
56
+ @sets.each do |set| arrays << set.data; @set_titles << set.name end
57
+ @combinations = all(*arrays)
58
+ end
59
+
60
+ def titles
61
+ @set_titles
62
+ end
63
+
64
+ def all(*args)
65
+ result = []
66
+ EnumerableOperator::Product.new(*args).each { |tuple|
67
+ result << tuple
68
+ }
69
+ result
70
+ end
71
+
72
+ def set_names
73
+ names = []
74
+ @sets.each do |set| names << set.name end
75
+ names
76
+ end
77
+
78
+ def set_by_name(setname)
79
+ @sets.detect do |set| set.name =~ /#{Regexp.escape(setname)}/ end
80
+ end
81
+
82
+ end
83
+
84
+ class Criteria
85
+ attr_reader :set_names, :set_values, :equalities
86
+
87
+ def initialize(data)
88
+ @data = data
89
+ @equalities = {}
90
+ parse
91
+ end
92
+
93
+ def parse
94
+ @data.split(/AND/).each do |bit|
95
+ set_name, set_value = bit.split(/==|=/)
96
+ set_name.strip!; set_value.strip!
97
+ if @equalities.keys.include?(set_name)
98
+ raise ParserException.new("Rule cannot have the same set <#{set_name}> equal to different values <#{@equalities[set_name]}, #{set_value}>")
99
+ end
100
+ @equalities[set_name] = set_value
101
+ end
102
+ @set_names = @equalities.keys
103
+ @set_values = @equalities.values
104
+ end
105
+
106
+ # hash keys should be valid set names and hash values should be valid
107
+ # set values in the named set
108
+ def match(hash)
109
+ # must match all equalities
110
+ @equalities.each_pair do |eq_name, eq_value|
111
+ actual_value = hash[eq_name]
112
+ return false if actual_value.nil?
113
+ return false if actual_value != eq_value
114
+ end
115
+ return true
116
+ end
117
+
118
+ def to_s
119
+ @data
120
+ end
121
+ end
122
+
123
+ class Rule
124
+ attr_reader :criteria, :description, :data
125
+
126
+ def initialize(rule_data)
127
+ @data = rule_data
128
+ parse_rule
129
+ end
130
+
131
+ def parse_rule
132
+ data = @data.sub(self.class.regexp, '')
133
+ criteria_data, *@description = data.split(/\n/)
134
+ criteria_data.strip!
135
+ @criteria = Criteria.new(criteria_data)
136
+ @description = (@description.join("\n") + "\n").outdent.strip
137
+ end
138
+ end
139
+
140
+ class ExcludeRule < Rule
141
+ def type_description
142
+ "exclude"
143
+ end
144
+
145
+ def ExcludeRule.regexp
146
+ /^exclude/i
147
+ end
148
+
149
+ def ExcludeRule.create(rule_data)
150
+ return ExcludeRule.new(rule_data) if rule_data =~ regexp
151
+ return nil
152
+ end
153
+ end
154
+
155
+ class Rules < Agent
156
+ def Rules.agent_id
157
+ "casegen:rules"
158
+ end
159
+
160
+ def initialize(data, reference_agents=[])
161
+ @data = data
162
+ @agents = reference_agents
163
+ @rules = []
164
+ @rule_classes = []
165
+ ObjectSpace.each_object(Class) do |obj|
166
+ @rule_classes << obj if obj.ancestors.include?(Rule) && obj != Rule
167
+ end
168
+ parse_data
169
+ end
170
+
171
+ def parse_data
172
+ raw_rules = @data.split(/(?=^\S)/)
173
+
174
+ raw_rules.each do |rule|
175
+ @rule_classes.each do |rule_class|
176
+ @rules << rule_class.create(rule.strip)
177
+ end
178
+ end
179
+ @rules.compact!
180
+ @rules.flatten!
181
+ validate_rules
182
+ end
183
+
184
+ def validate_rules
185
+ @agents.each do |agent|
186
+ if agent.class == Sets
187
+ @rules.each do |rule|
188
+ rule.criteria.equalities.each_pair do |set_name, set_value|
189
+ set = agent.set_by_name(set_name)
190
+ if set.nil?
191
+ raise ParserException.new("Invalid set name <#{set_name}> " +
192
+ "in rule <#{rule.criteria}>. Valid set names are <#{agent.set_names.join(', ')}>.")
193
+ end
194
+ if !set.values.include?(set_value)
195
+ raise ParserException.new("Invalid set value <#{set_value}> " +
196
+ "in rule <#{rule.criteria}>. Valid set values for <#{set.name}> " +
197
+ "are <#{set.values.join(', ')}>.")
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ def length
206
+ @rules.length
207
+ end
208
+
209
+ def [](index)
210
+ return @rules[index]
211
+ end
212
+
213
+ def each(&block)
214
+ @rules.each(&block)
215
+ end
216
+
217
+ def combinations
218
+ return @combinations if !@combinations.nil?
219
+ if @agents[0].class == Sets
220
+ agent = @agents[0]
221
+ @combinations = []
222
+ agent.combinations.each do |combo|
223
+ delete = false
224
+ combo_hash = {}
225
+ i = 0
226
+ # combo is an array of values, in the same order of the set_titles.
227
+ # combo_hash will have set names matched with set values
228
+ agent.set_titles.each do |title|
229
+ combo_hash[title] = combo[i]
230
+ i += 1
231
+ end
232
+ @rules.each do |rule|
233
+ delete |= rule.criteria.match(combo_hash)
234
+ end
235
+ @combinations << combo if !delete
236
+ end
237
+ return @combinations
238
+ end
239
+ return []
240
+ end
241
+
242
+ def titles
243
+ @agents[0].titles
244
+ end
245
+
246
+ def to_s
247
+ puts @agents[0].combinations.inspect if !@agents[0].nil?
248
+ puts
249
+ puts @rules.inspect
250
+ end
251
+ end
252
+
253
+ class ConsoleOutput < Agent
254
+ def ConsoleOutput.agent_id
255
+ "casegen:console"
256
+ end
257
+
258
+ def initialize(data, reference_agents)
259
+ @data = data
260
+ @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
266
+ @agents[0].each do |rule|
267
+ puts rule.data
268
+ puts
269
+ end if @agents[0].is_a?(Rules)
270
+ 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
+ end
294
+
295
+ class RubyArrayOutput < Agent
296
+ def self.agent_id
297
+ "casegen:ruby_array"
298
+ end
299
+
300
+ def initialize(data, reference_agents, io=STDOUT)
301
+ @io = io
302
+ @struct_name = "Case"
303
+ @struct_name = data if !data.empty?
304
+ @agents = reference_agents
305
+ @agents.each do |agent| execute(agent) end
306
+ end
307
+
308
+ def execute(agent)
309
+ struct_header = "#{@struct_name} = Struct.new("
310
+ struct = ''
311
+ agent.titles.each do |title|
312
+ struct << ', ' if !struct.empty?
313
+ struct << ":#{title.to_u.downcase}"
314
+ end
315
+ struct << ')'
316
+
317
+ guts_header = 'cases = ['
318
+ guts = ''
319
+ agent.combinations.each do |combo|
320
+ guts << ",\n#{' ' * guts_header.length}" if !guts.empty?
321
+ guts << "#{@struct_name}.new#{combo.inspect.gsub(/\[/, '(').gsub(/\]/, ')')}"
322
+ end
323
+ @io.print(struct_header)
324
+ @io.print(struct)
325
+ @io.print("\n\n")
326
+ @io.print(guts_header)
327
+ @io.print(guts)
328
+ @io.print("]\n")
329
+ end
330
+ end
331
+ end
332
+
333
+ if __FILE__ == $0
334
+ sets = CLabs::CaseGen::Sets.new("a: 1, 2\nb: 3, 4")
335
+ puts sets.combinations
336
+ end
@@ -0,0 +1,244 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Enumerable
4
+
5
+ class LinkedListDelegator
6
+ include Enumerable
7
+
8
+ attr_reader :first, :next_name, :next_map, :args
9
+
10
+ def initialize first, next_spec = nil, *args, &next_proc
11
+ @first = first
12
+ @args = args
13
+
14
+ case next_spec
15
+ when Symbol
16
+ @next_name = next_spec
17
+ when String
18
+ @next_name = next_spec.intern
19
+ when nil
20
+ @next_map = next_proc
21
+ else
22
+ unless next_spec.respond_to? :[]
23
+ raise ArgumentError,
24
+ "next_spec must be a method name or respond to []."
25
+ end
26
+ @next_map = next_spec
27
+ end
28
+
29
+ unless @next_name or @next_map
30
+ raise ArgumentError,
31
+ "no next-getter specified."
32
+ end
33
+ end
34
+
35
+ def each
36
+ cur = @first
37
+ if @next_name
38
+ next_name = @next_name
39
+ message = next_name, *@args
40
+ while cur
41
+ yield cur
42
+ cur = cur.send *message
43
+ end
44
+ elsif @next_map
45
+ next_map = @next_map
46
+ args = @args
47
+ while cur
48
+ yield cur
49
+ cur = next_map[cur, *args]
50
+ end
51
+ end
52
+
53
+ self
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+ class Object
61
+ def by next_spec = nil, *args, &next_proc
62
+ Enumerable::LinkedListDelegator.new self, next_spec, *args, &next_proc
63
+ end
64
+ end
65
+
66
+
67
+ =begin
68
+
69
+ ==class Object
70
+ ===instance method
71
+ ---Object#by next_spec = nil, *args, &next_proc
72
+
73
+ Allows use of (({Enumerable})) methods, such as (({each})), (({collect})),
74
+ (({select})), etc., to iterate over arbitrary objects. The caller supplies a
75
+ way of calculating the successor of each object, such as an accessor method for
76
+ the next element of a linked list.
77
+
78
+ Object#by returns an (({Enumerable})) object whose (({each})) method
79
+ iterates over the sequence beginning with (({self})) and continuing as
80
+ specified by the arguments. Only the current element of the sequence is
81
+ kept in memory. No attempt is made to avoid cycles.
82
+
83
+ If (({next_spec})) is a string or symbol, (({next_proc})) is ignored and
84
+ (({next_spec})) is treated as a method name. This method name is sent, along
85
+ with arguments (({args})), to each element of the sequence to generate the next
86
+ element. The sequence terminates at the first element for which the method
87
+ returns (({nil})) or (({false})).
88
+
89
+ If (({next_spec})) is anything else, except (({nil})), (({next_proc})) is
90
+ ignored and (({next_spec})) is required to be an object that responds to
91
+ (({[]})), such as a proc or a hash. The (({[]})) method of (({next_spec}))
92
+ is called with each element of the sequence in turn as an argument, along
93
+ with (({args})), to generate the next element. The sequence terminates at
94
+ the first element for which (({[]})) returns (({nil})) or (({false})).
95
+
96
+ If (({next_spec})) is not given, or is (({nil})), a block is required. In this
97
+ case, the block is converted to a proc and iteration proceeds as in the
98
+ preceding paragraph.
99
+
100
+ The return value is not an array, but an (({Enumerable})) object that refers
101
+ to the original objects. In this sense, (({Object#by})) is a ((*delegator*)).
102
+ Typically, (({by})) is used with the (({for .. in ..})) construct, or
103
+ (equivalently) with (({each})), or with (({collect})), (({select})), and so on.
104
+ In these cases, the dependence on the original sequence does not matter. To get
105
+ the array of entries produced by (({by})) as an independent data structure, use
106
+ (({Enumerable#entries})) or (({Enumerable#to_a})).
107
+
108
+ ===examples
109
+
110
+ require 'enum/by'
111
+
112
+ class A; end
113
+ class B < A; end
114
+ class C < B; end
115
+
116
+ for cl in C.by :superclass
117
+ print cl, " "
118
+ end
119
+
120
+ # prints: C B A Object
121
+
122
+ steps = proc { |x, incr, limit| y = x + incr; y <= limit ? y : nil }
123
+ p 0.by(steps, 10, 50).to_a
124
+
125
+ # prints: [0, 10, 20, 30, 40, 50]
126
+
127
+ See the end of the source file for more examples.
128
+
129
+ ==version
130
+
131
+ Enumerable tools 1.6
132
+
133
+ The current version of this software can be found at
134
+ ((<"http://redshift.sourceforge.net/enum
135
+ "|URL:http://redshift.sourceforge.net/enum>)).
136
+
137
+ ==license
138
+ This software is distributed under the Ruby license.
139
+ See ((<"http://www.ruby-lang.org"|URL:http://www.ruby-lang.org>)).
140
+
141
+ ==author
142
+ Joel VanderWerf,
143
+ ((<vjoel@users.sourceforge.net|URL:mailto:vjoel@users.sourceforge.net>))
144
+
145
+ ==acknowledgement
146
+ Thanks to David Alan Black for his helpful comments on the Ruby mailing
147
+ list
148
+ ((<"http://blade.nagaokaut.ac.jp/ruby/ruby-talk
149
+ "|URL:http://blade.nagaokaut.ac.jp/ruby/ruby-talk>)).
150
+
151
+ =end
152
+
153
+
154
+ if __FILE__ == $0
155
+
156
+ class Foo
157
+ attr_reader :value, :next_foo
158
+
159
+ def initialize value, next_foo = nil
160
+ @value = value
161
+ @next_foo = next_foo
162
+ end
163
+ end
164
+
165
+ list = Foo.new(0, Foo.new(1, Foo.new(2, Foo.new(3))))
166
+
167
+ puts "Iterating over a linked list by method next_foo:"
168
+ for foo in list.by :next_foo
169
+ print "#{foo.value} "
170
+ end
171
+ print "\n\n"
172
+
173
+ puts "By proc { |foo| foo.next_foo.next_foo }:"
174
+ for foo in list.by { |foo| foo.next_foo.next_foo }
175
+ print "#{foo.value} "
176
+ end
177
+ print "\n\n"
178
+
179
+ puts "Down a tree, taking a random branch at each node:"
180
+ puts "First, with a proc:"
181
+ tree = [[0, [1, 2]], 3, [4, 5, [6, 7, 8]], 9]
182
+ at_random = proc { |x| x.kind_of?(Array) && x.at(rand(x.size)) }
183
+ for node in tree.by at_random
184
+ p node
185
+ end
186
+ puts "Second, with methods:"
187
+ class Object
188
+ def at_random
189
+ nil
190
+ end
191
+ end
192
+ class Array
193
+ def at_random
194
+ at(rand(size))
195
+ end
196
+ end
197
+ for node in tree.by :at_random
198
+ p node
199
+ end
200
+ puts
201
+
202
+ puts "With numbers (but watch out for cycles!):"
203
+ p 0.by { |x| x<10 ? x+1 : nil }.to_a
204
+ puts
205
+
206
+ puts "With numbers using a proc and an argument:"
207
+ steps = proc { |x, incr, limit| y = x + incr; y <= limit ? y : nil }
208
+ p 0.by(steps, 10, 50).to_a
209
+ puts
210
+
211
+ puts "Up the superclass relation:"
212
+ class A; end
213
+ class B < A; end
214
+ class C < B; end
215
+ for cl in C.by :superclass
216
+ print cl, " "
217
+ end
218
+ puts "\n\n"
219
+
220
+ puts "Popping down a stack:"
221
+ p [0,1,2,3,4].by { |y| y.pop; y != [] && y.dup }.entries
222
+ puts
223
+
224
+ puts "#by behaves correctly with self==nil"
225
+ p nil.by(:something).entries
226
+ puts
227
+
228
+ puts "By hash, or other class responding to []:"
229
+ h = { :a => :b, :b => :c, :c => :d }
230
+ p :a.by {|x| h[x]}.entries
231
+ puts "The same effect, but simpler:"
232
+ p :a.by(h).entries
233
+ puts
234
+
235
+ puts "Around and around we go..."
236
+ n = 0;
237
+ for x in 0.by([1,2,3,4,0])
238
+ n += 1
239
+ print x, " "
240
+ break if n > 12
241
+ end
242
+ puts "..."
243
+
244
+ end