File Coverage

blib/lib/Pod/Elemental/Transformer/Pod5.pm
Criterion Covered Total %
statement 131 131 100.0
branch 40 42 95.2
condition 18 19 94.7
subroutine 21 21 100.0
pod 0 1 0.0
total 210 214 98.1


line stmt bran cond sub pod time code
1             package Pod::Elemental::Transformer::Pod5;
2             # ABSTRACT: the default, minimal semantics of Perl5's pod element hierarchy
3             $Pod::Elemental::Transformer::Pod5::VERSION = '0.103005';
4 11     11   1289 use Moose;
  11         28  
  11         100  
5             with 'Pod::Elemental::Transformer';
6              
7             #pod =head1 SYNOPSIS
8             #pod
9             #pod Pod::Elemental::Transformer::Pod5->new->transform_node($pod_elem_document);
10             #pod
11             #pod ...and that's it.
12             #pod
13             #pod =head1 OVERVIEW
14             #pod
15             #pod The Pod5 transformer is meant to be used to convert the result of a "stock"
16             #pod Pod::Elemental::Document into something simpler to work with. It assumes that
17             #pod the document conforms more or less to the convetions laid out in L<perlpod> and
18             #pod L<perlpodspec>. It is not very strict, and makes very few assumptions,
19             #pod described here:
20             #pod
21             #pod =over 4
22             #pod
23             #pod =item * =begin/=end and =for enclose or produce regions within the document
24             #pod
25             #pod =item * regions are associated with format names; format names that begin with a colon enclose more pod-like content
26             #pod
27             #pod =item * regions nest strictly; all inner regions must end before outer regions
28             #pod
29             #pod =item * paragraphs in non-pod regions are "data" paragraphs
30             #pod
31             #pod =item * non-data paragraphs that start with spaces are "verbatim" paragraphs
32             #pod
33             #pod =item * groups of data or verbatim paragraphs can be consolidated
34             #pod
35             #pod =back
36             #pod
37             #pod Further, all elements are replaced with equivalent elements that perform the
38             #pod L<Pod::Elemental::Autoblank> role, so all "blank" events can be removed form
39             #pod the tree and ignored.
40             #pod
41             #pod =head1 CONFIGURATION
42             #pod
43             #pod None. For now, it just does the same thing every time with no configuration or
44             #pod options.
45             #pod
46             #pod =cut
47              
48 11     11   78613 use namespace::autoclean;
  11         1322  
  11         100  
49              
50 11     11   1703 use Pod::Elemental::Document;
  11         28  
  11         353  
51 11     11   5432 use Pod::Elemental::Element::Pod5::Command;
  11         53  
  11         462  
52 11     11   5845 use Pod::Elemental::Element::Pod5::Data;
  11         36  
  11         434  
53 11     11   5697 use Pod::Elemental::Element::Pod5::Nonpod;
  11         39  
  11         412  
54 11     11   5873 use Pod::Elemental::Element::Pod5::Ordinary;
  11         49  
  11         477  
55 11     11   6309 use Pod::Elemental::Element::Pod5::Verbatim;
  11         44  
  11         456  
56 11     11   6378 use Pod::Elemental::Element::Pod5::Region;
  11         52  
  11         602  
57              
58 11     11   6553 use Pod::Elemental::Selectors -all;
  11         35  
  11         57  
