casegen 2.0.0 → 3.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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +109 -0
- data/.ruby-version +1 -1
- data/Gemfile +3 -1
- data/Gemfile.lock +51 -6
- data/README.md +10 -119
- data/Rakefile +9 -7
- data/bin/casegen +2 -1
- data/casegen.gemspec +13 -9
- data/doc/bounding_box.rb +37 -0
- data/doc/cart.rb +43 -0
- data/doc/expect_only.rb +28 -0
- data/doc/pricing.rb +50 -0
- data/doc/ruby_array.rb +41 -0
- data/lib/case_gen/combination.rb +38 -0
- data/lib/case_gen/combo_matcher.rb +15 -0
- data/lib/case_gen/exclude_rule.rb +50 -0
- data/lib/case_gen/expect_rule.rb +24 -0
- data/lib/case_gen/generator.rb +40 -0
- data/lib/case_gen/output/exclude.rb +6 -0
- data/lib/case_gen/output/exclude_as_table.rb +13 -0
- data/lib/case_gen/output/exclude_as_text.rb +12 -0
- data/lib/case_gen/output/exclude_inline.rb +13 -0
- data/lib/case_gen/output/exclude_inline_footnotes.rb +20 -0
- data/lib/case_gen/output.rb +66 -0
- data/lib/case_gen/rule_description.rb +11 -0
- data/lib/case_gen/set.rb +16 -0
- data/lib/casegen.rb +15 -183
- data/spec/cart_sample_spec.rb +46 -0
- data/spec/case_gen/combination_spec.rb +11 -0
- data/spec/case_gen/exclude_rule_spec.rb +17 -0
- data/spec/exclude_as_table_spec.rb +39 -0
- data/spec/exclude_as_text_spec.rb +58 -0
- data/spec/exclude_inline_footnotes_spec.rb +58 -0
- data/spec/exclude_inline_spec.rb +50 -0
- data/spec/expect_only_spec.rb +30 -0
- data/spec/spec_helper.rb +113 -0
- metadata +101 -35
- data/.idea/encodings.xml +0 -5
- data/.idea/misc.xml +0 -5
- data/.idea/modules.xml +0 -9
- data/.idea/vcs.xml +0 -7
- data/doc/calc.sample.txt +0 -13
- data/doc/cart.sample.rb +0 -3
- data/doc/cart.sample.txt +0 -33
- data/doc/ruby_array.sample.rb +0 -26
- data/lib/agents/sets/enum/by.rb +0 -244
- data/lib/agents/sets/enum/cluster.rb +0 -164
- data/lib/agents/sets/enum/inject.rb +0 -50
- data/lib/agents/sets/enum/nest.rb +0 -117
- data/lib/agents/sets/enum/op.rb +0 -283
- data/lib/agents/sets/enum/pipe.rb +0 -160
- data/lib/agents/sets/enum/tree.rb +0 -442
- data/lib/agents/sets.rb +0 -313
- data/test/agents/console_output_test.rb +0 -27
- data/test/agents/sets.test.rb +0 -227
- data/test/agents_test.rb +0 -41
- data/test/casegen.tests.rb +0 -0
- data/test/parser_test.rb +0 -163
- data/test/test_helper.rb +0 -2
@@ -1,442 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
module Enumerable
|
4
|
-
|
5
|
-
class TreeDelegator
|
6
|
-
include Enumerable
|
7
|
-
|
8
|
-
attr_reader :root, :child_name, :child_map,
|
9
|
-
:args, :output_type
|
10
|
-
|
11
|
-
def initialize root, child_spec = nil, *args, &child_proc
|
12
|
-
@root = root
|
13
|
-
@args = args
|
14
|
-
@output_type = :nodes
|
15
|
-
|
16
|
-
case child_spec
|
17
|
-
when Symbol
|
18
|
-
@child_name = child_spec
|
19
|
-
when String
|
20
|
-
@child_name = child_spec.intern
|
21
|
-
when nil
|
22
|
-
@child_map = child_proc
|
23
|
-
else
|
24
|
-
unless child_spec.respond_to? :[]
|
25
|
-
raise ArgumentError,
|
26
|
-
"child_spec must be a method name or respond to []."
|
27
|
-
end
|
28
|
-
@child_map = child_spec
|
29
|
-
end
|
30
|
-
|
31
|
-
unless @child_name or @child_map
|
32
|
-
raise ArgumentError,
|
33
|
-
"no child-getter specified."
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def get_children cur
|
38
|
-
(if @child_name
|
39
|
-
cur.send @child_name, *@args
|
40
|
-
else
|
41
|
-
@child_map[cur, *@args]
|
42
|
-
end).to_a
|
43
|
-
end
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
class ByBreadthDelegator < TreeDelegator
|
48
|
-
|
49
|
-
def each level = [@root], &block
|
50
|
-
|
51
|
-
children = []
|
52
|
-
for node in level
|
53
|
-
yield node
|
54
|
-
children |= get_children(node) || []
|
55
|
-
end
|
56
|
-
|
57
|
-
if children and not children.empty?
|
58
|
-
each children, &block
|
59
|
-
end
|
60
|
-
|
61
|
-
self
|
62
|
-
end
|
63
|
-
|
64
|
-
end
|
65
|
-
|
66
|
-
class ByDepthDelegator < TreeDelegator
|
67
|
-
|
68
|
-
def with_ancestors
|
69
|
-
@output_type = :with_ancestors
|
70
|
-
self
|
71
|
-
end
|
72
|
-
|
73
|
-
def branches
|
74
|
-
@output_type = :branches
|
75
|
-
self
|
76
|
-
end
|
77
|
-
|
78
|
-
def each(*args, &block)
|
79
|
-
case @output_type
|
80
|
-
when :nodes
|
81
|
-
each_node *args, &block
|
82
|
-
when :with_ancestors
|
83
|
-
each_with_ancestors *args, &block
|
84
|
-
when :branches
|
85
|
-
each_branch *args, &block
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
protected
|
90
|
-
def each_node cur = @root, &block
|
91
|
-
|
92
|
-
result = catch (:prune) do
|
93
|
-
yield cur
|
94
|
-
|
95
|
-
children = get_children cur
|
96
|
-
|
97
|
-
if children
|
98
|
-
for child in children
|
99
|
-
each_node child, &block
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
false
|
104
|
-
end
|
105
|
-
|
106
|
-
if result and result > 0
|
107
|
-
throw :prune, result - 1
|
108
|
-
end
|
109
|
-
|
110
|
-
self
|
111
|
-
end
|
112
|
-
|
113
|
-
def each_with_ancestors cur = @root, ancestors = [], &block
|
114
|
-
|
115
|
-
result = catch (:prune) do
|
116
|
-
yield cur, ancestors
|
117
|
-
|
118
|
-
children = get_children cur
|
119
|
-
|
120
|
-
if children
|
121
|
-
for child in children
|
122
|
-
each_with_ancestors child, ancestors + [cur], &block
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
false
|
127
|
-
end
|
128
|
-
|
129
|
-
if result and result > 0
|
130
|
-
throw :prune, result - 1
|
131
|
-
end
|
132
|
-
|
133
|
-
self
|
134
|
-
end
|
135
|
-
|
136
|
-
def each_branch branch = [@root], &block
|
137
|
-
cur = branch[-1]
|
138
|
-
|
139
|
-
children = get_children cur
|
140
|
-
|
141
|
-
if children and not children.empty?
|
142
|
-
for child in children
|
143
|
-
each_branch branch + [child], &block
|
144
|
-
end
|
145
|
-
else
|
146
|
-
yield branch
|
147
|
-
end
|
148
|
-
|
149
|
-
self
|
150
|
-
end
|
151
|
-
|
152
|
-
end
|
153
|
-
|
154
|
-
end
|
155
|
-
|
156
|
-
class Object
|
157
|
-
def by_depth child_spec = nil, *args, &child_proc
|
158
|
-
Enumerable::ByDepthDelegator.new self, child_spec, *args, &child_proc
|
159
|
-
end
|
160
|
-
def by_breadth child_spec = nil, *args, &child_proc
|
161
|
-
Enumerable::ByBreadthDelegator.new self, child_spec, *args, &child_proc
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
|
166
|
-
=begin
|
167
|
-
|
168
|
-
==class Object
|
169
|
-
===instance methods
|
170
|
-
---Object#by_depth child_spec = nil, *args, &child_proc
|
171
|
-
---Object#by_breadth child_spec = nil, *args, &child_proc
|
172
|
-
|
173
|
-
Allows use of (({Enumerable})) methods, such as (({each})), (({collect})),
|
174
|
-
(({select})), etc., to iterate over objects which have a caller-specified tree
|
175
|
-
structure (or, more generally, directed acyclic graph structure). The caller
|
176
|
-
defines this structure by providing a way of calculating the children of each
|
177
|
-
object, such as an accessor method for the array of children of a node in a
|
178
|
-
tree.
|
179
|
-
|
180
|
-
In order to yield a sequence of objects, the nonlinear structure of the tree is
|
181
|
-
linearized in either a depth-first or a breadth-first way, depending on the
|
182
|
-
method used, (({by_depth})) or (({by_breadth})). In a depth-first
|
183
|
-
linearization, the iteration visits a node's children before continuing with
|
184
|
-
the node's successive siblings. In a breadth-first linearization, the iteration
|
185
|
-
visits all siblings before visiting any of their children. In either case, the
|
186
|
-
parent is visited before its children, and the children are visited in an order
|
187
|
-
consistent with their order in the sequence of children provided by their
|
188
|
-
parent.
|
189
|
-
|
190
|
-
Speaking loosely, one can think of a depth-first iteration as working branch by
|
191
|
-
branch and a breadth-first iteration as working level by level, where a level
|
192
|
-
consists of nodes of equal depth.
|
193
|
-
|
194
|
-
====usage
|
195
|
-
|
196
|
-
require 'enum/tree'
|
197
|
-
|
198
|
-
for node in root.by_depth :a_method, ...
|
199
|
-
|
200
|
-
for node in root.by_depth a_proc, ...
|
201
|
-
|
202
|
-
for node in root.by_depth { |node| ... return children }
|
203
|
-
|
204
|
-
# by_breadth has the same form
|
205
|
-
|
206
|
-
====arguments
|
207
|
-
|
208
|
-
The means of accessing the children of each node is specified in the
|
209
|
-
argument list with a method name, a block, or an object that responds to
|
210
|
-
(({[]})), such as a proc or a hash. In any case, the value returned must be an
|
211
|
-
(({Enumerable})) object, typically an array.
|
212
|
-
|
213
|
-
If (({child_spec})) is a string or symbol, (({child_proc})) is ignored and
|
214
|
-
(({child_spec})) is treated as a method name. This method name is sent, along
|
215
|
-
with arguments (({args})), to each node to generate the children. The node is
|
216
|
-
considered childless when the method returns (({nil})) or (({false})) or an empty collection.
|
217
|
-
|
218
|
-
If (({child_spec})) is anything else, except (({nil})), (({child_proc})) is
|
219
|
-
ignored and (({child_spec})) is required to be an object that responds to
|
220
|
-
(({[]})), such as a proc or a hash. The (({[]})) method of (({child_spec})) is
|
221
|
-
called with each node as an argument, along with (({args})), to generate the
|
222
|
-
children. The node is considered childless when (({[]})) returns (({nil})) or
|
223
|
-
(({false})) or an empty collection.
|
224
|
-
|
225
|
-
If (({child_spec})) is not given, or is (({nil})), a block is required. In this
|
226
|
-
case, the block is converted to a proc and iteration proceeds as in the
|
227
|
-
preceding paragraph.
|
228
|
-
|
229
|
-
====return value
|
230
|
-
|
231
|
-
The return value is not an array, but an (({Enumerable})) object that refers
|
232
|
-
to the original objects. In this sense, (({Object#by_depth})) and
|
233
|
-
(({Object#by_breadth})) are ((*delegators*)). Typically, (({by_depth})) and
|
234
|
-
(({by_breadth})) are used with the (({for ... in ...})) construct, or
|
235
|
-
(equivalently) with (({each})), or with (({collect})), (({select})), and so on.
|
236
|
-
In these cases, the dependence on the original data structure does not matter.
|
237
|
-
To get the array of entries produced by (({by_depth})) or (({by_breadth})) as
|
238
|
-
an independent data structure, use (({Enumerable#entries})) or
|
239
|
-
(({Enumerable#to_a})).
|
240
|
-
|
241
|
-
====directed acyclic graphs
|
242
|
-
|
243
|
-
If a node occurs as the child of two different parent nodes, the structure is
|
244
|
-
not a tree. As long as no node is its own ancestor, these methods still produce
|
245
|
-
a useful interation (it is the caller's responsibility to avoid cycles). The
|
246
|
-
structure in this case is sometimes called a ((*directed acyclic graph*)). In a
|
247
|
-
depth-first iteration, a node with two parents will be reached twice. In a
|
248
|
-
breadth-first iteration, a node with two parents will be reaced once if the
|
249
|
-
parents are at the same depth in the graph, but twice otherwise.
|
250
|
-
|
251
|
-
====modifiers
|
252
|
-
|
253
|
-
The (({Object#by_depth})) method accepts two modifiers that affect the yielded
|
254
|
-
values, but not the order of iteration:
|
255
|
-
|
256
|
-
for node, ancestors in tree.by_depth(...).with_ancestors
|
257
|
-
|
258
|
-
for branch in tree.by_depth(...).branches
|
259
|
-
|
260
|
-
The (({with_ancestors})) modifier results in the same linearization, but
|
261
|
-
returns, along with each node, the node's array of ancestors, starting with the
|
262
|
-
root of the tree and ending with the immediate parent. In the non-tree case, the
|
263
|
-
ancestor list doesn't contain all ancestors, but just one path from the root to
|
264
|
-
the node. Each such path will occur once during the (({by_depth})) iteration.
|
265
|
-
|
266
|
-
With the (({branches})) modifier, the iteration yields all branches of the tree
|
267
|
-
(or directed acyclic graph). A ((*branch*)) is a path from the root to a leaf
|
268
|
-
node.
|
269
|
-
|
270
|
-
Note that a (({with_ancestors})) iteration yields at every node, but a
|
271
|
-
(({branches})) iteration yields only at leaf nodes.
|
272
|
-
|
273
|
-
====prune
|
274
|
-
|
275
|
-
Pruning the iteration means skipping a node and its descendants and continuing
|
276
|
-
with the nodes that would normally follow them in the iteration. This can be
|
277
|
-
done anywhere in dynamic scope during the iteration by simply throwing the
|
278
|
-
(({:prune})) symbol:
|
279
|
-
|
280
|
-
throw :prune
|
281
|
-
|
282
|
-
If an additional integer argument is supplied:
|
283
|
-
|
284
|
-
throw :prune, n
|
285
|
-
|
286
|
-
then the pruning occurs not at the current node, but at the node (({n})) levels
|
287
|
-
above the current node in the current ancestor list.
|
288
|
-
|
289
|
-
Note that (({prune})) cannot used with the (({each_branch})) modifier discussed
|
290
|
-
above; (({prune})) simply has no useful meaning in this context.
|
291
|
-
|
292
|
-
====examples
|
293
|
-
|
294
|
-
require 'enum/tree'
|
295
|
-
|
296
|
-
# Define a proc to compute the subclasses of a class
|
297
|
-
# It would probably be better to make this a method of Class
|
298
|
-
# and to implement it more efficiently, but for illustration...
|
299
|
-
|
300
|
-
subclasses = proc { |cl|
|
301
|
-
subs = []
|
302
|
-
ObjectSpace.each_object(Class) { |sub|
|
303
|
-
if sub.superclass == cl then subs << sub end
|
304
|
-
}
|
305
|
-
subs
|
306
|
-
}
|
307
|
-
|
308
|
-
print "Subclasses of Numeric:\n"
|
309
|
-
for node in Numeric.by_depth subclasses
|
310
|
-
print node, " "
|
311
|
-
end
|
312
|
-
|
313
|
-
# prints:
|
314
|
-
# Subclasses of Numeric:
|
315
|
-
# Numeric Float Integer Bignum Fixnum
|
316
|
-
|
317
|
-
puts "\n\nBranches:"
|
318
|
-
for branch in Numeric.by_depth(subclasses).branches
|
319
|
-
p branch
|
320
|
-
end
|
321
|
-
|
322
|
-
# prints:
|
323
|
-
# Branches:
|
324
|
-
# [Numeric, Float]
|
325
|
-
# [Numeric, Integer, Bignum]
|
326
|
-
# [Numeric, Integer, Fixnum]
|
327
|
-
|
328
|
-
puts "\nNodes with ancestors:"
|
329
|
-
for x, a in Numeric.by_depth(subclasses).with_ancestors
|
330
|
-
print "\t"*a.size, "node is #{x}, ancestors is #{a.inspect}.\n"
|
331
|
-
end
|
332
|
-
|
333
|
-
# prints:
|
334
|
-
# Nodes with ancestors:
|
335
|
-
# node is Numeric, ancestors is [].
|
336
|
-
# node is Float, ancestors is [Numeric].
|
337
|
-
# node is Integer, ancestors is [Numeric].
|
338
|
-
# node is Bignum, ancestors is [Numeric, Integer].
|
339
|
-
# node is Fixnum, ancestors is [Numeric, Integer].
|
340
|
-
|
341
|
-
See the end of the source file for more examples.
|
342
|
-
|
343
|
-
===version
|
344
|
-
|
345
|
-
Enumerable tools 1.6
|
346
|
-
|
347
|
-
The current version of this software can be found at
|
348
|
-
((<"http://redshift.sourceforge.net/enum
|
349
|
-
"|URL:http://redshift.sourceforge.net/enum>)).
|
350
|
-
|
351
|
-
===license
|
352
|
-
This software is distributed under the Ruby license.
|
353
|
-
See ((<"http://www.ruby-lang.org"|URL:http://www.ruby-lang.org>)).
|
354
|
-
|
355
|
-
===author
|
356
|
-
Joel VanderWerf,
|
357
|
-
((<vjoel@users.sourceforge.net|URL:mailto:vjoel@users.sourceforge.net>))
|
358
|
-
|
359
|
-
=end
|
360
|
-
|
361
|
-
|
362
|
-
if __FILE__ == $0
|
363
|
-
|
364
|
-
tree = [ [1,2,[3]], [4, [], 5] ]
|
365
|
-
for x in tree.by_depth { |x| x.kind_of?(Array) ? x : [] }
|
366
|
-
p x
|
367
|
-
end
|
368
|
-
puts
|
369
|
-
|
370
|
-
class Node
|
371
|
-
|
372
|
-
attr_reader :value, :children
|
373
|
-
|
374
|
-
def initialize value, children = nil
|
375
|
-
@value = value
|
376
|
-
@children = children
|
377
|
-
unless not children or children.respond_to? :each
|
378
|
-
raise ArgumentError, "children must be nil or have an each method."
|
379
|
-
end
|
380
|
-
end
|
381
|
-
|
382
|
-
end
|
383
|
-
|
384
|
-
tree = Node.new(0,
|
385
|
-
[Node.new(1,
|
386
|
-
[Node.new(2,
|
387
|
-
[Node.new(3)]),
|
388
|
-
Node.new(4),
|
389
|
-
Node.new(5)]),
|
390
|
-
Node.new(6)])
|
391
|
-
|
392
|
-
for node in tree.by_depth :children
|
393
|
-
if node.value == 4
|
394
|
-
throw :prune, 1
|
395
|
-
end
|
396
|
-
print node.value
|
397
|
-
end
|
398
|
-
puts
|
399
|
-
|
400
|
-
for node in tree.by_breadth :children
|
401
|
-
print node.value
|
402
|
-
end
|
403
|
-
puts
|
404
|
-
|
405
|
-
# Define a proc to compute the subclasses of a Class
|
406
|
-
# It would probably be better to make this a method of Class
|
407
|
-
# and to implement it more efficiently, but for illustration...
|
408
|
-
subclasses = proc { |cl|
|
409
|
-
subs = []
|
410
|
-
ObjectSpace.each_object(Class) { |sub|
|
411
|
-
if sub.superclass == cl then subs << sub end
|
412
|
-
}
|
413
|
-
subs
|
414
|
-
}
|
415
|
-
|
416
|
-
print "\nSubclasses of Numeric:\n"
|
417
|
-
for node in Numeric.by_depth subclasses
|
418
|
-
print node, " "
|
419
|
-
end
|
420
|
-
print "\n\n"
|
421
|
-
|
422
|
-
puts "Branches:"
|
423
|
-
for branch in Numeric.by_depth(subclasses).branches
|
424
|
-
p branch
|
425
|
-
end
|
426
|
-
puts
|
427
|
-
|
428
|
-
puts "Nodes with ancestors:"
|
429
|
-
for x, a in Numeric.by_depth(subclasses).with_ancestors
|
430
|
-
print "\t"*a.size, "node is #{x}, ancestors is #{a.inspect}.\n"
|
431
|
-
end
|
432
|
-
puts
|
433
|
-
|
434
|
-
# print the inheritance hierarchy of a given class
|
435
|
-
if ARGV[0]
|
436
|
-
root = Object.const_get(ARGV[0])
|
437
|
-
for x, a in root.by_depth(subclasses).with_ancestors
|
438
|
-
print "\t"*a.size, x, "\n"
|
439
|
-
end
|
440
|
-
end
|
441
|
-
|
442
|
-
end
|
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
|