File Coverage

blib/lib/List/Filter/Storage.pm
Criterion Covered Total %
statement 154 193 79.7
branch 10 16 62.5
condition 3 5 60.0
subroutine 26 33 78.7
pod 20 20 100.0
total 213 267 79.7


line stmt bran cond sub pod time code
1             package List::Filter::Storage;
2 1     1   294217 use base qw( Class::Base );
  1         2  
  1         898  
3              
4              
5             =head1 NAME
6              
7             List::Filter::Storage - storage handler for filters (e.g. filters)
8              
9             =head1 SYNOPSIS
10              
11             use List::Filter::Storage;
12             $stash_file = "$ENV{HOME}/project_filters.yaml";
13             my $filter_storage = List::Filter::Storage->new({
14             storage => [ $stash_file ],
15             });
16             my $filter = List::Filter->new(
17             { name => 'skip_boring_stuff',
18             terms => ['-\.vb$', '\-.js$'],
19             method => 'skip_boring_stuff',
20             description => "Skip the really boring stuff",
21             modifiers => "xi",
22             } );
23              
24             $filter_storage->save( $filter );
25              
26             # And later, in some other code...
27              
28             my $filter_storage = List::Filter::Storage->new({ storage =>
29             [ $stash_file ] });
30              
31             my $filter = $filter_storage->lookup( 'skip_boring_stuff' );
32              
33              
34             # Filters lookd up from a path of storage locations:
35             # (1) yaml file (2) a DBI database connection
36             my $yaml_file = "/tmp/filter_storage.yaml";
37             my $lfs = List::Filter::Storage->new( {
38             storage=> [
39             $yaml_file,
40             { format => 'DBI',
41             connect_to => $connect_to, # e.g. "dbi:Pg:dbname=$dbname"
42             owner => $owner,
43             password => $password,
44             },
45             ] } );
46              
47              
48             # storage format "MEM" keeps data in memory only
49             my $lfs = List::Filter::Storage->new( {
50             storage=> [
51             [
52             { format => 'MEM',
53             connect_to => {},
54             }
55             ] });
56              
57             # automatically make copies in the yaml file of any filters used from DBI
58             my $filter_storage = List::Filter::Storage->new(
59             { save_filters_when_used => $args->{ save_filters_when_used },
60             storage => [ $yaml_file,
61             { format => 'DBI',
62             connect_to => $connect_to,
63             owner => $owner,
64             password => $password,
65             },
66             ],
67              
68             } );
69              
70             # a storage handler can save objects of type 'transform'
71             # (a child of filter):
72             my $storage_tran = List::Filter::Storage->new(
73             { storage => [ $stash_file ],
74             type => 'transform',
75             } );
76             $storage_tran->save( $transform );
77              
78              
79              
80             =head1 DESCRIPTION
81              
82             List::Filter::Storage is a "storage handler", it deals with
83             multiple locations of different types of pluggable backing stores
84             to save and retrieve "filters" (and variant types of filters such
85             as 'transforms'). See L and L.
86              
87             To review the nature of the items that need to be stored: At the
88             heart of a "filter" is an array reference called 'terms' which
89             contains a list of arbitrary perl data structures. In the case
90             of the simple 'filter" type, this is a list of regular
91             expressions, in the case of 'transform' it's a list of array
92             references, each containing the three parts of a perl
93             substitution (in an unusual order, counting from 1 to 3: s/1/3/2).
94              
95             Also, in addition to this list of 'terms', each filter object
96             also has some attached to it some additional fields of data:
97             'name', 'method', 'modifiers', and 'description'.
98              
99             So this might be thought of an ORM system, except that it's much
100             more specialized (or perhaps "even more braindead") than ORMs
101             usually are. Also, while it can use a database as a backing
102             store (via DBI), the default storage system is simply to dump the
103             data to YAML files, which have the advantage of being relatively
104             easy to read and edit.
105              
106             =head2 METHODS
107              
108             =over
109              
110             =cut
111              
112 1     1   1554 use 5.8.0;
  1         4  
  1         54  
113 1     1   5 use strict;
  1         4  
  1         25  
114 1     1   6 use warnings;
  1         2  
  1         31  
115 1     1   5 use Carp;
  1         1  
  1         81  
116 1     1   8 use Data::Dumper;
  1         2  
  1         58  
117 1     1   1079 use Hash::Util qw( lock_keys unlock_keys );
  1         3413  
  1         8  
118 1     1   985 use List::Filter::Internal;
  1         3  
  1         37  
119 1     1   7 use File::Path qw(mkpath);
  1         2  
  1         48  
120 1     1   6 use File::Basename qw(dirname fileparse);
  1         1  
  1         49  
121 1     1   6 use Env qw(HOME);
  1         2  
  1         5  
122 1     1   165 use YAML qw(DumpFile LoadFile);
  1         1  
  1         63  
123 1     1   1035 use Module::List::Pluggable qw( list_modules_under import_modules );
  1         26748  
  1         2031  
124              
125             our $VERSION = '0.01';
126             my $DEBUG = 0;
127              
128             =item new
129              
130             Instantiates a new List::Filter::Storage object.
131              
132             Takes an optional hashref as an argument, with named fields
133             identical to the names of the object attributes.
134              
135             With no arguments creates a storage handler using the
136             default values: we're assumed to be storing data of type
137             "filter" in a "filters.yaml" file located in the
138             ".list-filter" subdirectory of the users home directory.
139              
140             Optional arguments:
141              
142             =over
143              
144             =item type
145              
146             scalar: the type of the filter to be stored (e.g. 'filter', 'transform')
147             Default: 'filter'
148              
149             =item storage
150              
151             aref: a search path of yaml files or hrefs specifying less
152             commonly used storage formats (DBI, MEM, CODE, etc)
153             Filter look-ups try each one in sequence.
154              
155             If not specified, defaults to a single yaml file in a dot
156             location in the $HOME directory.
157              
158             Valid entries in the storage path are described in more detail
159             below in L
160              
161             =item write_storage
162              
163             By default filters are saved to the first place in the storage path.
164             Setting this field should be done to save to an alternate location.
165              
166             (This may or may not be a location present in the storage path,
167             but it almost always will be, or else you would then be saving
168             things you couldn't see again later. Sometimes though, you might
169             want to do this, e.g. when copying filters from one location to
170             another.)
171              
172             =item save_filters_when_used
173              
174             A flag to indicate that copies of any filters that are used
175             should be saved off to the "write_storage" location.
176              
177             =back
178              
179             =cut
180              
181             # Note: "new" (inherited from Class::Base)
182             # calls the following "init" routine automatically.
183              
184             =item init
185              
186             Initialize object attributes and then lock them down to prevent
187             accidental creation of new ones.
188              
189             Note: there is no leading underscore on name "init", though it's
190             arguably an "internal" routine (i.e. not likely to be of use to
191             client code).
192              
193             =cut
194              
195             sub init {
196 26     26 1 126949 my $self = shift;
197 26         53 my $args = shift;
198 26         48 unlock_keys( %{ $self } );
  26         119  
199              
200             # get names of all modules under List::Filter::Storage
201 26         246 my $storage_plugins = list_modules_under( 'List::Filter::Storage');
202              
203             # create a lookup hash to check for existence of a plugin
204 26         23399 my %lookup;
205 26         49 @lookup{ @{ $storage_plugins } } = (1) x @{ $storage_plugins };
  26         111  
  26         82  
206 26         66 my $storage_plugin_lookup = \%lookup;
207 26         112 $self->set_storage_plugin_lookup( $storage_plugin_lookup ); # later init code needs this
208              
209 26   100     173 my $type = $args->{ type } || 'filter';
210 26         90 $self->set_type( $type ); # later init code needs this
211              
212 26         210 my $lfi = List::Filter::Internal->new();
213             my $storage = $lfi->qualify_storage_from_namespace(
214             $args->{ storage },
215 26         184 $type,
216             );
217              
218 26         111 my $storage_objects = $self->define_storage_objects( $storage );
219              
220             # First entry in storage attribute is the default location to save to
221 26   33     135 my $write_storage = $args->{write_storage} || $storage->[0];
222 26         69 my $write_storage_object = $self->objectify_storage( $write_storage );
223              
224             # define new attributes
225             my $attributes = {
226             type => $type,
227             storage => $storage, # aref
228             write_storage => $write_storage, # scalar
229             save_filters_when_used => $args->{ save_filters_when_used }, # boolean
230              
231             # internal use
232 26         192 write_storage_object => $write_storage_object, # scalar
233             storage_objects => $storage_objects, # aref
234             storage_plugin_lookup => $storage_plugin_lookup, # href
235             };
236              
237             # add attributes to object
238 26         51 my @fields = (keys %{ $attributes });
  26         133  
239 26         65 @{ $self }{ @fields } = @{ $attributes }{ @fields }; # hash slice
  26         134  
  26         131  
240              
241 26         55 lock_keys( %{ $self } );
  26         95  
242 26         360 return $self;
243             }
244              
245             =item lookup
246              
247             Given a filter name, returns a matching filter object, or undef.
248              
249             Interates over the storage path, looking in each until a matching
250             name has been found.
251              
252             If the lookup fails it returns undef, emitting the warning:
253             "Failed lookup of $type with name: $name";
254              
255             =cut
256              
257             sub lookup {
258 13     13 1 4022 my $self = shift;
259 13         31 my $name = shift;
260 13         35 my $storage_objects = $self->{storage_objects};
261              
262             # interate over storage path, look in each until a matching
263             # name has been found.
264              
265 13         21 my $filter;
266 13         26 foreach my $storage_handle ( @{ $storage_objects } ) {
  13         33  
267 14 100       69 if ( $filter = $storage_handle->lookup( $name ) ) {
268 12         31 last;
269             }
270             }
271              
272 13 100       48 unless( $filter ) {
273 1         6 my $type = $self->type;
274 1         210 carp "Failed lookup of $type with name: $name";
275 1         119 return;
276             }
277              
278             # let the filter know where it came from
279 12         61 $filter->set_storage_handler( $self );
280              
281             # pass on the flag setting to the filter
282 12 50       44 if( $self->save_filters_when_used ) {
283 0         0 $filter->set_save_filters_when_used( 1 );
284             }
285              
286 12         62 return $filter;
287             }
288              
289             =item save
290              
291             Save the given filter object.
292              
293             Returns a copy of it.
294              
295             =cut
296              
297             sub save {
298 11     11 1 115 my $self = shift;
299 11         18 my $filter = shift;
300              
301 11         49 my $wso = $self->write_storage_object;
302 11         68 $wso->save( $filter );
303 11         51 return $filter; # more likely to be a useful return than $self
304             }
305              
306             =item define_storage_objects
307              
308             Converts the array of storage locations into an array of
309             storage objects to make "lookups" simpler.
310              
311             =cut
312              
313             sub define_storage_objects {
314 26     26 1 38 my $self = shift;
315 26         47 my $storage = shift;
316              
317 26         50 my @storage_objects = ();
318 26         35 foreach my $stash (@{ $storage }){
  26         69  
319 27         89 my $storage_object = $self->objectify_storage( $stash );
320 27         85 push @storage_objects, $storage_object;
321             }
322 26         67 return \@storage_objects;
323             }
324              
325             =item objectify_storage
326              
327             Convert an entry from the storage path into an object of the
328             appropriate class: List::Filter::Storage::*
329              
330             =cut
331              
332             # Determines the type of storage requested (YAML, DBI, etc.)
333             # and does a "require" of the appropriate plugin, or croaks
334             # if it can't be found.
335             sub objectify_storage {
336 53     53 1 303 my $subname = ( caller(0) )[3];
337 53         100 my $self = shift;
338 53         58 my $stash = shift;
339              
340             # allowed list of plugin formats (a hash ref)
341 53         119 my $storage_plugin_lookup = $self->storage_plugin_lookup;
342              
343 53         86 my ($format, @plugin_name, $plugin_class, $db_type,
344             $connect_to, $owner, $password, $attributes);
345              
346 53 100       117 if (ref $stash eq 'HASH') {
347              
348 35         78 $format = $stash->{format};
349 35         60 $connect_to = $stash->{connect_to};
350 35         61 $owner = $stash->{owner};
351 35         48 $password = $stash->{password};
352 35         46 $attributes = $stash->{attributes};
353              
354 35         80 push @plugin_name, uc( $format );
355              
356 35 50       102 if ($format eq 'DBI') {
357 0         0 require DBI;
358 0         0 $db_type = ( split ":", $connect_to )[1];
359 0         0 my $specific_plugin = "DBI::$db_type";
360 0         0 push @plugin_name, $specific_plugin;
361             }
362              
363             } else { # if not href, $stash is a file name, extension determines format
364 18         20 $connect_to = $stash;
365              
366             # examine the string, looking for a file-extension.
367 18         73 my $ext_pat = qr{ (?<=\.) [^.]{0,5} $ }x;
368             # extension up to 5 chars, sans dot
369 18         519 my $extension = ( fileparse($stash, $ext_pat ) )[2];
370              
371 18         72 push @plugin_name, uc( $extension );
372             }
373              
374             # does a plugin exist? For DBI plugins, use database-specific
375             # one if available.
376              
377             PLUGIN:
378 53         103 foreach my $plugin_name (@plugin_name) {
379 53         90 $plugin_class = "List::Filter::Storage::$plugin_name";
380 53 50       153 if ( $storage_plugin_lookup->{ $plugin_class } ) {
381 53         118 last PLUGIN;
382             }
383             } continue {
384 0         0 my $mess = "No storage plug-in found for format $format";
385 0 0       0 $mess .= " (db type: $db_type)" if $db_type;
386 0         0 croak $mess;
387             }
388             # "last PLUGIN" jumps to here
389              
390 53         3482 eval "require $plugin_class";
391 53 50       238 if ($@) {
392 0         0 die "Could not require $plugin_class: $@\n";
393             }
394              
395 53         142 my $type = $self->type;
396 53         463 my $storage_obj = $plugin_class->new(
397             { connect_to => $connect_to,
398             owner => $owner,
399             password => $password,
400             attributes => $attributes,
401             type => $type, # type of filters to store
402             } );
403              
404 53         7406 return $storage_obj;
405             }
406              
407              
408              
409             =item list_filters
410              
411             Returns a list of all avaliable named filters.
412              
413             =cut
414              
415             sub list_filters {
416 2     2 1 34 my $self = shift;
417              
418 2         4 my %uniq = ();
419 2         8 my $storage_objects = $self->storage_objects;
420 2         4 foreach my $store (@{ $storage_objects}) {
  2         5  
421 2         14 my $add_filters = $store->list_filters;
422 2         5 @uniq{ @{ $add_filters } } = (1) x @{ $add_filters };
  2         26  
  2         5  
423             }
424              
425 2         18 my @filters = keys( %uniq );
426 2         15 return \@filters;
427             }
428              
429              
430              
431             =back
432              
433             =head2 Basic accessors (setters use "set_" prefix, getters have none)
434              
435             =over
436              
437             =item type
438              
439             Getter for object attribute type
440              
441             =cut
442              
443             sub type {
444 54     54 1 82 my $self = shift;
445 54         89 my $type = $self->{ type };
446 54         114 return $type;
447             }
448              
449             =item set_type
450              
451             Setter for object attribute set_type
452              
453             =cut
454              
455             sub set_type {
456 26     26 1 40 my $self = shift;
457 26         59 my $type = shift;
458 26         64 $self->{ type } = $type;
459 26         44 return $type;
460             }
461              
462             =item storage
463              
464             Getter for object attribute storage
465              
466             =cut
467              
468             sub storage {
469 0     0 1 0 my $self = shift;
470 0         0 my $storage = $self->{ storage };
471 0         0 return $storage;
472             }
473              
474             =item set_storage
475              
476             Setter for object attribute set_storage
477              
478             =cut
479              
480             sub set_storage {
481 0     0 1 0 my $self = shift;
482 0         0 my $storage = shift;
483 0         0 $self->{ storage } = $storage;
484              
485             # if this is changed, keep storage_objects in sync
486 0         0 my $storage_objects = $self->define_storage_objects( $storage );
487 0         0 $self->set_storage_objects( $storage_objects );
488              
489 0         0 return $storage;
490             }
491              
492              
493             =item write_storage
494              
495             Getter for object attribute write_storage
496              
497             =cut
498              
499             sub write_storage {
500 0     0 1 0 my $self = shift;
501 0         0 my $write_storage = $self->{ write_storage };
502 0         0 return $write_storage;
503             }
504              
505             =item set_write_storage
506              
507             Setter for object attribute set_write_storage.
508              
509             Note, this also automatically sets write_storage_object.
510              
511             =cut
512              
513             sub set_write_storage {
514 0     0 1 0 my $self = shift;
515 0         0 my $write_storage = shift;
516 0         0 $self->{ write_storage } = $write_storage;
517              
518             # if this is changed, keep the object form of this in sync
519 0         0 my $write_storage_object = $self->objectify_storage( $write_storage );
520 0         0 $self->set_write_storage_object( $write_storage_object );
521              
522 0         0 return $write_storage;
523             }
524              
525              
526              
527             =item save_filters_when_used
528              
529             Getter for object attribute save_filters_when_used
530              
531             =cut
532              
533             sub save_filters_when_used {
534 12     12 1 19 my $self = shift;
535 12         30 my $save_filters_when_used = $self->{ save_filters_when_used };
536 12         34 return $save_filters_when_used;
537             }
538              
539             =item set_save_filters_when_used
540              
541             Setter for object attribute set_save_filters_when_used
542              
543             =cut
544              
545             sub set_save_filters_when_used {
546 0     0 1 0 my $self = shift;
547 0         0 my $save_filters_when_used = shift;
548 0         0 $self->{ save_filters_when_used } = $save_filters_when_used;
549 0         0 return $save_filters_when_used;
550             }
551              
552             =item write_storage_object
553              
554             Getter for object attribute write_storage_object.
555              
556             =cut
557              
558              
559             sub write_storage_object {
560 11     11 1 20 my $self = shift;
561 11         26 my $write_storage_object = $self->{ write_storage_object };
562 11         25 return $write_storage_object;
563             }
564              
565             =item set_write_storage_object
566              
567             Setter for object attribute set_write_storage_object
568              
569             =cut
570              
571             sub set_write_storage_object {
572 0     0 1 0 my $self = shift;
573 0         0 my $write_storage_object = shift;
574 0         0 $self->{ write_storage_object } = $write_storage_object;
575 0         0 return $write_storage_object;
576             }
577              
578             =item storage_plugin_lookup
579              
580             Getter for object attribute storage_plugin_lookup
581              
582             =cut
583              
584             sub storage_plugin_lookup {
585 53     53 1 86 my $self = shift;
586 53         84 my $storage_plugin_lookup = $self->{ storage_plugin_lookup };
587              
588 53         91 return $storage_plugin_lookup;
589             }
590              
591             =item set_storage_plugin_lookup
592              
593             Setter for object attribute set_storage_plugin_lookup
594              
595             =cut
596              
597             sub set_storage_plugin_lookup {
598 26     26 1 50 my $self = shift;
599 26         42 my $storage_plugin_lookup = shift;
600 26         70 $self->{ storage_plugin_lookup } = $storage_plugin_lookup;
601 26         58 return $storage_plugin_lookup;
602             }
603              
604             =item storage_objects
605              
606             Getter for object attribute storage_objects
607              
608             =cut
609              
610             sub storage_objects {
611 2     2 1 4 my $self = shift;
612 2         3 my $storage_objects = $self->{ storage_objects };
613 2         5 return $storage_objects;
614             }
615              
616             =item set_storage_objects
617              
618             Setter for object attribute set_storage_objects
619              
620             =cut
621              
622             sub set_storage_objects {
623 0     0 1   my $self = shift;
624 0           my $storage_objects = shift;
625 0           $self->{ storage_objects } = $storage_objects;
626 0           return $storage_objects;
627             }
628              
629             1;
630              
631             =back
632              
633             =head1 MOTIVATION
634              
635             L was designed to answer an immediate need
636             of the L project: to provide a way to share
637             "filters" among different applications. This in itself is
638             requires only a very light-duty storage apparatus (dumping to
639             L files will most likely be more than adequate).
640              
641             But in order to hold open the door that List::Filter might
642             someday need to scale larger than it's current intended purposes
643             (writing command line utilities, and so on), it seemed logical to
644             allow the use of storage facilities that allow concurrent, shared
645             access, and this could be done most obviously via L.
646              
647             (However: this project makes no use whatsover of the "relational"
648             aspects of an RDBMS, and I offer my sincere apologies if this
649             seems to be part of a disturbing trend -- remember, providing for
650             DBI storage I already seems like over-kill for this
651             project. And qualifying perl regular expressions with a
652             relational database is a bit beyond the current scope.)
653              
654             So, having gone that far, I took the project one step further,
655             and provided a plug-in extension mechanism so that other storage
656             formats could be defined in the future. Possibilities include:
657             XML, TSV, CSV, INI... and so on.
658              
659              
660             =head2 the storage search path
661              
662             The "storage" search path hopefully seems like a straight-forward
663             concept (search paths are common enough: the PATH environment
664             variable, the perl @INC array, etc.). However, the syntax of the
665             "storage" attribute may seem a little peculiar: the general idea
666             is to make it indefinitely extensible without making the most
667             likely immediate uses excessively complex.
668              
669             The most common thing (or so I imagine) is to just use a single
670             yaml file in your home directory, so that's the default if no
671             "storage" is specified:
672              
673             ~/.list-filter/filters.yaml
674              
675             There might be some need for more than one of these files (say, a
676             private one and a shared, or perhaps multiple ones for different
677             projects), so that's the next most simple case: an array reference
678             of multiple file paths.
679              
680             If a file path is specified, the file extension is used to
681             determine the format. Currently YAML is the only one supported,
682             but plugins can be added for other formats (csv, etc.).
683              
684             In order to specify something like a DBI database connection, we
685             need a more complicated data-structure, so rather than simple
686             scalars, a hash reference can appear in the storage path. With a
687             hash ref, the format of the storage is spelled out explicitly,
688             along with various other connection parameters that might be
689             needed. This is discussed further in the following sections.
690              
691             =head2 storage location hash references
692              
693             The "storage" object attribute is designed to be very simple to
694             use in the most common cases (see the discussion above).
695              
696             The next step up in complexity beyond a single yaml file, is to
697             use an array reference of storage locations that are searched in
698             given sequence: a scalar element of this array is presumed to be
699             a path to a data file. If the element is a hash reference, it
700             can be used to specify something else, most commonly some sort of
701             database accessed via DBI.
702              
703             An example of intermixing DBI storage with a yaml file:
704              
705             # Filters lookd up from a path of storage locations:
706             # (1) yaml file (2) a DBI database connection
707             my $yaml_file = "/tmp/filter_storage.yaml";
708             my $lfs = List::Filter::Storage->new( {
709             storage=> [
710             $yaml_file,
711             { format => 'DBI',
712             connect_to => $connect_to,
713             owner => $owner,
714             password => $password,
715             },
716             ] } );
717              
718             Note the fields in the hash reference in the second position in
719             the storage array: The first is "format" (which obviously, is
720             defined as 'DBI' in the case of DBI access), and the remaining
721             three fields will no doubt seem familiar from L.
722              
723             In the case of the 'DBI' format, these three parameters are
724             passed through to the DBI module to create a database handle used
725             internally. For example, postgresql, the "connect_to" would be:
726             "dbi:Pg:dbname=$dbname".
727              
728             These fields can have a different meaning for different storage
729             formats, e.g. in the case of the 'MEM' (in-memory) format, the
730             "connect_to" parameter takes an href of hrefs containing
731             filter data in a format similar to what you see inside the yaml files.
732             See L.
733              
734             =head2 Storage formats
735              
736             Storage formats (e.g. 'DBI', 'YAML', 'MEM', etc.) are defined
737             using a plug-in system, so that new types may be defined at a
738             later date, all of them are named with the form
739             List::Filter::Storage::.
740              
741             For the special case of DBI handles, it is possible to define a
742             database specific format handler that will over-ride the generic
743             L. These should be named following
744             the convention List::Filter::Storage::DBI::, where
745             should match an existing DBD:: driver (e.g. "Pg" for the
746             postgresql database: L).
747              
748             As mentioned above, in the case of the 'MEM' format, the
749             "connect_to" is used to point to a data structure. Other formats
750             are free to use these connection parameters as they like. For
751             example, a 'LWP' format might be written some day where the
752             "connect_to" is a URL.
753              
754             At present, documentation on how to write the code to handle a new
755             storage format is very limited. It's suggested that you use the
756             existing format definition modules as examples:
757              
758             L
759             L
760             L
761             L
762              
763             =head1 SEE ALSO
764              
765             L
766             L
767              
768             =head1 AUTHOR
769              
770             Joseph Brenner, Edoom@kzsu.stanford.eduE,
771             18 May 2007
772              
773             =head1 COPYRIGHT AND LICENSE
774              
775             Copyright (C) 2007 by Joseph Brenner
776              
777             This library is free software; you can redistribute it and/or modify
778             it under the same terms as Perl itself, either Perl version 5.8.2 or,
779             at your option, any later version of Perl 5 you may have available.
780              
781             =head1 BUGS
782              
783             None reported... yet.
784              
785             =cut