59              
60 629     629   2402 sub _gen_class { "Pod::Elemental::Element::Generic::$_[1]" }
61 881     881   4581 sub _class { "Pod::Elemental::Element::Pod5::$_[1]" }
62              
63             sub _region_para_parts {
64 40     40   128 my ($self, $para) = @_;
65              
66 40         1318 my ($colon, $target, $content, $nl) = $para->content =~ m/
67             \A
68             (:)?
69             (\S+)
70             (?:\s+(.+))?
71             (\s+)\z
72             /xsm;
73              
74 40 50       115 confess("=begin cannot be parsed") unless defined $target;
75              
76 40   100     211 $colon ||= '';
77 40   100     178 $content ||= '';
78              
79 40         156 return ($colon, $target, "$content$nl");
80             }
81              
82             sub __extract_region {
83 13     13   47 my ($self, $name, $in_paras) = @_;
84              
85 13         55 my %nest = ($name => 1);
86 13         27 my @region_paras;
87              
88 13         49 REGION_PARA: while (my $region_para = shift @$in_paras) {
89 157 100       442 if (s_command([ qw(begin end) ], $region_para)) {
90 25         97 my ($r_colon, $r_target) = $self->_region_para_parts($region_para);
91              
92 25         103 for ($nest{ "$r_colon$r_target" }) {
93 25 100       872 $_ += $region_para->command eq 'begin' ? 1 : -1;
94              
95 25 50       89 confess("=end $r_colon$r_target without matching begin") if $_ < 0;
96              
97 25 100 100     161 last REGION_PARA if !$_ and "$r_colon$r_target" eq $name;
98             }
99             }
100              
101 144         5704 push @region_paras, $region_para;
102             };
103              
104 13         489 return \@region_paras;
105             }
106              
107             sub _upgrade_nonpod {
108 12     12   42 my ($self, $in_paras) = @_;
109              
110             @$in_paras = map {
111 12 100       41 $_->isa( $self->_gen_class('Nonpod') )
  233         442  
112             ? $self->_class('Nonpod')->new({
113             content => $_->content,
114             })
115             : $_
116             } @$in_paras;
117             }
118              
119             sub _collect_regions {
120 25     25   74 my ($self, $in_paras) = @_;
121              
122 25         51 my @out_paras;
123              
124 25         104 my $s_region = s_command([ qw(begin for) ]);
125 25         84 my $region_class = $self->_class('Region');
126              
127 25         51 PARA: while (my $para = shift @{ $in_paras }) {
  220         7479  
128 195 100       415 push(@out_paras, $para), next PARA unless $s_region->($para);
129              
130 15 100       532 if ($para->command eq 'for') {
131             # factor out (for vertical space if nothing else) -- rjbs, 2009-10-20
132 2         9 my ($colon, $target, $content) = $self->_region_para_parts($para);
133              
134 2 100       9 my $region = $region_class->new({
135             children => [
136             $self->_gen_class('Text')->new({ content => $content }),
137             ],
138             format_name => $target,
139             is_pod => $colon ? 1 : 0,
140             content => "\n",
141             });
142              
143 2         8 push @out_paras, $region;
144 2         71 next PARA;
145             }
146              
147 13         58 my ($colon, $target, $content) = $self->_region_para_parts($para);
148              
149 13         102 my $region_paras = $self->__extract_region("$colon$target", $in_paras);
150              
151 13         64 shift @$region_paras while s_blank($region_paras->[0]);
152 13   100     82 pop @$region_paras while @$region_paras && s_blank($region_paras->[-1]);
153              
154 13 100       124 my $region = $region_class->new({
155             children => $self->_collect_regions($region_paras),
156             format_name => $target,
157             is_pod => $colon ? 1 : 0,
158             content => $content,
159             });
160              
161 13         567 push @out_paras, $region;
162             }
163              
164 25         105 @$in_paras = @out_paras;
165              
166 25         684 return $in_paras;
167             }
168              
169             sub _strip_markers {
170 12     12   40 my ($self, $in_paras) = @_;
171              
172 12         38 @$in_paras = grep { ! s_command([ qw(cut pod) ], $_) } @$in_paras;
  262         8592  
173 12   66     190 shift @$in_paras while @$in_paras and s_blank($in_paras->[0]);
174             }
175              
176             sub _autotype_paras {
177 27     27   101 my ($self, $paras, $is_pod) = @_;
178              
179             @$paras = map {
180 27         72 my $elem = $_;
  197         310  
181 197 100       417 if ($elem->isa( $self->_gen_class('Text') )) {
182 48 100       1213 my $class = $is_pod
    100          
183             ? $elem->content =~ /\A\s/
184             ? $self->_class('Verbatim')
185             : $self->_class('Ordinary')
186             : $self->_class('Data');
187              
188 48         1487 $elem = $class->new({ content => $elem->content });
189             }
190              
191 197 100       483 if ($elem->isa( $self->_class('Region') )) {
192 15         508 $self->_autotype_paras( $elem->children, $elem->is_pod );
193             }
194              
195 197 100       417 if ($elem->isa( $self->_gen_class('Command') )) {
196 40         120 $elem = $self->_class('Command')->new({
197             command => $elem->command,
198             content => $elem->content,
199             });
200             }
201              
202 197         1168 $elem;
203              
204             } @$paras;
205             }
206              
207             sub __text_class {
208 145     145   265 my ($self, $para) = @_;
209              
210 145         241 for my $type (qw(Verbatim Data)) {
211 286         494 my $class = $self->_class($type);
212 286 100       887 return $class if $para->isa($class);
213             }
214              
215 135         219 return;
216             }
217              
218             sub _collect_runs {
219 27     27   81 my ($self, $paras) = @_;
220              
221             $self->_collect_runs($_->children)
222 27         65 foreach grep { $_->isa( $self->_class('Region') ) } @$paras;
  197         362  
223              
224 27         116 PASS: for my $start (0 .. $#$paras) {
225 171 100       369 last PASS if $#$paras - $start < 2; # we need X..Blank..X at minimum
226              
227 145         287 my $class = $self->__text_class( $paras->[ $start ] );
228 145 100       329 next PASS unless $class;
229              
230 10         38 my @to_collect = ($start);
231 10         46 NEXT: for my $next ($start+1 .. $#$paras) {
232 26 100 100     134 if ($paras->[ $next ]->isa($class) or s_blank($paras->[ $next ])) {
233 19         45 push @to_collect, $next;
234 19         66 next NEXT;
235             }
236              
237 7         17 last NEXT;
238             }
239              
240 10         52 pop @to_collect while s_blank($paras->[ $to_collect[ -1 ] ]);
241              
242 10 100       49 next PASS unless @to_collect >= 3;
243              
244             my $new_content = join(qq{\n},
245 4         26 map { $_ = $_->content; chomp; $_ } @$paras[@to_collect]
  16         533  
  16         507  
  16         47  
246             );
247              
248 4         137 splice @$paras, $start, scalar(@to_collect), $class->new({
249             content => $new_content,
250             });
251              
252 4         16 redo PASS;
253             }
254              
255 27         49 my @out;
256 27         129 PASS: for (my $i = 0; $i < @$paras; $i++) {
257 101         176 my $this = $paras->[$i];
258 101         183 push @out, $this;
259              
260 101   100     341 while ($paras->[$i+1] and s_blank($paras->[$i+1])) {
261 84         193 $i++;
262 84 100       175 next unless $this->isa( $self->_class('Data') );
263 3         124 $this->content( $this->content . $paras->[$i]->content );
264             }
265             }
266              
267             # @out = grep { not s_blank($_) } @$paras;
268              
269             # I really don't feel bad about rewriting in place by the time we get here.
270             # These are private methods, and I know the consequence of calling them.
271             # Nobody else should be. So there. -- rjbs, 2009-10-17
272 27         763 @$paras = @out;
273 27         108 return \@out;
274             }
275              
276             sub transform_node {
277 12     12 0 98 my ($self, $node) = @_;
278              
279 12         440 $self->_strip_markers($node->children);
280 12         410 $self->_upgrade_nonpod($node->children);
281 12         403 $self->_collect_regions($node->children);
282 12         415 $self->_autotype_paras($node->children, 1);
283 12         378 $self->_collect_runs($node->children);
284              
285 12         47 return $node;
286             }
287              
288             __PACKAGE__->meta->make_immutable;
289              
290             1;
291              
292             __END__
293              
294             =pod
295              
296             =encoding UTF-8
297              
298             =head1 NAME
299              
300             Pod::Elemental::Transformer::Pod5 - the default, minimal semantics of Perl5's pod element hierarchy
301              
302             =head1 VERSION
303              
304             version 0.103005
305              
306             =head1 SYNOPSIS
307              
308             Pod::Elemental::Transformer::Pod5->new->transform_node($pod_elem_document);
309              
310             ...and that's it.
311              
312             =head1 OVERVIEW
313              
314             The Pod5 transformer is meant to be used to convert the result of a "stock"
315             Pod::Elemental::Document into something simpler to work with. It assumes that
316             the document conforms more or less to the convetions laid out in L<perlpod> and
317             L<perlpodspec>. It is not very strict, and makes very few assumptions,
318             described here:
319              
320             =over 4
321              
322             =item * =begin/=end and =for enclose or produce regions within the document
323              
324             =item * regions are associated with format names; format names that begin with a colon enclose more pod-like content
325              
326             =item * regions nest strictly; all inner regions must end before outer regions
327              
328             =item * paragraphs in non-pod regions are "data" paragraphs
329              
330             =item * non-data paragraphs that start with spaces are "verbatim" paragraphs
331              
332             =item * groups of data or verbatim paragraphs can be consolidated
333              
334             =back
335              
336             Further, all elements are replaced with equivalent elements that perform the
337             L<Pod::Elemental::Autoblank> role, so all "blank" events can be removed form
338             the tree and ignored.
339              
340             =head1 CONFIGURATION
341              
342             None. For now, it just does the same thing every time with no configuration or
343             options.
344              
345             =head1 AUTHOR
346              
347             Ricardo SIGNES <rjbs@cpan.org>
348              
349             =head1 COPYRIGHT AND LICENSE
350              
351             This software is copyright (c) 2020 by Ricardo SIGNES.
352              
353             This is free software; you can redistribute it and/or modify it under
354             the same terms as the Perl 5 programming language system itself.
355              
356             =cut