File Coverage

blib/lib/Randomize.pm
Criterion Covered Total %
statement 323 368 87.7
branch 100 122 81.9
condition 12 12 100.0
subroutine 12 13 92.3
pod 0 4 0.0
total 447 519 86.1


line stmt bran cond sub pod time code
1             package Randomize;
2              
3             =head1 NAME
4              
5             Randomize - Perl extension for randomizing things.
6              
7             =head1 SYNOPSIS
8              
9             use Randomize;
10             my $randomizer = Randomize->new(\@rules);
11             print "There are ", $randomizer->permutations(),
12             " different possible outcomes.\n";
13             while (1) {
14             my $random_hash = $randomizer->generate();
15             }
16              
17             =head1 DESCRIPTION
18              
19             This packages takes a set of randomization rules in the form of an
20             array reference, and creates random hashes on request based on
21             the rules given.
22              
23             I know that doesn't make sense, so here's an example.
24              
25             my @randomizer_rules =
26             [ {Field => 'Street',
27             Values => [{Data => ['Preston', 'Hillcrest'],
28             Weight => 1},
29             {Data => ['Coit'],
30             Weight => 2}]},
31             {Field => 'Number',
32             Values => [18100..18299]}
33             };
34              
35             my $randomizer = Randomize->new(\@randomizer_rules);
36             while (1)
37             my $hashref = $randomizer->generate();
38             }
39              
40             The key is @randomizer_rules. What this list tells Randomizer is that,
41             every time you invoke the generate() method, you want to get back a reference
42             to a hash that looks like:
43              
44             $hashref = { Street => 'Preston',
45             Number => 18111 };
46              
47             where the Number is between 18100 and 18299 and the Street is either Preston,
48             Hillcrest, or Coit. Further, you want the numbers to be evenly distributed,
49             but you want the street to be Coit half the time, and evenly distributed
50             between Preston and Hillcrest the rest of the time.
51              
52             So, if you called $randomizer->generate() 1000 times, you'd get roughly
53             500 addresses on Coit and 250 addresses each on Preston and Hillcrest.
54              
55             Let's look at a more complicated @randomizer_rules now.
56              
57             my @randomizer_rules =
58             ( {Field => 'Street',
59             Values => [{Data => ['Preston', 'Hillcrest'],
60             Weight => 1},
61             {Data => ['Coit'],
62             Weight => 2}]},
63             {Field => 'Number',
64             Values => [{Precondition => "<> eq 'Preston'",
65             Alternatives => [{Data => [18100..18199],
66             Weight => 1},
67             {Data => [18200..18299],
68             Weight => 9}]},
69             {Precondition => 'DEFAULT',
70             Alternatives => [{Data => [18100..18299],
71             Weight => 1}]}]}
72             );
73              
74             Given this, the generate() method will still return a hash reference in
75             the form
76              
77             $hashref = { Street => 'Preston',
78             Number => 18111 };
79              
80             with the same streets and address ranges. However, if the street
81             picked happens to be Preston, 90% of the addresses generated
82             will be in the range 18200 to 18299.
83              
84             In final example, note the Retry_If clause:
85              
86             my @randomizer_rules =
87             ( {Field => 'Street',
88             Values => [{Data => ['Preston', 'Hillcrest'],
89             Weight => 1},
90             {Data => ['Coit'],
91             Weight => 2}]},
92             {Field => 'Number',
93             Values => [{Precondition => "<> eq 'Preston'",
94             Alternatives => [{Data => [18100..18199],
95             Weight => 1},
96             {Data => [18200..18299],
97             Weight => 9}],
98             Retry_If => ['defined $main::addr1 && <> == $main::addr1->{Number}']},
99             {Precondition => 'DEFAULT',
100             Alternatives => [{Data => [18100..18299],
101             Weight => 1}]}]}
102             );
103              
104             my $randomizer = Randomize->new(\@randomizer_rules);
105             while (1)
106             $main::addr1 = $main::addr2 = undef;
107             $main::addr1 = $randomizer->generate();
108             $main::addr2 = $randomizer->generate();
109             }
110              
111             In this example, we're generating pairs of addresses. The Retry_If clause
112             ensures that we never get a pair of identical addresses on Preston. It's
113             still possible to get identical addresses on Coit or Hillcrest, however.
114              
115             Retry_If clauses may also appear at the same level as Field and Values,
116             like so:
117              
118             my @randomizer_rules =
119             ( {Field => 'Street',
120             Values => ['Preston', 'Hillcrest', 'Coit']},
121             {Field => 'Number',
122             Values => [18100..18299],
123             Retry_If => ['<> eq 'Coit' && <> eq 18200']}
124             );
125              
126             This ruleset tells Randomize to try again if the address generated
127             is 18200 Coit.
128              
129             There is also one special rule that Randomize looks for: "DEBUG".
130             A "DEBUG ON" rule turns debugging messages on so you can see what's
131             happening when you call generate(). It also attempts to print the
132             code it generates to a file. You can optionally pass the filename
133             in, like "DEBUG ON myfile.code", or if you don't specify a file,
134             the default output file is "Randomize.code". If the file can't
135             be opened for writing, a warning is sent to standard error, but
136             execution of your program is otherwise unaffected.
137              
138             Correspondingly, a "DEBUG OFF" rule turns debugging off, although
139             the code is still printed. Placement of "DEBUG ON" and "DEBUG OFF"
140             statements determines which fields debugging information is printed for.
141             For example, take a look at the following ruleset:
142              
143             my @randomizer_rules =
144             ( 'DEBUG ON',
145             {Field => 'Street',
146             Values => ['Preston', 'Hillcrest', 'Coit']},
147             'DEBUG OFF',
148             {Field => 'Number',
149             Values => [18100..18299],
150             Retry_If => ['<> eq 'Coit' && <> eq 18200']},
151             );
152              
153             This ruleset results in debugging information being printed for
154             generation of the "Street" field, but not for the "Number" field,
155             and code will be printed to the file "Randomize.code".
156              
157             NOTE: Randomize cannot currently generate anything other than simple
158             hashes. If you want a complex data structure, you'll have to either
159             build it yourself by moving items around in the returned hash, or by
160             using multiple randomize objects.
161              
162             =head2 EXPORT
163              
164             None.
165              
166             =head1 AUTHOR
167              
168             Brand Hilton
169              
170             =head1 PUBLIC METHODS
171            
172              
173             =cut
174              
175              
176             # $Id: Randomize.pm,v 1.10 2001/04/30 13:09:40 bhilton Exp $
177              
178             # $Log: Randomize.pm,v $
179             # Revision 1.10 2001/04/30 13:09:40 bhilton
180             # Added generate_all method
181             #
182             # Revision 1.9 2001/04/24 21:59:35 bhilton
183             # Documentation updates
184             #
185             # Revision 1.8 2001/04/24 14:02:42 bhilton
186             # - Added permutations method
187             # - Fixed bug that would cause problems if you used both
188             # varieties of Retry_If at the same time
189             #
190             # Revision 1.7 2001/01/23 15:12:35 bhilton
191             # Moving to rev 1.7 for the CPAN bundle.
192             #
193             # Revision 1.6 2001/01/22 15:13:07 bhilton
194             # Added lots of error checking, fixed a couple of minor bugs.
195             #
196             # Revision 1.5 2000/12/01 19:41:08 bhilton
197             # Changed first-level "Alternatives" to "Values".
198             # Added DEBUG flag.
199             #
200             # Revision 1.4 2000/11/21 20:40:16 bhilton
201             # Added "Retry_If" capabilities.
202             #
203             # Revision 1.3 2000/11/18 23:50:59 bhilton
204             # Various improvements and bug fixes.
205             #
206             # Revision 1.2 2000/11/18 22:56:38 bhilton
207             # When you call generate, you can now specify the value of one or more
208             # fields in the hash.
209             #
210             # Revision 1.1 2000/11/18 22:07:59 bhilton
211             # Initial revision
212             #
213              
214             require 5.005_62;
215 1     1   20988 use strict;
  1         2  
  1         43  
