File Coverage

blib/lib/File/System/Test.pm
Criterion Covered Total %
statement 205 208 98.5
branch 96 182 52.7
condition 14 16 87.5
subroutine 18 18 100.0
pod 7 8 87.5
total 340 432 78.7


line stmt bran cond sub pod time code
1             package File::System::Test;
2              
3 6     6   5409 use strict;
  6         11  
  6         191  
4 6     6   30 use warnings;
  6         7  
  6         251  
5              
6             our $VERSION = '1.16';
7              
8             require Exporter;
9 6     6   30 use File::Basename ();
  6         8  
  6         94  
10 6     6   7808 use Test::Builder;
  6         138328  
  6         29927  
11              
12             my $test = Test::Builder->new;
13              
14             our @ISA = qw( Exporter );
15             our @EXPORT = qw(
16             prepare
17             is_root_sane
18             is_object_sane
19             is_container_sane
20             is_content_sane
21             is_content_writable
22             is_container_mobile
23             is_content_mobile
24             is_glob_and_find_consistent
25             );
26              
27             sub _check {
28 18665     18665   31852 my $true = shift;
29 18665         23899 my $name = shift;
30              
31 18665 50       39711 unless ($true) {
32 0         0 $test->ok(0, $name);
33 0         0 $test->diag(join('', @_));
34 0         0 return 0;
35             } else {
36 18665         81303 return 1;
37             }
38             }
39              
40             =head1 NAME
41              
42             File::System::Test - Module for testing file system drivers
43              
44             =head1 DESCRIPTION
45              
46             This suite of test subroutines will test if a file system object is internally consistent and can be used to test other features of the object.
47              
48             The following tests are available:
49              
50             =over
51              
52             =item is_root_sane($obj, $name)
53              
54             Checks to make sure the root file object is generally sane. It tests the
55             following:
56              
57             =over
58              
59             =item *
60              
61             Is the root object defined?
62              
63             =item *
64              
65             Does the root subroutine of the root object represent the same path?
66              
67             =item *
68              
69             Is the path '/'?
70              
71             =item *
72              
73             Does stringify work correctly?
74              
75             =item *
76              
77             Does basename return ''?
78              
79             =item *
80              
81             Does dirname return '/'?
82              
83             =item *
84              
85             Does a lookup of '' exist? Does it represent the same path?
86              
87             =item *
88              
89             Does a lookup of '.' exist? Does it represent the same path?
90              
91             =item *
92              
93             Does a lookup of '..' exist? Does it represent the same path?
94              
95             =item *
96              
97             Does a lookup of '/' exist? Does it represent the same path?
98              
99             =item *
100              
101             Does is_root return true?
102              
103             =item *
104              
105             Is parent path the same as root path?
106              
107             =item *
108              
109             Does properties return at least basename, dirname, and path?
110              
111             =item *
112              
113             Does is_container return true?
114              
115             =back
116              
117             =cut
118              
119             sub is_root_sane {
120 6     6 1 53 my $obj = shift;
121 6         13 my $name = shift;
122              
123 6 50       35 _check(defined $obj, $name, 'root object does not exist') || return;
124              
125 6         63 my $root = $obj->root;
126 6 50       81 _check($obj->path eq $root->path, $name,
127             "root object path [$obj] does not match ->root path [$root]") || return;
128              
129 6 50       56 _check($obj->path eq '/', $name,
130             "root object path is [$obj] instead of the desired [/]") || return;
131              
132 6 50       54 _check($obj->path eq "$obj", $name,
133             "root object stringify was [$obj] instead of [",$obj->path,"]") || return;
134              
135 6 50       91 _check($obj->basename eq '/', $name,
136             "root object basename was [",$obj->basename,"] instead of [/]") || return;
137              
138 6 50       78 _check($obj->dirname eq '/', $name,
139             "root object dirname was [",$obj->dirname,"] instead of [/]") || return;
140              
141 6 50       80 _check($obj->is_root, $name, "root object [$obj] is not reporting is_root") || return;
142              
143 6         96 my $parent = $obj->parent;
144 6 50       95 _check($obj->path eq $parent->path, $name,
145             "root object path is [$obj] but parent is [$parent]") || return;
146              
147 6 50       950 _check(grep(/^basename$/, $obj->properties), $name,
148             "root object [$obj] properties missing basename") || return;
149              
150 6 50       70 _check(grep(/^dirname$/, $obj->properties), $name,
151             "root object [$obj] properties missing dirname") || return;
152              
153 6 50       60 _check(grep(/^path$/, $obj->properties), $name,
154             "root object [$obj] properties missing path") || return;
155              
156 6 50       568 _check($obj->is_container, $name, "root object [$obj] is not a container") || return;
157              
158 6         21 for my $path ('', '.', '..', '/') {
159 24 50       186 _check($obj->exists($path), $name, "root [$obj] cannot access file path [$path]") || return;
160              
161 24         200 my $lookup = $root->lookup($path);
162 24 50       195 _check($obj->path eq $lookup->path, $name,
163             "root object path [$obj] does not match ->lookup($path) path [$lookup]") || return;
164             }
165              
166 6         54 $test->ok(1, $name);
167             }
168              
169             =item is_object_sane($obj, $name)
170              
171             This test performs the following:
172              
173             =over
174              
175             =item *
176              
177             Is the object defined?
178              
179             =item *
180              
181             Does stringify work?
182              
183             =item *
184              
185             Does lookup of path result in object for same path?
186              
187             =item *
188              
189             Does basename return the basename of path?
190              
191             =item *
192              
193             Does dirname return the dirname of path?
194              
195             =item *
196              
197             Does is_root return false?
198              
199             =item *
200              
201             Does parent path match dirname?
202              
203             =item *
204              
205             Does properties return at least basename, dirname, and path?
206              
207             =back
208              
209             =cut
210              
211             sub is_object_sane {
212 676     676 1 2833 my $obj = shift;
213 676         1085 my $name = shift;
214              
215 676 50       1969 _check(defined $obj, 'object does not exist') || return;
216              
217 676 50       4349 _check($obj->path eq "$obj", $name,
218             "object stringify was [$obj] instead of [",$obj->path,"]") || return;
219              
220 676         3827 my $lookup = $obj->lookup($obj->path);
221 676 50       4540 _check($obj->path eq $lookup->path, $name,
222             "object lookup($obj) results in [$lookup] instead of expected [$obj]") || return;
223              
224 676 50       5204 _check($obj->basename eq $obj->basename_of_path($obj->path), $name,
225             "object [$obj] basename was [",$obj->basename,"] instead of [",
226             $obj->basename_of_path($obj->path),"]") || return;
227              
228 676 50       4707 _check($obj->dirname eq $obj->dirname_of_path($obj->path), $name,
229             "object [$obj] dirname was [",$obj->dirname,"] instead of [",
230             $obj->dirname_of_path($obj->path),"]") || return;
231              
232 676 50       4293 _check(!$obj->is_root, $name, "object [$obj] is incorrectly reporting is_root") || return;
233              
234 676         4238 my $parent = $obj->parent;
235 676 50       4505 _check($obj->dirname eq $parent->path, $name,
236             "object [$obj] dirname is [",$obj->dirname,"] but parent path is [$parent]") || return;
237              
238 676 50       5984 _check(grep(/^basename$/, $obj->properties), $name,
239             "object [$obj] properties missing basename") || return;
240              
241 676 50       4300 _check(grep(/^dirname$/, $obj->properties), $name,
242             "object [$obj] properties missing dirname") || return;
243              
244 676 50       10796 _check(grep(/^path$/, $obj->properties), $name,
245             "object [$obj] properties missing path") || return;
246            
247 676         4332 $test->ok(1, $name);
248             }
249              
250             =item is_container_sane($obj, $name)
251              
252             Runs additional container specific tests. It tests the following:
253              
254             =over
255              
256             =item *
257              
258             Does is_container return true?
259              
260             =item *
261              
262             Can the container C?
263              
264             =item *
265              
266             Can the container C?
267              
268             =item *
269              
270             Can the container C?
271              
272             =item *
273              
274             Can the container C?
275              
276             =item *
277              
278             If not C, does C return '.' and '..' only? Does C return an empty list?
279              
280             =item *
281              
282             If the container C, does C return '.' and '..' and more? Does C return a non-empty list?
283              
284             =item *
285              
286             If the container C, does each C return an object for the same path as C.
287              
288             =back
289              
290             =cut
291              
292             sub is_container_sane {
293 114     114 1 614 my $obj = shift;
294 114         238 my $name = shift;
295              
296 114 50       614 _check($obj->is_container, $name, "is_container [$obj] does not return true") || return;
297              
298 114 50       1028 _check($obj->can("has_children"), $name,
299             "container [$obj] does not have a 'has_children' method.") || return;
300              
301 114 50       708 _check($obj->can("children_paths"), $name,
302             "container [$obj] does not have a 'children_paths' method.") || return;
303              
304 114 50       682 _check($obj->can('children'), $name,
305             "container [$obj] does not have a 'children' method.") || return;
306              
307 114 50       610 _check($obj->can('child'), $name,
308             "container [$obj] does not have a 'child' method.") || return;
309              
310 114         1638 my @children_paths = $obj->children_paths;
311 114         741 my @children = $obj->children;
312            
313 114 50       1216 _check(grep(/^\.$/, @children_paths), $name,
314             "container [$obj] does not contain a '.' child.") || return;
315              
316 114 50       755 _check(grep(/^\.\.$/, @children_paths), $name,
317             "container [$obj] does not contain a '..' child.") || return;
318              
319 114 100       1151 if ($obj->has_children) {
320 84 50       700 _check(grep(!/^\.\.?$/, @children_paths), $name,
321             "container [$obj] says it has children but children_paths returns none") || return;
322              
323 84 50       355 _check(scalar(@children) > 0, $name,
324             "container [$obj] says it has children but children returns none") || return;
325              
326 84         248 for my $path (@children_paths) {
327 281         1308 my $lookup = $obj->lookup($path);
328 281         1687 my $child = $obj->child($path);
329              
330 281   50     566 my $lookup_path = eval { $lookup->path } || '';
331 281   50     492 my $child_path = eval { $child->path } || '';
332              
333 281 50       1205 _check($lookup_path eq $child_path, $name,
334             "container [$obj] doesn't find the same object via child() [$child_path] as it does for lookup() [$lookup_path] of $path") || return;
335             }
336             } else {
337 30 50       207 _check(!grep(!/^\.\.?$/, @children_paths), $name,
338             "container says it has no children but children_paths returns some") || return;
339              
340 30 50       109 _check(scalar(@children) == 0, $name,
341             "container says it has no children but children returns some") || return;
342             }
343              
344 114         729 $test->ok(1, $name);
345             }
346              
347             =item is_content_sane($obj, $name)
348              
349             Runs additional content specific tests. It tests the following:
350              
351             =over
352              
353             =item *
354              
355             Does is_content return true?
356              
357             =back
358              
359             =cut
360              
361             sub is_content_sane {
362 224     224 1 1303 my $obj = shift;
363 224         409 my $name = shift;
364              
365 224 50       2972 _check($obj->has_content, $name, "has_content [$obj] does not return true") || return;
366              
367 224         1260 $test->ok(1, $name);
368             }
369              
370             =item is_content_writable($obj, $name)
371              
372             Checks to see if the given file object is writable and confirms that writing works as expected.
373              
374             =over
375              
376             =item *
377              
378             Check to see if is_readable and is_writable.
379              
380             =item *
381              
382             Does open("w") work?
383              
384             =item *
385              
386             Can we write to the file handle returned by open("w")?
387              
388             =item *
389              
390             Does the file handle close properly?
391              
392             =item *
393              
394             Is the content of the file the same as written?
395              
396             =item *
397              
398             Check to see if is_appendable. If so, write one more line to the end, close and reopen to check that the file is as expected.
399              
400             =item *
401              
402             Check to see if is_seekable. If so, seek into the middle, overwrite part of the file, close andreopen to check that the file is as expected.
403              
404             =back
405              
406             =cut
407              
408             sub is_content_writable {
409 224     224 1 116938 my $obj = shift;
410 224         446 my $name = shift;
411              
412 224 50       2994 _check($obj->is_readable, $name, "is_readable [$obj] returns false") || return;
413              
414 224 50       2750 _check($obj->is_writable, $name, "is_writable [$obj] returns false") || return;
415              
416 224         3094 my $fh = $obj->open("w");
417 224 50       61493 _check(defined $fh, $name, "open('w') [$obj] returns undef") || return;
418              
419 224         1027 my @expected = (
420             "Hello World\n",
421             "foo\n",
422             "bar\n",
423             "baz\n",
424             "qux\n",
425             );
426              
427 224         528 for my $line (@expected) {
428 1120 50       6928 _check(print($fh $line), $name, "print [$obj] failed on file handle") || return;
429             }
430              
431 224 50       17960 _check(close($fh), $name, "[$obj] failed to close file handle") || return;
432              
433 224         2576 my $content = $obj->content;
434 224 50       1292 _check($content eq join('', @expected), $name,
435             "[$obj] content read from file '$content' doesn't match expected") || return;
436              
437 224         1543 my @content = $obj->content;
438 224         1160 for (my $n = 0; $n < @content; ++$n) {
439 1120 50       4115 _check($content[$n] eq $expected[$n], $name,
440             "[$obj] content read from line $n of file, '$content[$n]', doesn't match expected") || return;
441             }
442              
443 224 50       2359 if ($obj->is_appendable) {
444 224         2205 my $fh = $obj->open("a");
445 224 50       32583 _check(defined $fh, $name, "open('a') [$obj] returns undef") || return;
446              
447 224 50       1717 _check(print($fh "quux\n"), $name, "print [$obj] failed on appendable file handle") || return;
448              
449 224 50       6531 _check(close($fh), $name, "[$obj] failed to close appendable file handle") || return;
450              
451 224         626 push @expected, "quux\n";
452 224         1802 my $content = $obj->content;
453 224 50       1383 _check($content eq join('', @expected), $name,
454             "[$obj] content read from appended file '$content' doesn't match expected") || return;
455             }
456              
457 224 50       2605 if ($obj->is_seekable) {
458 224         1671 my $fh = $obj->open("r+");
459 224 50       32388 _check(defined $fh, $name, "open('w') [$obj] returns undef") || return;
460              
461 224 50       2278 _check(seek($fh, 16, 0), $name, "seek [$obj] returned a failure") || return;
462              
463 224 50       1655 _check(print($fh "huh\n"), $name, "print [$obj] failed on seeked file handle") || return;
464              
465 224 50       5367 _check(close($fh), $name, "[$obj] failed to close seeked file handle") || return;
466              
467 224         889 splice @expected, 2, 1, "huh\n";
468 224         1590 my $content = $obj->content;
469 224 50       1341 _check($content eq join('', @expected), $name,
470             "[$obj] content read from seeked file '$content' doesn't match expected") || return;
471             }
472              
473 224         1558 $test->ok(1, $name);
474             }
475              
476             =item is_container_mobile($obj, $dest, $name)
477              
478             Checks to see if the container C<$obj> can be renamed (to 'renamed_container' and back), moved to the given container C<$dest> (and moved back), and copied to the given container (and the copy removed).
479              
480             Checks to make sure that after each of these operations that the entire subtree is preserved.
481              
482             =cut
483              
484             sub is_container_mobile {
485 31     31 1 246 my $obj = shift;
486 31         63 my $dest = shift;
487 31         60 my $name = shift;
488              
489             # RENAME
490 31         360 my $basename = $obj->basename;
491 31         292 my $path = $obj->path;
492 31         339 my $renamed_path = $obj->normalize_path($obj->dirname.'/renamed_container');
493              
494 31     69   858 my @files = $obj->find(sub { shift->path ne $obj->path });
  69         643  
495 38         358 my @renamed_files =
496 31         174 map { my $p = $_->path; $p =~ s/^$obj/$renamed_path/; $p } @files;
  38         722  
  38         166  
497              
498 31 50       499 _check($obj->rename('renamed_container')->path eq $renamed_path, $name,
499             "renamed container path is '$obj' rather than '$renamed_path'") || return;
500              
501 31         100 for my $path (@files) {
502 38 50       405 _check(!$path->exists, $name,
503             "renamed container '$obj' failed to rename child from '$path'") || return;
504             }
505              
506 31         84 for my $path (@renamed_files) {
507 38 50       344 _check($obj->exists($path), $name,
508             "renamed container '$obj' failed to rename child to '$path'") || return;
509             }
510              
511 31 50       294 _check($obj->rename($basename)->path eq $path, $name,
512             "originally renamed container path is '$obj' rather than '$path'") || return;
513              
514 31         81 for my $path (@files) {
515 38 50       336 _check($path->exists, $name,
516             "originally renamed container '$obj' failed to rename child to '$path'") || return;
517             }
518              
519 31         82 for my $path (@renamed_files) {
520 38 50       364 _check(!$obj->exists($path), $name,
521             "originally renamed container '$obj' failed to rename child from '$path'") || return;
522             }
523              
524             # MOVE
525 31         329 my $parent = $obj->parent;
526 31         308 my $new_path = $obj->normalize_path($dest->path."/$basename");
527              
528 38         360 my @new_files =
529 31         77 map { my $p = $_->path; $p =~ s/^$obj/$new_path/; $p } @files;
  38         267  
  38         165  
530              
531 31 50       347 _check($obj->move($dest, 'force')->path eq $new_path, $name,
532             "moved container path is '$obj' rather than '$new_path'") || return;
533              
534 31         97 for my $path (@files) {
535 38 50       371 _check(!$obj->exists($path), $name,
536             "moved container '$obj' failed to move child from '$path'") || return;
537             }
538            
539 31         83 for my $path (@new_files) {
540 38 50       360 _check($obj->exists($path), $name,
541             "moved container '$obj' failed to move child to '$path'") || return;
542             }
543              
544 31 50       795 _check($obj->move($parent, 'force')->path eq $path, $name,
545             "originally moved container path is '$obj' rather than '$path'") || return;
546              
547 31         103 for my $path (@files) {
548 38 50       370 _check($path->exists, $name,
549             "originally moved container '$obj' failed to move child to '$path'") || return;
550             }
551            
552 31         80 for my $path (@new_files) {
553 38 50       365 _check(!$obj->exists($path), $name,
554             "originally moved container '$obj' failed to move child from '$path'") || return;
555             }
556              
557             # COPY
558 31         353 my $copy = $obj->copy($dest, 'force');
559 31 50       325 _check($copy->path eq $new_path, $name,
560             "copied container path is '$obj' rather than '$new_path'") || return;
561              
562 31         201 for my $path (@files) {
563 38 50       367 _check($obj->exists($path), $name,
564             "original container '$obj' lost child from '$path' after copy") || return;
565             }
566            
567 31         89 for my $path (@new_files) {
568 38 50       364 _check($obj->exists($path), $name,
569             "copied container '$copy' failed to copy child to '$path'") || return;
570             }
571              
572             # REMOVE
573 31         353 $copy->remove('force');
574 31 50       862 _check(!$copy->is_valid, $name,
575             "removed container '$copy' is still valid") || return;
576              
577 31         90 for my $path (@files) {
578 38 50       377 _check($path->exists, $name,
579             "pre-copy container '$obj' lost child to '$path' when copy container '$copy' was removed") || return;
580             }
581            
582 31         97 for my $path (@new_files) {
583 38 50       350 _check(!$obj->exists($path), $name,
584             "removed container '$copy' failed to remove child from '$path'") || return;
585             }
586              
587 31         276 $test->ok(1, $name);
588             }
589              
590             =item is_container_mobile($obj, $dest, $name)
591              
592             Checks to see if the content C<$obj> can be renamed (to 'renamed_content' and back), moved to the given container C<$dest> (and moved back), and copied to the given container (and the copy removed).
593              
594             =cut
595              
596             sub is_content_mobile {
597 217     217 0 1306 my $obj = shift;
598 217         1216 my $dest = shift;
599 217         324 my $name = shift;
600              
601             # RENAME
602 217         1594 my $basename = $obj->basename;
603 217         1233 my $path = $obj->path;
604 217         1458 my $renamed_path = $obj->normalize_path($obj->dirname.'/renamed_content');
605              
606 217 50       1889 _check($obj->rename('renamed_content')->path eq $renamed_path, $name,
607             "renamed content path is '$obj' rather than '$renamed_path'") || return;
608              
609 217 50       1374 _check($obj->rename($basename)->path eq $path, $name,
610             "originally renamed content path is '$obj' rather than '$path'") || return;
611              
612             # MOVE
613 217         1589 my $parent = $obj->parent;
614 217         1327 my $new_path = $obj->normalize_path($dest->path."/$basename");
615              
616 217 50       1600 _check($obj->move($dest)->path eq $new_path, $name,
617             "moved content path is '$obj' rather than '$new_path'") || return;
618              
619 217 50       1337 _check($obj->move($parent)->path eq $path, $name,
620             "originally moved content path is '$obj' rather than '$path'") || return;
621              
622             # COPY
623 217         1783 my $copy = $obj->copy($dest);
624 217 50       1219 _check($copy->path eq $new_path, $name,
625             "copied content path is '$obj' rather than '$new_path'") || return;
626              
627             # REMOVE
628 217         8262 $copy->remove;
629 217 50       2322 _check(!$copy->is_valid, $name,
630             "removed content '$copy' is still valid") || return;
631              
632 217         1591 $test->ok(1, $name);
633             }
634              
635             =item is_glob_and_find_consistent($obj, $name)
636              
637             Checks several different glob patterns on the object to see if the glob patterns find the same set of objects that a similar find operation returns. The object passed can be a root object or any other object in the tree.
638              
639             This method also tests to see that the various different ways of calling C and C are self-consistent. That is,
640              
641             $obj->find(\&test) === $root->find(\&test, $obj)
642             $obj->glob($test) === $root->glob("$obj/$test")
643              
644             =cut
645              
646             sub is_glob_and_find_consistent {
647 128     128 1 1334 my $obj = shift;
648 128         258 my $name = shift;
649              
650             my @tests = (
651 1250 100 100 1250   6540 [ '*{ar,az}', sub { $_[0]->path !~ /\/\.[^\/]+$/
652             && $_[0]->parent eq $obj
653             && $_[0]->path =~ /ar$|az$/ } ],
654 1250 100 100 1250   7056 [ '*', sub { $_[0]->parent eq $obj
655             && $_[0]->path !~ /\/\.[^\/]+$/
656             && $_[0]->path ne $obj->path } ],
657 1250 100   1250   7654 [ '.??*', sub { $_[0]->parent eq $obj
658             && $_[0]->path =~ /\/\.[^\/]+$/ } ],
659 1250 100 100 1250   6657 [ '*/*', sub { $_[0]->path =~ /^$obj\/?[^\/]+\//
      100        
660             && $_[0]->path !~ /^$obj\/?[^\/]+\/[^\/]+\//
661             && $_[0]->path !~ /\/\.[^\/]+$/
662 128         1971 && $_[0]->path !~ /\/\.[^\/]+\/[^\/]+$/ } ],
663             );
664              
665 128         985 my $root = $obj->root;
666 128         375 for my $test (@tests) {
667 512         3630 my @glob = $obj->glob($test->[0]);
668 512         3327 my @find = $obj->find($test->[1]);
669              
670 512         2223 my @root_glob = $root->glob("$obj/$test->[0]");
671 512         4131 my @root_find = $root->find($test->[1], $obj);
672              
673 512         1956 my $glob_err = join ', ', @glob;
674 512         1399 my $find_err = join ', ', @find;
675              
676 512         1526 my $root_glob_err = join ', ', @root_glob;
677 512         1310 my $root_find_err = join ', ', @root_find;
678              
679 512 50       3154 _check(@glob eq @find, $name, "in '$obj' for '$test->[0]', glob returned [ $glob_err ] but find returned [ $find_err ]") || return;
680              
681 512 50       3098 _check(@glob eq @root_glob, $name, "in '$obj' for '$test->[0]', obj glob returned [ $glob_err ] but root glob returned [ $root_glob_err ]") || return;
682              
683 512 50       2864 _check(@find eq @root_find, $name, "in '$obj' for '$test->[0]', obj find returned [ $find_err ] but root find returned [ $root_find_err ]") || return;
684              
685 512         3252 for (my $i = 0; $i < @glob; ++$i) {
686 466 50       7229 _check($glob[$i] eq $find[$i], $name, "in '$obj' element $i of glob was '$glob[$i]', but element $i of find was '$find[$i]'") || return;
687              
688 466 50       1916 _check($glob[$i] eq $root_glob[$i], $name, "in '$obj' element $i of obj glob was '$glob[$i]', but element $i of root glob was '$root_glob[$i]'") || return;
689              
690 466 50       1767 _check($find[$i] eq $root_find[$i], $name, "in '$obj' element $i of obj find was '$find[$i]', but element $i of root find was '$root_find[$i]'") || return;
691             }
692             }
693              
694 128         1091 $test->ok(1, $name);
695             }
696              
697             =head1 SEE ALSO
698              
699             L
700              
701             =head1 AUTHOR
702              
703             Andrew Sterling Hanenkamp, Ehanenkamp@users.sourceforge.netE
704              
705             =head1 COPYRIGHT AND LICENSE
706              
707             Copyright 2005 Andrew Sterling Hanenkamp. All Rights Reserved.
708              
709             This library is licensed and distributed under the same terms as Perl itself.
710              
711             =cut
712              
713             1