File Coverage

lib/Pod/PseudoPod.pm
Criterion Covered Total %
statement 186 218 85.3
branch 105 132 79.5
condition 45 65 69.2
subroutine 17 17 100.0
pod 1 2 50.0
total 354 434 81.5


line stmt bran cond sub pod time code
1              
2             package Pod::PseudoPod;
3 10     10   6598 use Pod::Simple;
  10         274740  
  10         423  
4             @ISA = qw(Pod::Simple);
5 10     10   72 use strict;
  10         19  
  10         251  
6              
7 10         985 use vars qw(
8             $VERSION @ISA
9             @Known_formatting_codes @Known_directives
10             %Known_formatting_codes %Known_directives
11 10     10   47 );
  10         15  
12              
13             @ISA = ('Pod::Simple');
14             $VERSION = '0.19';
15              
16 10 50   10   29465 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 844 my $self = shift;
25 123         342 my $new = $self->SUPER::new();
26              
27 123         3469 $new->{'accept_codes'} = { map( ($_=>$_), @Known_formatting_codes ) };
28 123         391 $new->{'accept_directives'} = \%Known_directives;
29 123         223 return $new;
30             }
31              
32             sub _handle_element_start {
33 424     424   3561 my ($self, $element, $flags) = @_;
34              
35 424         529 $element =~ tr/-:./__/;
36              
37 424         1369 my $sub = $self->can('start_' . $element);
38 424 100       1194 $sub->($self, $flags) if $sub;
39             }
40              
41             sub _handle_text {
42 273     273   2003 my $self = shift;
43              
44 273         477 my $sub = $self->can('handle_text');
45 273 50       613 $sub->($self, @_) if $sub;
46             }
47              
48             sub _handle_element_end {
49 424     424   3332 my ($self, $element, $flags) = @_;
50 424         543 $element =~ tr/-:./__/;
51              
52 424         1109 my $sub = $self->can('end_' . $element);
53 424 100       1067 $sub->($self, $flags) if $sub;
54             }
55              
56 33     33 0 65 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   7060 my($self,@stack) = @_;
63              
64 46         65 my($i, $treelet);
65 46         73 my $start_line = $stack[0][1]{'start_line'};
66              
67             # A recursive algorithm implemented iteratively! Whee!
68              
69 46         97 while($treelet = shift @stack) {
70 88         161 for($i = 2; $i < @$treelet; ++$i) { # iterate over children
71 179 100       403 next unless ref $treelet->[$i]; # text nodes are uninteresting
72 50 100       93 unless($treelet->[$i][0] eq 'Z') {
73 42         58 unshift @stack, $treelet->[$i]; # recurse
74 42         64 next;
75             }
76            
77 8 100       34 if ($self->{'nix_Z_codes'}) {
78             #DEBUG > 1 and print "Nixing Z node @{$treelet->[$i]}\n";
79 2         5 splice(@$treelet, $i, 1); # thereby just nix this node.
80 2         5 --$i;
81             }
82              
83             }
84             }
85            
86 46         74 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   57133 my $self = $_[0];
113 457         473 my $paras;
114 457 100       423 return unless @{$paras = $self->{'paras'}};
  457         974  