216 1     1   5 use warnings;
  1         2  
  1         31  
217 1     1   5 use Data::Dumper;
  1         7  
  1         5576  
218             $Data::Dumper::Deepcopy = 1;
219              
220             our ($VERSION) = '$Revision: 1.10 $'=~/(\d+(\.\d+))/;
221              
222             our $errmsg = '';
223              
224              
225             sub _process_alternatives {
226 23     23   44 my ($fieldname, $valueno, $alts) = @_;
227 23         32 my @array;
228              
229 23         85 foreach my $index (0..$#{$alts}) {
  23         64  
230 42         178 my $ary = $alts->[$index];
231              
232 42 100       174 unless (exists $ary->{Data}) {
233 3         16 $errmsg = "Field $fieldname Value $valueno Alternative $index "
234             . "doesn't contain a Data element";
235 3         28 return;
236             }
237              
238 39 100       311 unless (exists $ary->{Weight}) {
239 2         9 $errmsg = "Field $fieldname Value $valueno Alternative $index "
240             . "doesn't contain a Weight element";
241 2         15 return;
242             }
243              
244 37 100       226 unless (ref $ary->{Data} eq 'ARRAY') {
245 2         10 $errmsg = "Field $fieldname Value $valueno Alternative $index: "
246             . "Data element isn't an array ref.";
247 2         16 return;
248             }
249              
250 35 100       209 unless ($ary->{Weight} =~ /^\d+$/) {
251 2         11 $errmsg = "Field $fieldname Value $valueno Alternative $index: "
252             . "Weight element isn't a positive integer.";
253 2         17 return;
254             }
255              
256 33         136 push @array, (@{$ary->{Data}}) x $ary->{Weight};
  33         282  
257             }
258 14         110 return Data::Dumper->Dump([\@array], ['$stuff']);
259             }
260              
261              
262             # _create_generate_method
263             #
264             # This subroutine creates the Generate method of the randomizer.
265             # It takes the same set of rules that the new() method takes, and
266             # returns a code reference.
267              
268             sub _create_generate_method {
269 30     30   80 my ($rules) = @_;
270              
271 30         33 my $print_filename; # Name of the file to print code to. Also serves
272             # as a flag signalling whether to print code at all.
273              
274 30         63 my $code = "sub {\n"
275             . " my \%retval = \@_;\n"
276             . " my \$stuff;\n"
277             . " my \$debug = 0;\n"
278             . " my \$counter;\n\n";
279              
280 30         45 foreach my $i (0..$#{$rules}) {
  30         98  
281 45 100       240 if ($rules->[$i] =~ /^\s*DEBUG\s/i) {
282 5 100       241 unless ($rules->[$i] =~ /^\s*DEBUG\s+(ON|OFF)\s*(.*?)\s*$/i) {
283 1         53 $errmsg = "Syntax error in DEBUG directive";
284 1         10 return;
285             }
286 4         12 my $onoff = uc $1;
287 4 100 100     84 $print_filename = $2 || 'Randomize.code' if $onoff eq 'ON';
288 4         18 $code .= " \$debug = " . {ON => 1, OFF => 0}->{$onoff} . ";\n\n";
289 4         12 next;
290             }
291              
292 40 100       121 unless (exists $rules->[$i]{Field}) {
293 1         5 $errmsg = "Rule " . ($i+1) . " doesn't contain a field name";
294 1         8 return;
295             }
296              
297 39 100       109 unless (exists $rules->[$i]{Values}) {
298 1         4 $errmsg = "Field '$rules->[$i]{Field}' doesn't have a Values field";
299 1         7 return;
300             }
301              
302              
303 38         62 my $fieldname = $rules->[$i]{Field};
304              
305 38 50       115 if (ref $rules->[$i]{Values} eq 'ARRAY') {
306 38         52 my $outer_retry_clause;
307 38         48 my $outer_indent = ' ';
308 38 100       101 if (exists $rules->[$i]{Retry_If}) {
309 7         29 $outer_retry_clause = '('
310 7         15 . join(') || (', @{$rules->[$i]{Retry_If}})
311             . ')';
312 7         92 $outer_retry_clause =~ s/<<(.*?)>>/\$retval{$1}/g;
313             }
314 38 100       186 if (ref $rules->[$i]{Values}[0] eq '') {
    50          
315             # In the form [1..15] or ['one', 'two', 'three']
316 13 100       50 if (exists $rules->[$i]{Retry_If}) {
317 1         4 $code .= _retry_if_start_for_generate($outer_retry_clause,
318             $fieldname,
319             $outer_indent);
320 1         2 $outer_indent .= ' ';
321             }
322 13         151 my $temp_code = Data::Dumper->Dump([$rules->[$i]{Values}], ['$stuff']);
323 13         1176 $temp_code =~ s/^/ /mg;
324 13         27 $code .= $temp_code;
325 13 100       96 if (exists $rules->[$i]{Retry_If}) {
326 1         5 $code .= _retry_if_finish_for_generate($outer_retry_clause,
327             $fieldname,
328             $outer_indent);
329             }
330             else {
331 12         39 $code .= " \$retval{$fieldname} ||= \$stuff->[rand \@\$stuff];\n";
332 12         49 $code .= " print \"$fieldname just set to \$retval{$fieldname}\\n\" if \$debug;\n\n";
333             }
334             }
335             elsif (ref $rules->[$i]{Values}[0] eq 'HASH') {
336 25 100       86 if (exists $rules->[$i]{Values}[0]{Alternatives}) {
337             # In the form [{Precondition => "<> eq 'Preston'",
338             # Alternatives => [{Data => [18100..18199],
339             # Weight => 1},
340             # {Data => [18200..18299],
341             # Weight => 9}],
342             # Retry_If => "<> == 18113"},
343             # {Precondition => 'DEFAULT',
344             # Alternatives => [{Data => [18100..18299],
345             # Weight => 1}]}]
346 18         32 my $done = 0;
347 18         24 my $branchno = 1;
348 18         29 $code .= " \$counter = 0;\n";
349 18         26 foreach my $j (0..$#{$rules->[$i]{Values}}) {
  18         55  
350 38         279 my $hash = $rules->[$i]{Values}[$j];
351              
352 38 100       114 unless (exists $hash->{Precondition}) {
353 1         7 $errmsg = "Field '$fieldname', Value " . ($j+1) .
354             ": No precondition given.";
355 1         8 return;
356             }
357              
358 37 50       128 unless (exists $hash->{Alternatives}) {
359 0         0 $errmsg = "Field '$fieldname', Value " . ($j+1) .
360             ": No alternatives given.";
361 0         0 return;
362             }
363              
364 37         134 my $condition = $hash->{Precondition};
365 37 100       107 if ($condition eq 'DEFAULT') {
366 17 100       41 if ($branchno > 1) {
367 10         21 $code .= " else {\n";
368 10         65 $code .= " print \"Field $fieldname, inside else\\n\" if \$debug;\n";
369             }
370             else {
371 7         11 $code .= " if (1) {\n";
372 7         14 $code .= " print \"Field $fieldname, inside if (1)\\n\" if \$debug;\n";
373             }
374 17         47 $done = 1;
375             }
376             else {
377 20 50       123 if ($done) {
378 0         0 $errmsg = "Error in field '$fieldname': " .
379             "DEFAULT must be the last condition listed.";
380 0         0 return;
381             }
382 20         198 $condition =~ s/<<(.*?)>>/\$retval{$1}/g;
383 20         39 $code .= ' ';
384 20 100       207 $code .= 'els' if $branchno > 1;
385 20         45 $code .= "if ($condition) {\n";
386 20         46 $code .= " print \"Field $fieldname, inside branch number $branchno\\n\" if \$debug;\n";
387 20         33 $branchno++;
388             }
389              
390 37         42 my $retry_clause;
391 37         51 my $indent = ' ';
392 37 100       97 if (exists $hash->{Retry_If}) {
393 12         44 $retry_clause = '('
394 12         23 . join(') || (', @{$hash->{Retry_If}})
395             . ')';
396 12         109 $retry_clause =~ s/<<(.*?)>>/\$retval{$1}/g;
397             }
398              
399 37 100 100     283 if (exists $hash->{Retry_If} || exists $rules->[$i]{Retry_If}) {
400 24         35 my @clauses;
401 24 100       80 push @clauses, $retry_clause if exists $hash->{Retry_If};
402 24 100       81 push @clauses, $outer_retry_clause
403             if exists $rules->[$i]{Retry_If};
404 24         68 $retry_clause = '(' . join(' || ', @clauses) . ')';
405 24         63 $code .= _retry_if_start_for_generate($retry_clause,
406             $fieldname,
407             $indent);
408 24         55 $indent .= ' ';
409             }
410              
411 37         44 my $temp_code;
412 37 100       148 if (ref $hash->{Alternatives}[0] eq '') {
    50          
413             # In the form [1..15] or ['one', 'two', 'three']
414 21         179 $temp_code = Data::Dumper->Dump([$hash->{Alternatives}], ['$stuff']);
415             }
416             elsif (ref $hash->{Alternatives}[0] eq 'HASH') {
417 16 100       617 $temp_code = _process_alternatives($fieldname, $j,
418             $hash->{Alternatives})
419             or return;
420             }
421             else {
422 0         0 $errmsg = "Error in Field '$fieldname'. " .
423             "First element of the conditional Alternatives " .
424             "array is neither a scalar nor an array.";
425 0         0 return;
426             }
427 33         3809 $temp_code =~ s/^/$indent/mg;
428 33         73 $code .= $temp_code;
429              
430 33 100 100     505 if (exists $hash->{Retry_If} || exists $rules->[$i]{Retry_If}) {
431 20         61 $code .= _retry_if_finish_for_generate($retry_clause,
432             $fieldname,
433             $indent);
434             }
435             else {
436 13         38 $code .= $indent . "\$retval{$fieldname} ||= \$stuff->[rand \@\$stuff];\n\n";
437 13         36 $code .= $indent . "print \"$fieldname just set to \$retval{$fieldname}\\n\" if \$debug;\n\n";
438             }
439 33         134 $code .= " }\n\n";
440             }
441             #if (exists $rules->[$i]{Retry_If}) {
442             # $code .= substr($outer_indent, 0, length($outer_indent)-2) . "}\n";
443             # $code .= substr($outer_indent, 0, length($outer_indent)-4) . "}\n";
444             #}
445             }
446             else {
447             # In the form [{Data => [1..5],
448             # Weight => 1},
449             # {Data => [6..10],
450             # Weight => 2}]
451 7 100       27 if (exists $rules->[$i]{Retry_If}) {
452 1         3 $code .= _retry_if_start_for_generate($outer_retry_clause,
453             $fieldname,
454             $outer_indent);
455 1         2 $outer_indent .= ' ';
456             }
457 7   100     26 my $temp_code .= (_process_alternatives($fieldname, 0,
458             $rules->[$i]{Values})
459             or return);
460 2         343 $temp_code =~ s/^/$outer_indent/mg;
461 2         6 $code .= $temp_code;
462 2 100       9 if (exists $rules->[$i]{Retry_If}) {
463 1         7 $code .= _retry_if_finish_for_generate($outer_retry_clause,
464             $fieldname,
465             $outer_indent);
466             }
467             else {
468 1         4 $code .= " \$retval{$fieldname} ||= \$stuff->[rand \@\$stuff];\n\n";
469 1         7 $code .= " print \"$fieldname just set to \$retval{$fieldname}\\n\" if \$debug;\n\n";
470             }
471             }
472             }
473             else {
474 0         0 $errmsg = "Error in field '$fieldname': " .
475             "First element of Values is neither a scalar nor a hash.";
476 0         0 return;
477             }
478             }
479             else {
480 0         0 $errmsg = "Error in field '$fieldname': " .
481             "Values element should be an array.";
482 0         0 return;
483             }
484             }
485              
486 17         43 $code .= " return \\\%retval;\n}\n";
487              
488 17 100       42 if ($print_filename) {
489 2 50       40363 if (open CODE, ">$print_filename") {
490 2         30 print CODE "# generate() method\n\n", $code;
491 2         206 close CODE;
492             }
493             else {
494 0         0 print STDERR "Failed to open $print_filename for writing: $!";
495             }
496             }
497              
498 17         13502 my $retval = eval $code;
499 17 50       80 unless (defined $retval) {
500 0         0 $errmsg = $@;
501 0         0 return;
502             }
503 17         112 return $retval;
504             }
505              
506              
507             # _create_permutations_generateall_method
508             #
509             # This subroutine creates the anonymous sub that implements both
510             # the permutations() and the generate_all() methods of the randomizer.
511             # It takes the same set of rules that the new() method takes, and
512             # returns a code reference.
513              
514             sub _create_permutations_generateall_method {
515 17     17   66 my ($rules) = @_;
516              
517 17         23 my $print_filename; # Name of the file to print code to. Also serves
518             # as a flag signalling whether to print code at all.
519 17         30 my $nestlevel = 0;
520            
521 17         25 my @fieldnames;
522              
523 17         31 my $retry_code = '';
524              
525 17         35 my $code = "sub {\n"
526             . " my \$count_or_generate = shift;\n"
527             . " my \%parms = \@_;\n"
528             . " my \%retval = \@_;\n"
529             . " my \$stuff;\n"
530             . " my \$debug = 0;\n"
531             . " my \@retlist;\n"
532             . " my \$permutations = 0;\n\n";
533              
534 17         27 foreach my $i (0..$#{$rules}) {
  17         63  
535 32 100       178 if ($rules->[$i] =~ /^\s*DEBUG\s/i) {
536 4 50       37 unless ($rules->[$i] =~ /^\s*DEBUG\s+(ON|OFF)\s*(.*?)\s*$/i) {
537 0         0 $errmsg = "Syntax error in DEBUG directive";
538 0         0 return;
539             }
540 4         10 my $onoff = uc $1;
541 4 100 100     29 $print_filename = $2 || 'Randomize.code' if $onoff eq 'ON';
542 4         20 $code .= " \$debug = " . {ON => 1, OFF => 0}->{$onoff} . ";\n\n";
543 4         15 next;
544             }
545              
546 28 50       85 unless (exists $rules->[$i]{Field}) {
547 0         0 $errmsg = "Rule " . ($i+1) . " doesn't contain a field name";
548 0         0 return;
549             }
550              
551 28 50       74 unless (exists $rules->[$i]{Values}) {
552 0         0 $errmsg = "Field '$rules->[$i]{Field}' doesn't have a Values field";
553 0         0 return;
554             }
555              
556              
557 28         56 my $fieldname = $rules->[$i]{Field};
558              
559 28 50       95 if (ref $rules->[$i]{Values} eq 'ARRAY') {
560 28         34 my $outer_retry_clause;
561 28         46 my $outer_indent = ' ';
562 28 100       91 if (exists $rules->[$i]{Retry_If}) {
563 7         36 $outer_retry_clause = '('
564 7         14 . join(') || (', @{$rules->[$i]{Retry_If}})
565             . ')';
566 7         150 $outer_retry_clause =~ s/<<(.*?)>>/\$retval{$1}/g;
567 7         20 $retry_code .= " if ($outer_retry_clause) {\n";
568 7         14 $retry_code .= " print \" rejected\\n\" if \$debug;\n";
569 7         10 $retry_code .= " next;\n";
570 7         16 $retry_code .= " }\n";
571             #$code .= _retry_if_start_for_permutations($outer_retry_clause,
572             # $fieldname,
573             # $outer_indent);
574 7         13 $outer_indent .= ' ';
575             }
576 28 100       148 if (ref $rules->[$i]{Values}[0] eq '') {
    50          
577             # In the form [1..15] or ['one', 'two', 'three']
578 13         92 my $temp_code = Data::Dumper->Dump([$rules->[$i]{Values}], ['$stuff']);
579 13         1461 $temp_code =~ s/^/ /mg;
580 13         39 $code .= " if (\$parms{$fieldname}) {\n";
581 13         34 $code .= " \$stuff = [\"\$parms{$fieldname}\"];\n";
582 13         36 $code .= " }\n";
583 13         24 $code .= " else {\n";
584 13         24 $code .= $temp_code;
585 13         20 $code .= " }\n";
586 13         71 $code .= " foreach my \$thingy (\@\$stuff) {\n";
587 13         27 $code .= " \$retval{$fieldname} = \$thingy;\n";
588 13         30 $code .= " print \"$fieldname just set to \$retval{$fieldname}\\n\" if \$debug;\n\n";
589 13         26 $fieldnames[$nestlevel] = $fieldname;
590 13         39 $nestlevel++;
591             }
592             elsif (ref $rules->[$i]{Values}[0] eq 'HASH') {
593 15 100       142 if (exists $rules->[$i]{Values}[0]{Alternatives}) {
594             # In the form [{Precondition => "<> eq 'Preston'",
595             # Alternatives => [{Data => [18100..18199],
596             # Weight => 1},
597             # {Data => [18200..18299],
598             # Weight => 9}],
599             # Retry_If => "<> == 18113"},
600             # {Precondition => 'DEFAULT',
601             # Alternatives => [{Data => [18100..18299],
602             # Weight => 1}]}]
603 13         31 my $done = 0;
604 13         21 my $branchno = 1;
605 13         33 $code .= " if (\$parms{$fieldname}) {\n";
606             #$code .= " \$stuff = [\"\$parms{$fieldname}\"];\n";
607 13         25 $code .= " \$stuff = [\$parms{$fieldname}];\n";
608 13         23 $code .= " }\n";
609 13         28 foreach my $j (0..$#{$rules->[$i]{Values}}) {
  13         41  
610 33         85 my $hash = $rules->[$i]{Values}[$j];
611              
612 33 50       92 unless (exists $hash->{Precondition}) {
613 0         0 $errmsg = "Field '$fieldname', Value " . ($j+1) .
614             ": No precondition given.";
615 0         0 return;
616             }
617              
618 33 50       93 unless (exists $hash->{Alternatives}) {
619 0         0 $errmsg = "Field '$fieldname', Value " . ($j+1) .
620             ": No alternatives given.";
621 0         0 return;
622             }
623              
624 33         96 my $condition = $hash->{Precondition};
625 33 100       75 if ($condition eq 'DEFAULT') {
626 13 100       40 if ($branchno > 1) {
627 10         17 $code .= " else {\n";
628 10         21 $code .= " print \"Field $fieldname, inside else\\n\" if \$debug;\n";
629             }
630             else {
631 3         6 $code .= " if (1) {\n";
632 3         10 $code .= " print \"Field $fieldname, inside if (1)\\n\" if \$debug;\n";
633             }
634 13         17 $done = 1;
635             }
636             else {
637 20 50       41 if ($done) {
638 0         0 $errmsg = "Error in field '$fieldname': " .
639             "DEFAULT must be the last condition listed.";
640 0         0 return;
641             }
642 20         407 $condition =~ s/<<(.*?)>>/\$retval{$1}/g;
643 20         46 $code .= " elsif ($condition) {\n";
644 20         239 $code .= " print \"Field $fieldname, inside branch number $branchno\\n\" if \$debug;\n";
645 20         32 $branchno++;
646             }
647              
648 33         39 my $retry_clause;
649 33         51 my $indent = ' ';
650 33 100       128 if (exists $hash->{Retry_If}) {
651 8         226 $retry_clause = '('
652 8         17 . join(') || (', @{$hash->{Retry_If}})
653             . ')';
654 8         298 $retry_clause =~ s/<<(.*?)>>/\$retval{$1}/g;
655 8         305 $retry_code .= " if ($retry_clause) {\n";
656 8         15 $retry_code .= " print \" rejected\\n\" if \$debug;\n";
657 8         14 $retry_code .= " next;\n";
658 8         14 $retry_code .= " }\n";
659             #$code .= _retry_if_start_for_permutations($retry_clause,
660             # $fieldname,
661             # $indent);
662 8         13 $indent .= ' ';
663             }
664              
665 33         176 my $temp_code;
666 33 100       164 if (ref $hash->{Alternatives}[0] eq '') {
    50          
667             # In the form [1..15] or ['one', 'two', 'three']
668 21         111 $temp_code = Data::Dumper->Dump([$hash->{Alternatives}], ['$stuff']);
669             }
670             elsif (ref $hash->{Alternatives}[0] eq 'HASH') {
671 12         15 my @array;
672 12         17 foreach my $index (0..$#{$hash->{Alternatives}}) {
  12         44  
673 24         47 my $ary = $hash->{Alternatives}[$index];
674 24         25 push @array, @{$ary->{Data}};
  24         167  
675             }
676 12         428 $temp_code = Data::Dumper->Dump([\@array], ['$stuff']);
677             }
678             else {
679 0         0 $errmsg = "Error in Field '$fieldname'. " .
680             "First element of the conditional Alternatives " .
681             "array is neither a scalar nor an array.";
682 0         0 return;
683             }
684 33         3402 $temp_code =~ s/^/$indent/mg;
685 33         62 $code .= $temp_code;
686 33         90 $code .= " }\n";
687             }
688 13         28 $code .= " foreach my \$thingy (\@\$stuff) {\n";
689 13         21 $code .= " my \$stuff;\n";
690 13         27 $code .= " \$retval{$fieldname} = \$thingy;\n";
691 13         42 $code .= " print \"$fieldname just set to \$retval{$fieldname}\\n\" if \$debug;\n\n";
692 13         29 $fieldnames[$nestlevel] = $fieldname;
693 13         41 $nestlevel++;
694             }
695             else {
696             # In the form [{Data => [1..5],
697             # Weight => 1},
698             # {Data => [6..10],
699             # Weight => 2}]
700 2         4 my @array;
701 2         3 foreach my $index (0..$#{$rules->[$i]{Values}}) {
  2         7  
702 5         11 my $ary = $rules->[$i]{Values}[$index];
703 5         6 push @array, @{$ary->{Data}};
  5         23  
704             }
705 2         15 my $temp_code = Data::Dumper->Dump([\@array], ['$stuff']);
706 2         178 $temp_code =~ s/^/$outer_indent/mg;
707 2         9 $code .= $outer_indent . "if (\$parms{$fieldname}) {\n";
708 2         5 $code .= $outer_indent . " \$stuff = [\"\$parms{$fieldname}\"];\n";
709 2         5 $code .= $outer_indent . "}\n";
710 2         2 $code .= $outer_indent . "else {\n";
711 2         5 $code .= $temp_code;
712 2         4 $code .= $outer_indent . "}\n";
713 2         5 $code .= $outer_indent . "foreach my \$thingy (\@\$stuff) {\n";
714 2         6 $code .= $outer_indent . " \$retval{$fieldname} = \$thingy;\n";
715 2         6 $code .= $outer_indent . " print \"$fieldname just set to \$retval{$fieldname}\\n\" if \$debug;\n\n";
716 2         5 $fieldnames[$nestlevel] = $fieldname;
717 2         10 $nestlevel++;
718             }
719             }
720             else {
721 0         0 $errmsg = "Error in field '$fieldname': " .
722             "First element of Values is neither a scalar nor a hash.";
723 0         0 return;
724             }
725             }
726             else {
727 0         0 $errmsg = "Error in field '$fieldname': " .
728             "Values element should be an array.";
729 0         0 return;
730             }
731             }
732              
733 17         40 $code .= $retry_code;
734 17         35 $code .= " if (\$count_or_generate eq 'count') {\n";
735 17         41 $code .= " \$permutations++;\n";
736 17         27 $code .= " }\n";
737 17         30 $code .= " else {\n";
738 17         23 $code .= " push \@retlist, {\%retval};\n";
739 17         89 $code .= " }\n";
740              
741 17         219 while ($nestlevel) {
742 28         35 $nestlevel--;
743 28         539 $code .= "delete \$retval{$fieldnames[$nestlevel]};\n";
744 28         65 $code .= "}\n";
745             }
746 17         26 $code .= "\n\n";
747 17         29 $code .= " return \$count_or_generate eq 'count' ? \$permutations\n";
748 17         23 $code .= " : \@retlist;\n";
749 17         25 $code .= "}\n";
750              
751 17 100       53 if ($print_filename) {
752 2 50       180 if (open CODE, ">>$print_filename") {
753 2         18 print CODE "\n\n\n# permutations() and generate_all() method\n\n", $code;
754 2         69 close CODE;
755             }
756             else {
757 0         0 print STDERR "Failed to open $print_filename for append: $!";
758             }
759             }
760              
761 17         10260 my $retval = eval $code;
762 17 50       95 unless (defined $retval) {
763 0         0 $errmsg = $@;
764 0         0 return;
765             }
766 17         106 return $retval;
767             }
768              
769              
770             =head1 new
771              
772             =head2 Description
773              
774             This is the constructor for Randomize objects. It takes one parameter:
775             a reference to an array containing randomizer rules. From these rules,
776             the generate() and permutations() methods are created. If an error is
777             detected in the rules, the package variable $Randomize::errmsg will contain
778             the error message and new() will return undef.
779              
780             =head2 Syntax
781              
782             $randomizer = Randomize->new(\@rules);
783              
784             $randomizer - On success, a Randomize object. On failure, undef
785             is returned and $Randomize::errmsg will contain a
786             descriptive error message.
787              
788             \@rules - A reference to an array containing Randomize rules,
789             as described in the DESCRIPTION section.
790              
791             =cut
792              
793             sub new {
794 30     30 0 7819 my ($class, $rules) = @_;
795 30         67 $errmsg = '';
796 30 50       93 $errmsg = "No class specified", return unless $class;
797 30 50       83 $errmsg = "No rules specified", return unless $rules;
798 30 50       100 $errmsg = "\$rules is not an array ref", return
799             unless ref $rules eq 'ARRAY';
800              
801 30         52 my $self = {};
802              
803 30 100       85 return unless $self->{Generate} = _create_generate_method($rules);
804              
805 17 50       57 return unless $self->{Permutations_and_Generate_All} =
806             _create_permutations_generateall_method($rules);
807              
808              
809 17         98 bless $self, $class;
810              
811             } # new
812              
813              
814              
815              
816             ##################################################################
817             #
818             # _retry_if_start_for_generate
819             #
820             # Generates code for the Retry_If clause for the generate method.
821             #
822             # Syntax:
823             #
824             # $code = _retry_if_start_for_generate($retry_clause, $fieldname, $indent);
825              
826             sub _retry_if_start_for_generate {
827 26     26   49 my ($retry_clause, $fieldname, $indent) = @_;
828              
829 26         40 my $code = '';
830 26         57 $code .= "if (exists \$retval{$fieldname}) {\n";
831 26         64 $code .= " print \"The user specified a value for $fieldname\\n\" if \$debug;\n";
832 26         55 $code .= " if ($retry_clause) {\n";
833 26         72 $code .= " die \"The user-specified value for $fieldname violates the Retry_If rule.\"\n";
834 26         39 $code .= " }\n";
835 26         28 $code .= "}\n";
836 26         39 $code .= "else {\n";
837 26         124 $code .= " my \$done = 0;\n";
838 26         30 $code .= " while (!\$done) {\n";
839 26         83 $code .= " print \"Getting ready to choose a value for $fieldname\\n\" if \$debug;\n";
840 26         36 $code .= " \$counter++;\n";
841 26         747 $code =~ s/^/$indent/mg;
842              
843 26         77 return $code;
844             }
845              
846              
847              
848              
849             ##################################################################
850             #
851             # _retry_if_finish_for_generate
852             #
853             # Generates code for the Retry_If clause for the generate method.
854             #
855             # Syntax:
856             #
857             # $code = _retry_if_finish_for_generate($retry_clause, $fieldname, $indent);
858              
859             sub _retry_if_finish_for_generate {
860 22     22   46 my ($retry_clause, $fieldname, $indent) = @_;
861 22         54 my $code = $indent . "\$retval{$fieldname} = \$stuff->[rand \@\$stuff];\n";
862 22         76 $code .= $indent . "print \"$fieldname just set to \", Dumper(\$retval{$fieldname}), \"\\n\" if \$debug;\n\n";
863 22         61 $code .= $indent . "if ($retry_clause) {\n";
864 22         121 $code .= $indent . " print \"Gonna have to retry\\n\" if \$debug;\n";
865 22         40 $code .= $indent . " die <= 100;\n";
866 22         43 $code .= "Couldn't find a usable value for $fieldname in 100 tries.\n";
867 22         34 $code .= "Maybe your retry clauses are too restrictive.\n";
868 22         65 $code .= "EOT\n";
869 22         30 $code .= $indent . "}\n";
870 22         32 $code .= $indent . "else {\n";
871 22         45 $code .= $indent . " print \"Passed the retry clause.\\n\" if \$debug;\n";
872 22         32 $code .= $indent . " \$done = 1;\n";
873 22         32 $code .= $indent . "}\n";
874 22         62 $code .= substr($indent, 0, length($indent)-2) . "}\n";
875 22         47 $code .= substr($indent, 0, length($indent)-4) . "}\n";
876 22         77 return $code;
877             }
878              
879              
880              
881              
882             ##################################################################
883             #
884             # _retry_if_start_for_permutations
885             #
886             # Generates code for the Retry_If clause for the permutations method.
887             #
888             # Syntax:
889             #
890             # $code = _retry_if_start_for_permutations($retry_clause, $fieldname, $indent);
891              
892             sub _retry_if_start_for_permutations {
893 0     0   0 my ($retry_clause, $fieldname, $indent) = @_;
894              
895 0         0 my $code = '';
896 0         0 $code .= "if (exists \$retval{$fieldname}) {\n";
897 0         0 $code .= " print \"The user specified a value for $fieldname\\n\" if \$debug;\n";
898 0         0 $code .= " if ($retry_clause) {\n";
899 0         0 $code .= " die \"The user-specified value for $fieldname violates the Retry_If rule.\"\n";
900 0         0 $code .= " }\n";
901 0         0 $code .= "}\n";
902 0         0 $code .= "else {\n";
903 0         0 $code =~ s/^/$indent/mg;
904              
905 0         0 return $code;
906             }
907              
908              
909             =head1 generate
910              
911             =head2 Description
912              
913             This method returns a reference to a hash. The hash contains the fields
914             you specified in your randomizer rules. Each call to generate() gives you
915             a new hash, with a new set of randomized values.
916              
917             NOTE: If you wish to specify a value for one or more fields of the hash,
918             you can pass in the field and its value.
919              
920             =head2 Syntax
921              
922             $hashref = $randomizer->generate( [ $fieldname, $value, ... ] );
923              
924             $hashref - A hash reference returned by generate().
925              
926             $randomizer - A Randomize object.
927              
928             $fieldname - The name of a field in the hash.
929              
930             $value - The value you wish that field to take
931             this time through.
932              
933             =cut
934              
935              
936              
937             sub generate {
938 18056     18056 0 157200 my $self = shift;
939 18056         26236 &{$self->{Generate}}(@_);
  18056         460056  
940             }
941              
942              
943             =head1 permutations
944              
945             =head2 Description
946              
947             This method returns the number of permutations of the hash you've
948             specified.
949              
950             NOTE: If you wish to specify a value for one or more fields of the hash,
951             you can pass in the field and its value.
952              
953             =head2 Syntax
954              
955             $permutations = $randomizer->permutations( [ $fieldname, $value, ... ] );
956              
957             $permutations - The exact number of permutations of the
958             hash you've specified.
959              
960             $randomizer - A Randomize object.
961              
962             $fieldname - The name of a field in the hash.
963            
964             $value - The value you wish that field to take
965             this time through.
966              
967             =cut
968              
969              
970              
971             sub permutations {
972 5     5 0 758 my $self = shift;
973 5         12 &{$self->{Permutations_and_Generate_All}}('count',@_);
  5         350  
974             }
975              
976              
977              
978              
979              
980             =head1 generate_all
981              
982             =head2 Description
983              
984             This method returns a list containing every permutation of the hash you've
985             specified.
986              
987             NOTE: If you wish to specify a value for one or more fields of the hash,
988             you can pass in the field and its value.
989              
990             =head2 Syntax
991              
992             @permutations = $randomizer->generate_all( [ $fieldname, $value, ... ] );
993              
994             @permutations - A list containing every possible permutation
995             of the hash you've specified.
996              
997             $randomizer - A Randomize object.
998              
999             $fieldname - The name of a field in the hash.
1000            
1001             $value - The value you wish that field to take
1002             this time through.
1003              
1004             =cut
1005              
1006              
1007              
1008             sub generate_all {
1009 5     5 0 1934 my $self = shift;
1010 5         13 &{$self->{Permutations_and_Generate_All}}('generate',@_);
  5         235  
1011             }
1012              
1013             1;
1014