File Coverage

blib/lib/Mail/Log/Trace.pm
Criterion Covered Total %
statement 216 223 96.8
branch 35 42 83.3
condition n/a
subroutine 51 51 100.0
pod 8 8 100.0
total 310 324 95.6


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3              
4             package Mail::Log::Trace;
5             {
6             =head1 NAME
7              
8             Mail::Log::Trace - Trace an email through the mailsystem logs.
9              
10             =head1 SYNOPSIS
11              
12             use Mail::Log::Trace;
13            
14             my $tracer = Mail::Log::Trace::SUBCLASS->new({log_file => 'path/to/log'});
15             $tracer->set_message_id('message_id');
16             $tracer->find_message();
17             my $from_address = $tracer->get_from_address();
18            
19             etc.
20              
21             =head1 DESCRIPTION
22              
23             This is the root-level class for a mail tracer: It allows you to search for
24             and find messages in maillogs. Accessors are provided for info common to
25             most maillogs: Specific subclasses may have further accessors depending on their
26             situation.
27              
28             Probably the two methods most commonly used (and sort of the point of this
29             module) are C and C. Both are simply stubs
30             for subclasses to implement: The first is defined to find the first (or first
31             from current location...) mention of the specified message in the log.
32             Depending on the log format that may or may not be the only mention, and there
33             may be information missing/incomplete at that point.
34              
35             C should find I information about a specific message
36             in the log. (Well, all information about a specific instance of the message:
37             If there are multiple messages that would match the info provided it must
38             find info on the first found.) That may mean searching through the log for
39             other information.
40              
41             If you just need to find if the message exists, use C: it will
42             be faster (or at the least, the same speed. It should never be slower.)
43              
44             =head1 USAGE
45              
46             This is a an object-orientend module, with specific methods documented below.
47              
48             The string coersion is overloaded to return the class name, and the file
49             we are working with. Boolean currently checks to see if we were able to
50             open the file. (Which is kinda silly, as we'd throw an error if we couldn't.)
51              
52             All times are expected to be in Unix epoc-time format.
53              
54             =cut
55              
56 4     4   95655 use strict;
  4         4  
  4         91  
57 4     4   12 use warnings;
  4         4  
  4         85  
58 4     4   11 use Scalar::Util qw(refaddr blessed reftype);
  4         6  
  4         169  
59 4     4   705 use Mail::Log::Exceptions 1.0100;
  4         14437  
  4         82  
60 4     4   19 use base qw(Exporter);
  4         5  
  4         191  
61              
62             BEGIN {
63 4     4   12 use Exporter ();
  4         4  
  4         63  
64 4     4   10 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  4         4  
  4         244  
65 4     4   7 $VERSION = '1.0100_1';
66             #Give a hoot don't pollute, do not export more than needed by default
67 4         8 @EXPORT = qw();
68 4         4 @EXPORT_OK = qw();
69 4         793 %EXPORT_TAGS = ();
70             }
71              
72             #
73             # Define class variables. Note that they are hashes...
74             #
75              
76             my %message_info;
77             my %log_info;
78             my %message_raw_info;
79              
80             # Accessors.
81             my %public = ( from_address => undef,
82             message_id => undef,
83             recieved_time => undef,
84             sent_time => undef,
85             relay => undef,
86             subject => undef,
87             );
88             my %public_set_only = ();
89             my %public_get_only = ( connect_time => undef, disconnect_time => undef, delay => undef );
90             my %array_accessors = ( to_address => undef );
91             my @valid_parameters;
92             my @checked_parameters = qw(from_address message_id recieved_time sent_time relay
93             subject to_address);
94             my %all_setters;
95             my %all_getters;
96              
97             my @cleared_parameters = qw(from_address message_id recieved_time sent_time
98             relay to_address subject connect_time disconnect_time
99             delay);
100              
101             #
102             # DESTROY class variables.
103             #
104             ### IF NOT DONE THERE IS A MEMORY LEAK. ###
105              
106             sub DESTROY {
107 36     36   136368 my ($self) = @_;
108            
109 36         155 delete $message_info{$$self};
110 36         111 delete $log_info{$$self};
111 36         4412 delete $message_raw_info{$$self};
112 36         356 delete $all_setters{$$self};
113 36         521 delete $all_getters{$$self};
114            
115 36         198 return;
116             }
117              
118             #
119             # Set the coercions to something useful.
120             #
121              
122             use overload (
123             # Strings overload to the path and line number.
124 2     2   4 qw{""} => sub { my ($self) = @_;
125             return blessed($self)
126             .' File: '
127 2         21 .$log_info{$$self}{'filename'};
128             },
129            
130             # Boolean overloads to if we are usable. (Have a filehandle.)
131 2     2   4 qw{bool} => sub { my ($self) = @_;
132 2         9 return defined($log_info{$$self}{'log_parser'});
133             },
134            
135             # Numeric context just doesn't mean anything. Throw an error.
136 2     2   93 q{0+} => sub { Mail::Log::Exceptions->throw(q{Can't get a numeric value of a Mail::Log::Trace.} );
137             },
138            
139             # Perl standard for everything else.
140 4         43 fallback => 1,
141 4     4   15 );
  4         5  
142              
143              
144             =head2 new (constructor)
145              
146             The base constructor for the Mail::Log::Trace classes. It takes inital values
147             for the following in a hash: C, C, C,
148             C. The only required value is the path to the logfile.
149              
150             use Mail::Log::Trace;
151             my $object = Mail::Log::Trace->new({ from_address => 'from@example.com',
152             to_address => 'to@example.com',
153             message_id => 'messg.id.string',
154             log_file => 'path/to/log',
155             });
156              
157             =cut
158              
159             sub new
160             {
161 36     36 1 12970 my ($class, $parameters_ref) = @_;
162              
163 36         43 my $self = bless \do{my $anon}, $class;
  36         73  
164 36         379 $$self = refaddr $self;
165              
166             # Build accessors
167            
168             # Get stuff from the any base classes.
169 36         97 my @public = $self->_requested_public_accessors();
170 36         81 my %public_special = $self->_requested_special_accessors();
171 36         105 my @public_set_only = $self->_requested_public_set_only();
172 36         86 my @public_get_only = $self->_requested_public_get_only();
173 36         70 my @array = $self->_requested_array_accessors();
174            
175 36         72 @checked_parameters = ($self->_set_as_message_info(), @checked_parameters);
176 36 50       61 my %checked_parameters = map { $_ => undef if $_ ne ''; } @checked_parameters;
  378         768  
177 36         137 @checked_parameters = keys %checked_parameters;
178              
179 36         67 foreach my $item ( @public ) {
180 69         83 $public{$item} = undef;
181             }
182 36         48 foreach my $item ( @public_set_only ) {
183 0         0 $public_set_only{$item} = undef;
184             }
185 36         39 foreach my $item ( @public_get_only ) {
186 0         0 $public_get_only{$item} = undef;
187             }
188 36         44 foreach my $item ( @array ) {
189 0         0 $array_accessors{$item} = undef;
190             }
191            
192             # Setters first.
193 36         124 my %merged_hash = (%public, %public_set_only, %public_special);
194 36         106 while ( my ($accessor, $action) = each %merged_hash ) {
195 308         446 $all_setters{$$self}{$accessor} = $self->_build_setter($accessor, 0, $action);
196 308         751 push @valid_parameters, $accessor;
197             }
198            
199             # Now getters.
200 36         106 foreach my $accessor ( keys %public, keys %public_get_only, keys %public_special ) {
201 416         539 $all_getters{$$self}{$accessor} = $self->_build_getter($accessor);
202             }
203              
204             # Now build the private.
205 36         104 $all_setters{$$self}{$_} = $self->_build_setter($_, 1) foreach ( keys %public_get_only );
206 36         56 $all_getters{$$self}{$_} = $self->_build_getter($_, 1) foreach ( keys %public_set_only );
207              
208             # And the complex...
209 36         108 $self->_build_array_accessors($_) foreach ( keys %array_accessors );
210 36         57 push @valid_parameters, keys %array_accessors;
211              
212             # Get the list of parameters to clear when 'clear' is called.
213 36         117 my @requested_cleared = $self->_requested_cleared_parameters();
214 36         56 @requested_cleared = grep { defined($_) } @requested_cleared;
  69         95  
215 36         47 push @cleared_parameters, @requested_cleared;
216              
217             # Set up any/all passed parameters.
218             # (Only does message info. Note this can only be called after the above!)
219 36         78 $self->_parse_args($parameters_ref, 0);
220              
221             # Log info.
222 35         126 $self->set_log($parameters_ref->{'log_file'}); # Better to keep validation together.
223              
224 32         143 return $self;
225             }
226              
227             #
228             # The method factories.
229             #
230              
231             sub _build_setter {
232 416     416   353 my ($self, $attribute, $private, $action) = @_;
233            
234             # Build the correct name.
235 416         401 my $sub_name = "set_$attribute";
236 416 100       558 $sub_name = "_$sub_name" if $private;
237            
238             # The typeglob below sets off all kinds of warnings.
239             # (The 'redefine' is because this happens for _every_object_.)
240 4     4   1670 no strict 'refs';
  4         5  
  4         108  
241 4     4   13 no warnings qw(redefine);
  4         6  
  4         151  
242            
243             # Build the actual subroutine.
244 416 100       447 if ( defined($action) ) {
245             # If we do processing or validation, give it a chance to happen.
246             return *$sub_name = sub {
247 4     4   12 use strict 'refs';
  4         5  
  4         681  
248 5     5   6 my ($self, $new_id) = @_;
249            
250             # True if they accept the value, false otherwise.
251             # (To make validation easier.)
252 5         9 $new_id = $action->($self, $new_id);
253 5 50       8 if ( $new_id ne '____INVALID__VALUE____' ) {
254 5         7 $message_info{$$self}{$attribute} = $new_id;
255             }
256             else {
257             # If they don't accept the value, tell the user.
258 0         0 Mail::Log::Exceptions::InvalidParameter->throw("'$new_id' is not a valid value for $attribute.\n");
259             }
260 5         6 return;
261             }
262 23         304 }
263             else {
264             # For basic setters, use a speed-optimized version.
265             return *$sub_name = sub {
266 667     667   418 $message_info{${$_[0]}}{$attribute} = $_[1];
  667         598  
267 667         592 return;
268             }
269 393         4350 }
270             }
271              
272             sub _build_getter {
273 416     416   328 my ($self, $attribute, $private) = @_;
274              
275             # Build the correct name.
276 416         392 my $sub_name = "get_$attribute";
277 416 50       477 $sub_name = "_$sub_name" if $private;
278              
279             # The typeglob below sets off all kinds of warnings.
280             # (The 'redefine' is because this happens for _every_object_.)
281 4     4   15 no strict 'refs';
  4         3  
  4         87  
282 4     4   12 no warnings qw(redefine);
  4         7  
  4         467  
283              
284             # Build the actual subroutine. (As fast as we can make it.)
285             return *$sub_name = sub {
286 1600     1600   7017 return $message_info{${$_[0]}}{$attribute};
  1600         3596  
287             }
288 416         4082 }
289              
290             sub _build_array_accessors {
291 36     36   44 my ($self, $attribute, $private) = @_;
292              
293 36         57 my $get_name = "get_$attribute";
294 36         44 my $set_name = "set_$attribute";
295 36         54 my $add_name = "add_$attribute";
296 36         37 my $remove_name = "remove_$attribute";
297              
298 36 50       53 if ( $private ) {
299 0         0 foreach my $name ( ($get_name, $set_name, $add_name, $remove_name) ) {
300 0         0 $name = "_$name";
301             }
302             }
303              
304 4     4   13 no strict 'refs';
  4         4  
  4         86  
305 4     4   12 no warnings qw(redefine);
  4         1  
  4         469  
306              
307             *$get_name = sub {
308 121     121   4460 return $message_info{${$_[0]}}{$attribute};
  121         358  
309 36         115 };
310 36         316 $all_getters{$$self}{$attribute} = *$get_name;
311              
312             # Note that strict refs still aren't in effect.
313             # Needed for the call to $add_name below.
314             *$set_name = sub {
315 89     89   69 my ($self, $new_id) = @_;
316 89 100       92 if (defined($new_id) ) {
317 86         61 @{$message_info{$$self}{$attribute}} = ();
  86         95  
318 86         108 $add_name->($self, $new_id);
319             }
320             else {
321 3         6 $message_info{$$self}->{$attribute} = undef;
322             }
323 89         81 return;
324 36         203 };
325 36         313 $all_setters{$$self}{$attribute} = *$set_name;
326              
327             *$add_name = sub {
328 4     4   14 use strict 'refs';
  4         3  
  4         3147  
329 176     176   142 my ($self, $new_id) = @_;
330            
331             # If we are given a single element, and we haven't seen it before,
332             # add it to the array.
333 176 100       279 if ( !defined(reftype($new_id)) ) {
    100          
334 163 100       107 unless ( grep { $_ eq $new_id } @{$message_info{$$self}{$attribute}} ) {
  1342         1095  
  163         242  
335 155         94 push @{$message_info{$$self}{$attribute}}, ($new_id);
  155         179  
336             }
337             }
338             # If we are given an array of elements, merge it with our current array.
339             elsif ( reftype($new_id) eq 'ARRAY' ) {
340 12         7 my %temp_hash;
341 12         8 foreach my $element (@{$message_info{$$self}{$attribute}}, @{$new_id}) {
  12         11  
  12         9  
342 26         30 $temp_hash{$element} = undef;
343             }
344 12         15 @{$message_info{$$self}{$attribute}} = keys %temp_hash;
  12         17  
345             }
346 176         230 return;
347 36         241 };
348              
349             *$remove_name = sub {
350 1     1   1 my ($self, $id) = @_;
351 1         2 @{$message_info{$$self}{$attribute}}
352 1         1 = grep { $_ ne $id } @{$message_info{$$self}{$attribute}};
  3         4  
  1         2  
353 1         2 return;
354 36         230 };
355             }
356              
357             #
358             # Setters.
359             #
360              
361             =head2 SETTERS
362              
363             =head3 set_from_address
364              
365             Sets the from address of the message we are looking for.
366              
367             =head3 set_message_id
368              
369             Sets the message_id of the message we are looking for.
370             (Check with the specific parser class for what that means in a particular
371             log format.)
372              
373             =head3 set_recieved_time
374              
375             Sets the recieved time of the message we are looking for.
376             (The time this machine got the message.)
377              
378             =head3 set_sent_time
379              
380             Sets the sent time of the message we are looking for.
381             (The time this machine sent the message.)
382              
383             =head3 set_relay_host
384              
385             Sets the relay host of the message we are looking for. Commonly either
386             the relay we recieved it from, or the relay we sent it to. (Depending
387             on the logfile.)
388              
389             =head3 set_subject
390              
391             Sets the subject of the message we are looking for.
392              
393             =head3 set_parser_class
394              
395             Sets the parser class to use when searching the log file. A subclass will
396             have a 'default' parser that it will normally use: This is to allow easy
397             site-specific logfile formats based on more common formats. To use you
398             would subclass the default parser for the log file format of the base program
399             to handle the site's specific changes.
400              
401             Takes the name of a class as a string, and will throw an exception
402             (C) if that class name doesn't start
403             with Mail::Log::Parse.
404              
405             =cut
406              
407             sub set_parser_class {
408 5     5 1 8 my ($self, $new_id) = @_;
409 5 100       18 if ( $new_id =~ /Mail::Log::Parse::/ ) {
410 4         7 $log_info{$$self}{parser_class} = $new_id;
411             }
412             else {
413 1         5 Mail::Log::Exceptions::InvalidParameter->throw('Parser class needs to be a Mail::Log::Parse:: subclass.');
414             }
415 4         4 return;
416             }
417              
418             =head3 set_log
419              
420             Sets the log file we are searching throuh. Takes a full or relative path.
421             If it doesn't exist, or can't be read by the current user, it will throw an
422             exception. (C) Note that it does I
423             try to open it immedeately. That will be done at first attempt to read from
424             the logfile.
425              
426             =cut
427              
428             sub set_log {
429 37     37 1 47 my ($self, $new_name) = @_;
430              
431 37 100       74 if ( ! defined($new_name) ) {
432 1         15 Mail::Log::Exceptions::InvalidParameter->throw('No log file specified in call to '.blessed($self).'->new().');
433             }
434              
435             # Check to make sure the file exists,
436             # and then that we can read it, before accpeting the filename.
437 36 100       531 if ( -e $new_name ) {
438 34 50       201 if ( -r $new_name ) {
439 34         125 $log_info{refaddr $self}{'filename'} = $new_name;
440             }
441             else {
442 0         0 Mail::Log::Exceptions::LogFile->throw("Log file $new_name is not readable.");
443             }
444             }
445             else {
446 2         18 Mail::Log::Exceptions::LogFile->throw("Log file $new_name does not exist.");
447             }
448              
449             # Reset the parser.
450 34         86 $self->_set_log_parser(undef);
451              
452 34         26 return;
453             }
454              
455             =head3 set_to_address
456              
457             Sets the to address of the message we are looking for. Multiple addresses can
458             be specified, they will all be added, with duplicates skipped. This method
459             completely clears the array: there will be no addresses in the list except
460             those given to it. Duplicates will be consolidated: Only one of any particular
461             address will be in the final array.
462              
463             As a special case, passing C to this will set the array to undef.
464              
465             =head3 add_to_address
466              
467             Adds to the list of to addresses we are looking for. It does I delete the
468             array first.
469              
470             Duplicates will be consolidated, so that the array will only have one of any
471             given address. (No matter the order they are given in.)
472              
473             =head3 remove_to_address
474              
475             Removes a single to address from the array.
476              
477             =cut
478              
479             #
480             # Getters.
481             #
482              
483             =head2 GETTERS
484              
485             =head3 get_from_address
486              
487             Gets the from address. (Either as set using the setter, or as found in the
488             log.)
489              
490             =head3 get_to_address
491              
492             Gets the to address array. (Either as set using the setters, or as found in the
493             log.)
494              
495             Will return a reference to an array, or 'undef' if the to address has not been
496             set/found.
497              
498             =head3 get_message_id
499              
500             Gets the message_id. (Either as set using the setter, or as found in the
501             log.)
502              
503             =head3 get_subject
504              
505             Gets the message subject. (Either as set using the setter, or as found in the
506             log.)
507              
508             =head3 get_recieved_time
509              
510             Gets the recieved time. (Either as set using the setter, or as found in the
511             log.)
512              
513             =head3 get_sent_time
514              
515             Gets the sent time. (Either as set using the setter, or as found in the
516             log.)
517              
518             =head3 get_relay_host
519              
520             Gets the relay host. (Either as set using the setter, or as found in the
521             log.)
522              
523             =head3 get_log
524              
525             Returns the path to the logfile we are reading.
526              
527             =cut
528              
529             sub get_log {
530 27     27 1 45 my ($self) = @_;
531 27         156 return $log_info{$$self}{'filename'};
532             }
533              
534             =head3 get_connect_time
535              
536             Returns the time the remote host connected to this host to send the message.
537              
538             =head3 get_disconnect_time
539              
540             Returns the time the remote host disconnected from this host after sending
541             the message.
542              
543             =head3 get_delay
544              
545             Returns the total delay in this stage in processing the message.
546              
547             =head3 get_all_info
548              
549             Returns message info as returned from the parser, for more direct/complete
550             access.
551              
552             (It's probably a good idea to avoid using this, but it is useful and arguably
553             needed under certain circumstances.)
554              
555             =cut
556              
557             sub get_all_info {
558 126     126 1 95 my ($self) = @_;
559 126         183 return $message_raw_info{$$self};
560             }
561              
562             #
563             # To be implemented by the sub-classes.
564             #
565              
566             =head2 Utility subroutines
567              
568             =head3 clear_message_info
569              
570             Clears I known information on the current message, but not on the log.
571              
572             Use to start searching for a new message.
573              
574             =cut
575              
576             sub clear_message_info {
577 3     3 1 10120 my ($self) = @_;
578              
579 3         7 foreach my $parameter ( @cleared_parameters ) {
580 66 50       148 $all_setters{$$self}{$parameter}->($self, undef) if defined($all_setters{$$self}{$parameter});
581             }
582              
583 3         8 $self->_set_message_raw_info(undef);
584              
585 3         3 return;
586             }
587              
588             =head3 find_message
589              
590             Finds the first/next occurance of a message in the log. Can be passed any
591             of the above information in a hash format.
592              
593             Default is to search I in the log: If you have already done a search,
594             this will start searching where the previous search ended. To start over
595             at the beginning of the logfile, set C as true in the parameter
596             hash.
597              
598             This method needs to be overridden by the subclass: by default it will throw
599             an C error.
600              
601             =cut
602              
603             sub find_message {
604 1     1 1 16 Mail::Log::Exceptions::Unimplemented->throw("Method 'find_message' needs to be implemented by subclass.\n");
605             # return 0; # Return false: The message couldn't be found. This will never be called.
606             }
607              
608             =head3 find_message_info
609              
610             Finds as much information as possible about a specific occurance of a message
611             in the logfile. Acts much the same as find_message, other than the fact that
612             once it finds a message it will do any searching necarry to find all information
613             on that message connection.
614              
615             (Also needs to be implemented by subclasses.)
616              
617             =cut
618              
619             sub find_message_info {
620 1     1 1 1082 Mail::Log::Exceptions::Unimplemented->throw("Method 'find_message_info' needs to be implemented by subclass.\n");
621             # return 0; # Return false: The message couldn't be found. This will never be called.
622             }
623              
624             #
625             # Private functions/methods.
626             #
627              
628             sub _set_message_raw_info {
629 137     137   120 my ($self, $new_hash) = @_;
630 137         140 $message_raw_info{$$self} = $new_hash;
631 137         159 return;
632             }
633              
634             sub _set_log_parser {
635 52     52   58 my ($self, $log_parser) = @_;
636 52         78 $log_info{$$self}->{log_parser} = $log_parser;
637 52         61 return;
638             }
639              
640             sub _get_log_parser {
641 34     34   44 my ($self) = @_;
642 34         65 return $log_info{$$self}->{log_parser};
643             }
644              
645             sub _get_parser_class {
646 24     24   23 my ($self) = @_;
647 24         57 return $log_info{$$self}->{parser_class};
648             }
649              
650             #
651             # Private to be implemented by the sub-classes...
652             # (If needed.)
653             #
654              
655 13     13   16 sub _requested_public_accessors { return (); };
656 36     36   58 sub _requested_public_set_only { return (); };
657 36     36   40 sub _requested_public_get_only { return (); };
658 36     36   38 sub _requested_array_accessors { return (); };
659 13     13   20 sub _requested_cleared_parameters { return (); };
660 13     13   16 sub _requested_special_accessors { return (); };
661 13     13   35 sub _set_as_message_info { return (); };
662              
663             sub _parse_args {
664 69     69   74 my ($self, $argref, $throw_error) = @_;
665            
666             # It is possible for them to pass the message info here.
667 69         76 my %args;
668 69         86 foreach my $parameter ( @valid_parameters ) {
669 3997 100       4791 $all_setters{$$self}{$parameter}->($self, $argref->{$parameter}) if exists $argref->{$parameter};
670             }
671            
672             # Not all parameters are checked...
673 69         73 foreach my $parameter ( @checked_parameters ) {
674 642 50       1396 $args{$parameter} = $all_getters{$$self}{$parameter}->($self) if defined($all_setters{$$self}{$parameter});
675             }
676 69 100       136 $args{from_start} = $argref->{from_start} ? 1 : 0;
677            
678             # And log info.
679 69 100       111 $self->set_parser_class($argref->{parser_class}) if exists $argref->{parser_class};
680            
681             # Speed things up a bit, and make it easier to read.
682            
683 68 100       101 if ($throw_error) {
684             # If none are defined...
685 30 100       83 if ( (grep { defined($args{$_}) } keys %args) == 1 ) {
  330         326  
686 1         7 Mail::Log::Exceptions::Message->throw("Warning: Trying to search for a message with no message-specific data.\n");
687             }
688             }
689              
690 67         118 return \%args;
691             }
692              
693             =head1 BUGS
694              
695             None known at the moment...
696              
697             =head1 REQUIRES
698              
699             L, L.
700              
701             Some subclass, and probably a L class to be useful.
702              
703             =head1 HISTORY
704              
705             1.00.03 Dec 5, 2208 - Licence clarification.
706              
707             1.00.02 Dec 2, 2008 - I really mean it this time.
708              
709             1.00.01 Dec 1, 2008 - Requirements fix, no code changes.
710              
711             1.00.00 Nov 28, 2008
712             - original version.
713              
714             =head1 AUTHOR
715              
716             Daniel T. Staal
717             CPAN ID: DSTAAL
718             dstaal@usa.net
719              
720             =head1 COPYRIGHT
721              
722             This program is free software; you can redistribute
723             it and/or modify it under the same terms as Perl itself.
724              
725             This copyright will expire in 30 years, or five years after the author's death,
726             whichever occurs last, at which time the code be released to the public domain.
727              
728             =cut
729              
730             #################### main pod documentation end ###################
731              
732             }
733             1;
734             # The preceding line will help the module return a true value
735