File Coverage

lib/Pod/PseudoPod.pm
Criterion Covered Total %
statement 180 208 86.5
branch 102 128 79.6
condition 44 65 67.6
subroutine 16 16 100.0
pod 1 2 50.0
total 343 419 81.8


line stmt bran cond sub pod time code
1              
2             package Pod::PseudoPod;
3 10     10   66109 use Pod::Simple;
  10         437819  
  10         538  
4             @ISA = qw(Pod::Simple);
5 10     10   123 use strict;
  10         23  
  10         371  
6              
7 10         1359 use vars qw(
8             $VERSION @ISA
9             @Known_formatting_codes @Known_directives
10             %Known_formatting_codes %Known_directives
11 10     10   66 );
  10         19  
12              
13             @ISA = ('Pod::Simple');
14             $VERSION = '0.18';
15              
16 10 50   10   34177 BEGIN { *DEBUG = sub () {0} unless defined &DEBUG }
17              
18             @Known_formatting_codes = qw(A B C E F G H I L M N R S T U X Z);
19             %Known_formatting_codes = map(($_=>1), @Known_formatting_codes);
20             @Known_directives = qw(head0 head1 head2 head3 head4 item over back headrow bodyrows row cell);
21             %Known_directives = map(($_=>'Plain'), @Known_directives);
22              
23             sub new {
24 123     123 1 1113 my $self = shift;
25 123         567 my $new = $self->SUPER::new();
26              
27 123         7699 $new->{'accept_codes'} = { map( ($_=>$_), @Known_formatting_codes ) };
28 123         898 $new->{'accept_directives'} = \%Known_directives;
29 123         450 return $new;
30             }
31              
32             sub _handle_element_start {
33 424     424   3869 my ($self, $element, $flags) = @_;
34              
35 424         596 $element =~ tr/-:./__/;
36              
37 424         2027 my $sub = $self->can('start_' . $element);
38 424 100       1858 $sub->($self, $flags) if $sub;
39             }
40              
41             sub _handle_text {
42 273     273   2438 my $self = shift;
43              
44 273         1313 my $sub = $self->can('handle_text');
45 273 50       1303 $sub->($self, @_) if $sub;
46             }
47              
48             sub _handle_element_end {
49 424     424   3581 my ($self, $element, $flags) = @_;
50 424         589 $element =~ tr/-:./__/;
51              
52 424         1676 my $sub = $self->can('end_' . $element);
53 424 100       1846 $sub->($self, $flags) if $sub;
54             }
55              
56 33     33 0 1480 sub nix_Z_codes { $_[0]{'nix_Z_codes'} = $_[1] }
57              
58             # Largely copied from Pod::Simple::_treat_Zs, modified to optionally
59             # keep Z elements, and so it doesn't complain about Zs with content.
60             #
61             sub _treat_Zs { # Nix Z<...>'s
62 46     46   9305 my($self,@stack) = @_;
63              
64 46         73 my($i, $treelet);
65 46         103 my $start_line = $stack[0][1]{'start_line'};
66              
67             # A recursive algorithm implemented iteratively! Whee!
68              
69 46         145 while($treelet = shift @stack) {
70 88         1490 for($i = 2; $i < @$treelet; ++$i) { # iterate over children
71 179 100       692 next unless ref $treelet->[$i]; # text nodes are uninteresting
72 50 100       147 unless($treelet->[$i][0] eq 'Z') {
73 42         73 unshift @stack, $treelet->[$i]; # recurse
74 42         110 next;
75             }
76            
77 8 100       37 if ($self->{'nix_Z_codes'}) {
78             #DEBUG > 1 and print "Nixing Z node @{$treelet->[$i]}\n";
79 2         4 splice(@$treelet, $i, 1); # thereby just nix this node.
80 2         7 --$i;
81             }
82              
83             }
84             }
85            
86 46         117 return;
87             }
88              
89             # The _ponder_* methods override the _ponder_* methods from
90             # Pod::Simple::BlackBox to add or alter functionality.
91              
92             sub _ponder_paragraph_buffer {
93              
94             # Para-token types as found in the buffer.
95             # ~Verbatim, ~Para, ~end, =head1..4, =for, =begin, =end,
96             # =over, =back, =item
97             # and the null =pod (to be complained about if over one line)
98             #
99             # "~data" paragraphs are something we generate at this level, depending on
100             # a currently open =over region
101              
102             # Events fired: Begin and end for:
103             # directivename (like head1 .. head4), item, extend,
104             # for (from =begin...=end, =for),
105             # over-bullet, over-number, over-text, over-block,
106             # item-bullet, item-number, item-text,
107             # Document,
108             # Data, Para, Verbatim
109             # B, C, longdirname (TODO -- wha?), etc. for all directives
110             #
111              
112 457     457   114451 my $self = $_[0];
113 457         761 my $paras;
114 457 100       1467 return unless @{$paras = $self->{'paras'}};
  457         1883  
115 342   100     1724 my $curr_open = ($self->{'curr_open'} ||= []);
116              
117 342         451 DEBUG > 10 and print "# Paragraph buffer: <<", pretty($paras), ">>\n";
118              
119             # We have something in our buffer. So apparently the document has started.
120 342 100       762 unless($self->{'doc_has_started'}) {
121 115         200 $self->{'doc_has_started'} = 1;
122            
123 115         136 my $starting_contentless;
124 115   33     1172 $starting_contentless =
125             (
126             !@$curr_open
127             and @$paras and ! grep $_->[0] ne '~end', @$paras
128             # i.e., if the paras is all ~ends
129             )
130             ;
131 115         128 DEBUG and print "# Starting ",
132             $starting_contentless ? 'contentless' : 'contentful',
133             " document\n"
134             ;
135            
136 115 50       722 $self->_handle_element_start('Document',
137             {
138             'start_line' => $paras->[0][1]{'start_line'},
139             $starting_contentless ? ( 'contentless' => 1 ) : (),
140             },
141             );
142             }
143              
144 342         494 my($para, $para_type);
145 342         960 while(@$paras) {
146 539 100 100     5820 last if @$paras == 1 and
      66        
147             ( $paras->[0][0] eq '=over' or $paras->[0][0] eq '~Verbatim'
148             or $paras->[0][0] eq '=item' )
149             ;
150             # Those're the three kinds of paragraphs that require lookahead.
151             # Actually, an "=item Foo" inside an <over type=text> region
152             # and any =item inside an <over type=block> region (rare)
153             # don't require any lookahead, but all others (bullets
154             # and numbers) do.
155              
156             # TODO: winge about many kinds of directives in non-resolving =for regions?
157             # TODO: many? like what? =head1 etc?
158              
159 509         705 $para = shift @$paras;
160 509         2737 $para_type = $para->[0];
161              
162 509         627 DEBUG > 1 and print "Pondering a $para_type paragraph, given the stack: (",
163             $self->_dump_curr_open(), ")\n";
164            
165 509 100       2186 if($para_type eq '=for') {
    100          
    100          
    100          
166 9 50       29 next if $self->_ponder_for($para,$curr_open,$paras);
167             } elsif($para_type eq '=begin') {
168 43 50       146 next if $self->_ponder_begin($para,$curr_open,$paras);
169             } elsif($para_type eq '=end') {
170 50 50       174 next if $self->_ponder_end($para,$curr_open,$paras);
171             } elsif($para_type eq '~end') { # The virtual end-document signal
172 123 50       482 next if $self->_ponder_doc_end($para,$curr_open,$paras);
173             }
174              
175              
176             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
177             #~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
178 284 100       923 if(grep $_->[1]{'~ignore'}, @$curr_open) {
179 2         4 DEBUG > 1 and
180             print "Skipping $para_type paragraph because in ignore mode.\n";
181 2         7 next;
182             }
183             #~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
184             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
185              
186 282 100       958 if($para_type eq '=pod') {
    100          
    100          
    100          
187 50         231 $self->_ponder_pod($para,$curr_open,$paras);
188             } elsif($para_type eq '=over') {
189 16 50       99 next if $self->_ponder_over($para,$curr_open,$paras);
190             } elsif($para_type eq '=back') {
191 16 50       104 next if $self->_ponder_back($para,$curr_open,$paras);
192             } elsif($para_type eq '=row') {
193 13 50       56 next if $self->_ponder_row_start($para,$curr_open,$paras);
194            
195             } else {
196             # All non-magical codes!!!
197            
198             # Here we start using $para_type for our own twisted purposes, to
199             # mean how it should get treated, not as what the element name
200             # should be.
201              
202 187         187 DEBUG > 1 and print "Pondering non-magical $para_type\n";
203              
204             # In tables, the start of a headrow or bodyrow also terminates an
205             # existing open row.
206 187 100 100     790 if($para_type eq '=headrow' || $para_type eq '=bodyrows') {
207 4         14 $self->_ponder_row_end($para,$curr_open,$paras);
208             }
209              
210             # Enforce some =headN discipline
211 187 50 66     732 if($para_type =~ m/^=head\d$/s
      66        
      33        
212             and ! $self->{'accept_heads_anywhere'}
213             and @$curr_open
214             and $curr_open->[-1][0] eq '=over'
215             ) {
216 0         0 DEBUG > 2 and print "'=$para_type' inside an '=over'!\n";
217 0         0 $self->whine(
218             $para->[1]{'start_line'},
219             "You forgot a '=back' before '$para_type'"
220             );
221 0         0 unshift @$paras, ['=back', {}, ''], $para; # close the =over
222 0         0 next;
223             }
224              
225              
226 187 100 33     1276 if($para_type eq '=item') {
    50          
    50          
    100          
    100          
    100          
    50          
227 25 50       134 next if $self->_ponder_item($para,$curr_open,$paras);
228 25         1031 $para_type = 'Plain';
229             # Now fall thru and process it.
230              
231             } elsif($para_type eq '=extend') {
232             # Well, might as well implement it here.
233 0         0 $self->_ponder_extend($para);
234 0         0 next; # and skip
235             } elsif($para_type eq '=encoding') {
236             # Not actually acted on here, but we catch errors here.
237 0         0 $self->_handle_encoding_second_level($para);
238              
239 0         0 next; # and skip
240             } elsif($para_type eq '~Verbatim') {
241 9         23 $para->[0] = 'Verbatim';
242 9         16 $para_type = '?Verbatim';
243             } elsif($para_type eq '~Para') {
244 96         164 $para->[0] = 'Para';
245 96         141 $para_type = '?Plain';
246             } elsif($para_type eq 'Data') {
247 2         6 $para->[0] = 'Data';
248 2         6 $para_type = '?Data';
249             } elsif( $para_type =~ s/^=//s
250             and defined( $para_type = $self->{'accept_directives'}{$para_type} )
251             ) {
252 55         73 DEBUG > 1 and print " Pondering known directive ${$para}[0] as $para_type\n";
253             } else {
254             # An unknown directive!
255             DEBUG > 1 and printf "Unhandled directive %s (Handled: %s)\n",
256 0         0 $para->[0], join(' ', sort keys %{$self->{'accept_directives'}} )
257             ;
258 0         0 $self->whine(
259             $para->[1]{'start_line'},
260             "Unknown directive: $para->[0]"
261             );
262              
263             # And maybe treat it as text instead of just letting it go?
264 0         0 next;
265             }
266              
267 187 100       799 if($para_type =~ s/^\?//s) {
268 107 100       253 if(! @$curr_open) { # usual case
269 63         89 DEBUG and print "Treating $para_type paragraph as such because stack is empty.\n";
270             } else {
271 44         183 my @fors = grep $_->[0] eq '=for', @$curr_open;
272 44         43 DEBUG > 1 and print "Containing fors: ",
273             join(',', map $_->[1]{'target'}, @fors), "\n";
274            
275 44 100       166 if(! @fors) {
    50          
276 13         20 DEBUG and print "Treating $para_type paragraph as such because stack has no =for's\n";
277            
278             #} elsif(grep $_->[1]{'~resolve'}, @fors) {
279             #} elsif(not grep !$_->[1]{'~resolve'}, @fors) {
280             } elsif( $fors[-1][1]{'~resolve'} ) {
281             # Look to the immediately containing for
282            
283 31 100       57 if($para_type eq 'Data') {
284 2         3 DEBUG and print "Treating Data paragraph as Plain/Verbatim because the containing =for ($fors[-1][1]{'target'}) is a resolver\n";
285 2         4 $para->[0] = 'Para';
286 2         4 $para_type = 'Plain';
287             } else {
288 29         50 DEBUG and print "Treating $para_type paragraph as such because the containing =for ($fors[-1][1]{'target'}) is a resolver\n";
289             }
290             } else {
291 0         0 DEBUG and print "Treating $para_type paragraph as Data because the containing =for ($fors[-1][1]{'target'}) is a non-resolver\n";
292 0         0 $para->[0] = $para_type = 'Data';
293             }
294             }
295             }
296              
297             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
298 187 100       486 if($para_type eq 'Plain') {
    50          
    0          
299 178         643 $self->_ponder_Plain($para);
300             } elsif($para_type eq 'Verbatim') {
301 9         79 $self->_ponder_Verbatim($para);
302             } elsif($para_type eq 'Data') {
303 0         0 $self->_ponder_Data($para);
304             } else {
305 0         0 die "\$para type is $para_type -- how did that happen?";
306             # Shouldn't happen.
307             }
308              
309             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
310 187         24952 $para->[0] =~ s/^[~=]//s;
311              
312 187         223 DEBUG and print "\n", Pod::Simple::BlackBox::pretty($para), "\n";
313              
314             # traverse the treelet (which might well be just one string scalar)
315 187   100     12017 $self->{'content_seen'} ||= 1;
316 187         776 $self->_traverse_treelet_bit(@$para);
317             }
318             }
319            
320 342         2403 return;
321             }
322              
323             sub _ponder_for {
324 9     9   15 my ($self,$para,$curr_open,$paras) = @_;
325              
326             # Fake it out as a begin/end
327 9         12 my $target;
328              
329 9 50       22 if(grep $_->[1]{'~ignore'}, @$curr_open) {
330 0         0 DEBUG > 1 and print "Ignoring ignorable =for\n";
331 0         0 return 1;
332             }
333              
334 9         22 for(my $i = 2; $i < @$para; ++$i) {
335 9 50       50 if($para->[$i] =~ s/^\s*(\S+)\s*//s) {
336 9         14 $target = $1;
337 9         66 last;
338             }
339             }
340 9 50       24 unless(defined $target) {
341 0         0 $self->whine(
342             $para->[1]{'start_line'},
343             "=for without a target?"
344             );
345 0         0 return 1;
346             }
347              
348 9 100 66     38 if (@$para > 3 or $para->[2]) {
349             # This is an ordinary =for and should be handled in the Pod::Simple way
350              
351 2         2 DEBUG > 1 and
352             print "Faking out a =for $target as a =begin $target / =end $target\n";
353            
354 2         5 $para->[0] = 'Data';
355            
356 2         23 unshift @$paras,
357             ['=begin',
358             {'start_line' => $para->[1]{'start_line'}, '~really' => '=for'},
359             $target,
360             ],
361             $para,
362             ['=end',
363             {'start_line' => $para->[1]{'start_line'}, '~really' => '=for'},
364             $target,
365             ],
366             ;
367            
368             } else {
369             # This is a =for with an =end tag
370              
371 7         8 DEBUG > 1 and
372             print "Faking out a =for $target as a =begin $target\n";
373            
374 7         33 unshift @$paras,
375             ['=begin',
376             {'start_line' => $para->[1]{'start_line'}, '~really' => '=for'},
377             $target,
378             ],
379             ;
380              
381             }
382 9         40 return 1;
383             }
384              
385             sub _ponder_begin {
386 43     43   67 my ($self,$para,$curr_open,$paras) = @_;
387              
388 43 100       242 unless ($para->[2] =~ /^\s*(?:table|sidebar|figure|listing)/) {
389 23         113 return $self->SUPER::_ponder_begin($para,$curr_open,$paras);
390             }
391              
392 20         63 my $content = join ' ', splice @$para, 2;
393 20         51 $content =~ s/^\s+//s;
394 20         50 $content =~ s/\s+$//s;
395              
396 20         110 my ($target, $title) = $content =~ m/^(\S+)\s*(.*)$/;
397 20 100       68 $title =~ s/^(picture|html)\s*// if ($target eq 'table');
398 20 100       51 $para->[1]{'title'} = $title if ($title);
399 20         45 $para->[1]{'target'} = $target; # without any ':'
400              
401 20 50       66 return 1 unless $self->{'accept_targets'}{$target};
402              
403 20         31 $para->[0] = '=for'; # Just what we happen to call these, internally
404 20   50     115 $para->[1]{'~really'} ||= '=begin';
405             # $para->[1]{'~ignore'} = 0;
406 20         36 $para->[1]{'~resolve'} = 1;
407              
408 20         36 push @$curr_open, $para;
409 20   50     85 $self->{'content_seen'} ||= 1;
410 20         52 $self->_handle_element_start($target, $para->[1]);
411              
412 20         97 return 1;
413             }
414              
415             sub _ponder_end {
416 50     50   89 my ($self,$para,$curr_open,$paras) = @_;
417 50         140 my $content = join ' ', splice @$para, 2;
418 50         266 $content =~ s/^\s+//s;
419 50         85 $content =~ s/\s+$//s;
420 50         73 DEBUG and print "Ogling '=end $content' directive\n";
421            
422 50 100       122 unless(length($content)) {
423 13 100 100     64 if (@$curr_open and $curr_open->[-1][1]{'~really'} eq '=for') {
424             # =for allows an empty =end directive
425 6         12 $content = $curr_open->[-1][1]{'target'};
426             } else {
427             # Everything else should complain about an empty =end directive
428 7         12 my $complaint = "'=end' without a target?";
429 7 100 66     24 if ( @$curr_open and $curr_open->[-1][0] eq '=for' ) {
430 1         15 $complaint .= " (Should be \"=end " . $curr_open->[-1][1]{'target'} . '")';
431             }
432 7         47 $self->whine( $para->[1]{'start_line'}, $complaint);
433 7         117 DEBUG and print "Ignoring targetless =end\n";
434 7         35 return 1;
435             }
436             }
437            
438 43 50       1231 unless($content =~ m/^\S+$/) { # i.e., unless it's one word
439 0         0 $self->whine(
440             $para->[1]{'start_line'},
441             "'=end $content' is invalid. (Stack: "
442             . $self->_dump_curr_open() . ')'
443             );
444 0         0 DEBUG and print "Ignoring mistargetted =end $content\n";
445 0         0 return 1;
446             }
447            
448 43 100       116 $self->_ponder_row_end($para,$curr_open,$paras) if $content eq 'table';
449              
450 43 50 33     242 unless(@$curr_open and $curr_open->[-1][0] eq '=for') {
451 0         0 $self->whine(
452             $para->[1]{'start_line'},
453             "=end $content without matching =begin. (Stack: "
454             . $self->_dump_curr_open() . ')'
455             );
456 0         0 DEBUG and print "Ignoring mistargetted =end $content\n";
457 0         0 return 1;
458             }
459            
460 43 100       134 unless($content eq $curr_open->[-1][1]{'target'}) {
461 1 50 33     8 if ($content eq 'for' and $curr_open->[-1][1]{'~really'} eq '=for') {
462             # =for allows a "=end for" directive
463 1         2 $content = $curr_open->[-1][1]{'target'};
464             } else {
465 0         0 $self->whine(
466             $para->[1]{'start_line'},
467             "=end $content doesn't match =begin "
468             . $curr_open->[-1][1]{'target'}
469             . ". (Stack: "
470             . $self->_dump_curr_open() . ')'
471             );
472 0         0 DEBUG and print "Ignoring mistargetted =end $content at line $para->[1]{'start_line'}\n";
473 0         0 return 1;
474             }
475             }
476              
477             # Else it's okay to close...
478 43 100       166 if(grep $_->[1]{'~ignore'}, @$curr_open) {
479 2         3 DEBUG > 1 and print "Not firing any event for this =end $content because in an ignored region\n";
480             # And that may be because of this to-be-closed =for region, or some
481             # other one, but it doesn't matter.
482             } else {
483 41         92 $curr_open->[-1][1]{'start_line'} = $para->[1]{'start_line'};
484             # what's that for?
485            
486 41   50     107 $self->{'content_seen'} ||= 1;
487 41 100 100     333 if ($content eq 'table' or $content eq 'sidebar' or $content eq 'figure' or $content eq 'listing') {
      100        
      100        
488 20         47 $self->_handle_element_end( $content );
489             } else {
490 21         71 $self->_handle_element_end( 'for', { 'target' => $content } );
491             }
492             }
493 43         59 DEBUG > 1 and print "Popping $curr_open->[-1][0] $curr_open->[-1][1]{'target'} because of =end $content\n";
494 43         82 pop @$curr_open;
495              
496 43         240 return 1;
497             }
498              
499             sub _ponder_row_start {
500 13     13   24 my ($self,$para,$curr_open,$paras) = @_;
501              
502 13         210 $self->_ponder_row_end($para,$curr_open,$paras);
503              
504 13         178 push @$curr_open, $para;
505              
506 13   50     38 $self->{'content_seen'} ||= 1;
507 13         31 $self->_handle_element_start('row', $para->[1]);
508              
509 13         234 return 1;
510             }
511              
512             sub _ponder_row_end {
513 28     28   46 my ($self,$para,$curr_open,$paras) = @_;
514             # PseudoPod doesn't have a row closing entity, so "=row" and "=end
515             # table" have to double for it.
516              
517 28 100 66     316 if(@$curr_open and $curr_open->[-1][0] eq '=row') {
518 13   50     60 $self->{'content_seen'} ||= 1;
519 13         20 my $over = pop @$curr_open;
520 13         30 $self->_handle_element_end( 'row' );
521             }
522 28         43 return 1;
523             }
524              
525             1;
526              
527             __END__
528              
529             =head1 NAME
530              
531             Pod::PseudoPod - A framework for parsing PseudoPod
532              
533             =head1 SYNOPSIS
534              
535             use strict;
536             package SomePseudoPodFormatter;
537             use base qw(Pod::PseudoPod);
538              
539             sub handle_text {
540             my($self, $text) = @_;
541             ...
542             }
543              
544             sub start_head1 {
545             my($self, $flags) = @_;
546             ...
547             }
548             sub end_head1 {
549             my($self) = @_;
550             ...
551             }
552              
553             ...and start_*/end_* methods for whatever other events you
554             want to catch.
555              
556             =head1 DESCRIPTION
557              
558             PseudoPod is an extended set of Pod tags used for book manuscripts.
559             Standard Pod doesn't have all the markup options you need to mark up
560             files for publishing production. PseudoPod adds a few extra tags for
561             footnotes, tables, sidebars, etc.
562              
563             This class adds parsing support for the PseudoPod tags. It also
564             overrides Pod::Simple's C<_handle_element_start>, C<_handle_text>, and
565             C<_handle_element_end> methods so that parser events are turned into
566             method calls.
567              
568             In general, you'll only want to use this module as the base class for
569             a PseudoPod formatter/processor.
570              
571             =head1 SEE ALSO
572              
573             L<Pod::Simple>, L<Pod::PseudoPod::HTML>, L<Pod::PseudoPod::Tutorial>
574              
575             =head1 COPYRIGHT
576              
577             Copyright (C) 2003-2009 Allison Randal.
578              
579             This library is free software; you can redistribute it and/or modify
580             it under the same terms as Perl itself. The full text of the license
581             can be found in the LICENSE file included with this module.
582              
583             This library is distributed in the hope that it will be useful, but
584             without any warranty; without even the implied warranty of
585             merchantability or fitness for a particular purpose.
586              
587             =head1 AUTHOR
588              
589             Allison Randal <allison@perl.org>
590              
591             =cut
592