casegen 1.3.0

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