115 342   100     831 my $curr_open = ($self->{'curr_open'} ||= []);
116              
117 342         335 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       537 unless($self->{'doc_has_started'}) {
121 115         147 $self->{'doc_has_started'} = 1;
122            
123 115         121 my $starting_contentless;
124 115   33     957 $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         123 DEBUG and print "# Starting ",
132             $starting_contentless ? 'contentless' : 'contentful',
133             " document\n"
134             ;
135            
136             $self->_handle_element_start('Document',
137             {
138 115 50       382 'start_line' => $paras->[0][1]{'start_line'},
139             $starting_contentless ? ( 'contentless' => 1 ) : (),
140             },
141             );
142             }
143              
144 342         462 my($para, $para_type);
145 342         510 while(@$paras) {
146 539 100 100     2355 last if @$paras == 1 and
      100        
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 region
152             # and any =item inside an 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         728 $para = shift @$paras;
160 509         644 $para_type = $para->[0];
161              
162 509         478 DEBUG > 1 and print "Pondering a $para_type paragraph, given the stack: (",
163             $self->_dump_curr_open(), ")\n";
164            
165 509 100       1279 if($para_type eq '=for') {
    100          
    100          
    100          
166 9 50       27 next if $self->_ponder_for($para,$curr_open,$paras);
167             } elsif($para_type eq '=begin') {
168 43 50       108 next if $self->_ponder_begin($para,$curr_open,$paras);
169             } elsif($para_type eq '=end') {
170 50 50       117 next if $self->_ponder_end($para,$curr_open,$paras);
171             } elsif($para_type eq '~end') { # The virtual end-document signal
172 123 50       304 next if $self->_ponder_doc_end($para,$curr_open,$paras);
173             }
174              
175              
176             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
177             #~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
178 284 100       607 if(grep $_->[1]{'~ignore'}, @$curr_open) {
179 2         5 DEBUG > 1 and
180             print "Skipping $para_type paragraph because in ignore mode.\n";
181 2         4 next;
182             }
183             #~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
184             # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
185              
186 282 100       626 if($para_type eq '=pod') {
    100          
    100          
    100          
187 50         138 $self->_ponder_pod($para,$curr_open,$paras);
188             } elsif($para_type eq '=over') {
189 16 50       61 next if $self->_ponder_over($para,$curr_open,$paras);
190             } elsif($para_type eq '=back') {
191 16 50       55 next if $self->_ponder_back($para,$curr_open,$paras);
192             } elsif($para_type eq '=row') {
193 13 50       31 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         186 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     533 if($para_type eq '=headrow' || $para_type eq '=bodyrows') {
207 4         11 $self->_ponder_row_end($para,$curr_open,$paras);
208             }
209              
210             # Enforce some =headN discipline
211 187 50 66     497 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             $self->whine(
218 0         0 $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     754 if($para_type eq '=item') {
    50          
    50          
    100          
    100          
    100          
    50          
227 25 50       74 next if $self->_ponder_item($para,$curr_open,$paras);
228 25         468 $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         17 $para->[0] = 'Verbatim';
242 9         14 $para_type = '?Verbatim';
243             } elsif($para_type eq '~Para') {
244 96         133 $para->[0] = 'Para';
245 96         138 $para_type = '?Plain';
246             } elsif($para_type eq 'Data') {
247 2         4 $para->[0] = 'Data';
248 2         5 $para_type = '?Data';
249             } elsif( $para_type =~ s/^=//s
250             and defined( $para_type = $self->{'accept_directives'}{$para_type} )
251             ) {
252 55         64 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             $self->whine(
259 0         0 $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       538 if($para_type =~ s/^\?//s) {
268 107 100       182 if(! @$curr_open) { # usual case
269 63         69 DEBUG and print "Treating $para_type paragraph as such because stack is empty.\n";
270             } else {
271 44         125 my @fors = grep $_->[0] eq '=for', @$curr_open;
272             DEBUG > 1 and print "Containing fors: ",
273 44         51 join(',', map $_->[1]{'target'}, @fors), "\n";
274            
275 44 100       120 if(! @fors) {
    50          
276 13         31 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       63 if($para_type eq 'Data') {
284 2         2 DEBUG and print "Treating Data paragraph as Plain/Verbatim because the containing =for ($fors[-1][1]{'target'}) is a resolver\n";
285 2         6 $para->[0] = 'Para';
286 2         4 $para_type = 'Plain';
287             } else {
288 29         40 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       287 if($para_type eq 'Plain') {
    50          
    0          
299 178         446 $self->_ponder_Plain($para);
300             } elsif($para_type eq 'Verbatim') {
301 9         58 $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         12364 $para->[0] =~ s/^[~=]//s;
311              
312 187         206 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     448 $self->{'content_seen'} ||= 1;
316 187         428 $self->_traverse_treelet_bit(@$para);
317             }
318             }
319            
320 342         1774 return;
321             }
322              
323             sub _ponder_for {
324 9     9   17 my ($self,$para,$curr_open,$paras) = @_;
325              
326             # Fake it out as a begin/end
327 9         12 my $target;
328              
329 9 50       20 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         24 for(my $i = 2; $i < @$para; ++$i) {
335 9 50       43 if($para->[$i] =~ s/^\s*(\S+)\s*//s) {
336 9         21 $target = $1;
337 9         15 last;
338             }
339             }
340 9 50       18 unless(defined $target) {
341             $self->whine(
342 0         0 $para->[1]{'start_line'},
343             "=for without a target?"
344             );
345 0         0 return 1;
346             }
347              
348 9 100 66     32 if (@$para > 3 or $para->[2]) {
349             # This is an ordinary =for and should be handled in the Pod::Simple way
350              
351 2         4 DEBUG > 1 and
352             print "Faking out a =for $target as a =begin $target / =end $target\n";
353            
354 2         4 $para->[0] = 'Data';
355            
356             unshift @$paras,
357             ['=begin',
358             {'start_line' => $para->[1]{'start_line'}, '~really' => '=for'},
359             $target,
360             ],
361             $para,
362             ['=end',
363 2         17 {'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         33 DEBUG > 1 and
372             print "Faking out a =for $target as a =begin $target\n";
373            
374             unshift @$paras,
375             ['=begin',
376 7         45 {'start_line' => $para->[1]{'start_line'}, '~really' => '=for'},
377             $target,
378             ],
379             ;
380              
381             }
382 9         33 return 1;
383             }
384              
385             sub _ponder_begin {
386 43     43   71 my ($self,$para,$curr_open,$paras) = @_;
387              
388 43 100       155 unless ($para->[2] =~ /^\s*(?:table|sidebar|figure|listing)/) {
389 23         109 return $self->SUPER::_ponder_begin($para,$curr_open,$paras);
390             }
391              
392 20         65 my $content = join ' ', splice @$para, 2;
393 20         40 $content =~ s/^\s+//s;
394 20         44 $content =~ s/\s+$//s;
395              
396 20         71 my ($target, $title) = $content =~ m/^(\S+)\s*(.*)$/;
397 20 100       50 $title =~ s/^(picture|html)\s*// if ($target eq 'table');
398 20 100       36 $para->[1]{'title'} = $title if ($title);
399 20         32 $para->[1]{'target'} = $target; # without any ':'
400              
401 20 50       45 return 1 unless $self->{'accept_targets'}{$target};
402              
403 20         24 $para->[0] = '=for'; # Just what we happen to call these, internally
404 20   50     89 $para->[1]{'~really'} ||= '=begin';
405             # $para->[1]{'~ignore'} = 0;
406 20         33 $para->[1]{'~resolve'} = 1;
407              
408 20         30 push @$curr_open, $para;
409 20   50     81 $self->{'content_seen'} ||= 1;
410 20         43 $self->_handle_element_start($target, $para->[1]);
411              
412 20         67 return 1;
413             }
414              
415             sub _ponder_end {
416 50     50   92 my ($self,$para,$curr_open,$paras) = @_;
417 50         144 my $content = join ' ', splice @$para, 2;
418 50         105 $content =~ s/^\s+//s;
419 50         81 $content =~ s/\s+$//s;
420 50         50 DEBUG and print "Ogling '=end $content' directive\n";
421            
422 50 100       94 unless(length($content)) {
423 13 100 100     57 if (@$curr_open and $curr_open->[-1][1]{'~really'} eq '=for') {
424             # =for allows an empty =end directive
425 6         11 $content = $curr_open->[-1][1]{'target'};
426             } else {
427             # Everything else should complain about an empty =end directive
428 7         8 my $complaint = "'=end' without a target?";
429 7 100 66     16 if ( @$curr_open and $curr_open->[-1][0] eq '=for' ) {
430 1         3 $complaint .= " (Should be \"=end " . $curr_open->[-1][1]{'target'} . '")';
431             }
432 7         32 $self->whine( $para->[1]{'start_line'}, $complaint);
433 7         176 DEBUG and print "Ignoring targetless =end\n";
434 7         21 return 1;
435             }
436             }
437            
438 43 50       139 unless($content =~ m/^\S+$/) { # i.e., unless it's one word
439             $self->whine(
440 0         0 $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       109 $self->_ponder_row_end($para,$curr_open,$paras) if $content eq 'table';
449              
450 43 50 33     162 unless(@$curr_open and $curr_open->[-1][0] eq '=for') {
451             $self->whine(
452 0         0 $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       93 unless($content eq $curr_open->[-1][1]{'target'}) {
461 1 50 33     9 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             $self->whine(
466             $para->[1]{'start_line'},
467             "=end $content doesn't match =begin "
468 0         0 . $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       167 if(grep $_->[1]{'~ignore'}, @$curr_open) {
479 2         5 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         65 $curr_open->[-1][1]{'start_line'} = $para->[1]{'start_line'};
484             # what's that for?
485            
486 41   50     69 $self->{'content_seen'} ||= 1;
487 41 100 100     208 if ($content eq 'table' or $content eq 'sidebar' or $content eq 'figure' or $content eq 'listing') {
      100        
      100        
488 20         68 $self->_handle_element_end( $content );
489             } else {
490 21         62 $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         52 pop @$curr_open;
495              
496 43         177 return 1;
497             }
498              
499             sub _ponder_row_start {
500 13     13   24 my ($self,$para,$curr_open,$paras) = @_;
501              
502 13         32 $self->_ponder_row_end($para,$curr_open,$paras);
503              
504 13         17 push @$curr_open, $para;
505              
506 13   50     21 $self->{'content_seen'} ||= 1;
507 13         24 $self->_handle_element_start('row', $para->[1]);
508              
509 13         30 return 1;
510             }
511              
512             sub _ponder_row_end {
513 28     28   38 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     87 if(@$curr_open and $curr_open->[-1][0] eq '=row') {
518 13   50     23 $self->{'content_seen'} ||= 1;
519 13         24 my $over = pop @$curr_open;
520 13         24 $self->_handle_element_end( 'row' );
521             }
522 28         34 return 1;
523             }
524              
525             sub _get_item_type {
526 41     41   711 my ($self, $para) = @_;
527 41 100       97 return $para->[1]{'~type'} if $para->[1]{'~type'};
528              
529 25         35 my $content = join "\n", @{$para}[2 .. $#$para];
  25         47  
530 25 50       62 if ($content =~ s/^\s*(\d+)\.?\s+?//s) {
531 0         0 $para->[1]{orig_content} = $content;
532 0         0 $para->[1]{number} = $1;
533              
534 0         0 $para->[-1] = $content;
535 0         0 return $para->[1]{'~type'} = 'number';
536             }
537              
538 25         71 return $self->SUPER::_get_item_type($para);
539             }
540              
541             1;
542              
543             __END__