File Coverage

blib/lib/Pod/Elemental/PerlMunger.pm
Criterion Covered Total %
statement 28 33 84.8
branch 2 2 100.0
condition n/a
subroutine 9 10 90.0
pod 3 3 100.0
total 42 48 87.5


line stmt bran cond sub pod time code
1             package Pod::Elemental::PerlMunger;
2             # ABSTRACT: a thing that takes a string of Perl and rewrites its documentation
3             $Pod::Elemental::PerlMunger::VERSION = '0.200003';
4 1     1   679 use Moose::Role;
  1         2  
  1         10  
5              
6             #pod =head1 OVERVIEW
7             #pod
8             #pod This role is to be included in classes that rewrite the documentation of a Perl
9             #pod document, stripping out all the Pod, munging it, and replacing it into the
10             #pod Perl.
11             #pod
12             #pod The only relevant method is C<munge_perl_string>, which must be implemented
13             #pod with a different interface than will be exposed.
14             #pod
15             #pod When calling the C<munge_perl_string> method, arguments should be passed like
16             #pod this:
17             #pod
18             #pod $object->munge_perl_string($perl_string, \%arg);
19             #pod
20             #pod C<$perl_string> should be a character string containing Perl source code.
21             #pod
22             #pod C<%arg> may contain any input for the underlying procedure. The only key with
23             #pod associated meaning is C<filename> which may be omitted. If given, it should be
24             #pod the name of the file whose contents are being munged.
25             #pod
26             #pod The method will return a character string containing the rewritten and combined
27             #pod document.
28             #pod
29             #pod Classes including this role must implement a C<munge_perl_string> that expects
30             #pod to be called like this:
31             #pod
32             #pod $object->munge_perl_string(\%doc, \%arg);
33             #pod
34             #pod C<%doc> will have two entries:
35             #pod
36             #pod ppi - a PPI::Document of the Perl document with all its Pod removed
37             #pod pod - a Pod::Elemental::Document with no transformations yet performed
38             #pod
39             #pod This C<munge_perl_string> method should return a hashref in the same format as
40             #pod C<%doc>.
41             #pod
42             #pod =cut
43              
44 1     1   5309 use namespace::autoclean;
  1         2  
  1         10  
45              
46 1     1   63 use Encode ();
  1         2  
  1         22  
47 1     1   5 use List::Util 1.33 qw(any max);
  1         29  
  1         69  
48 1     1   5 use Params::Util qw(_INSTANCE);
  1         1  
  1         37  
49 1     1   735 use PPI;
  1         119382  
  1         1148  
50              
51             requires 'munge_perl_string';
52              
53             around munge_perl_string => sub {
54             my ($orig, $self, $perl, $arg) = @_;
55              
56             my $perl_utf8 = Encode::encode('utf-8', $perl, Encode::FB_CROAK);
57              
58             my $ppi_document = PPI::Document->new(\$perl_utf8);
59             confess(PPI::Document->errstr) unless $ppi_document;
60              
61             my $last_code_elem;
62             my $code_elems = $ppi_document->find(sub {
63             return if grep { $_[1]->isa("PPI::Token::$_") }
64             qw(Comment Pod Whitespace Separator Data End);
65             return 1;
66             });
67              
68             $code_elems ||= [];
69             for my $elem (@$code_elems) {
70             # Really, we might get two elements on the same line, and one could be
71             # later in position because it could have a later column — but we don't
72             # care, because we're only thinking about Pod, which is linewise.
73             next if $last_code_elem
74             and $elem->line_number <= $last_code_elem->line_number;
75              
76             $last_code_elem = $elem;
77             }
78              
79             my @pod_tokens;
80              
81             {
82             my @queue = $ppi_document->children;
83             while (my $element = shift @queue) {
84             if ($element->isa('PPI::Token::Pod')) {
85             my $after_last = $last_code_elem
86             && $last_code_elem->line_number > $element->line_number;
87             my @replacements = $self->_replacements_for($element, $after_last);
88              
89             # save the text for use in building the Pod-only document
90             push @pod_tokens, "$element";
91              
92             my $last = $element;
93             while (my $next = shift @replacements) {
94             my $ok = $last->insert_after($next);
95             confess("error inserting replacement!") unless $ok;
96             $last = $next;
97             }
98              
99             $element->delete;
100              
101             next;
102             }
103              
104             if ( _INSTANCE($element, 'PPI::Node') ) {
105             # Depth-first keeps the queue size down
106             unshift @queue, $element->children;
107             }
108             }
109             }
110              
111             my $finder = sub {
112             my $node = $_[1];
113             return 0 unless any { $node->isa($_) }
114             qw( PPI::Token::Quote PPI::Token::QuoteLike PPI::Token::HereDoc );
115             return 1 if $node->content =~ /^=[a-z]/m;
116             return 0;
117             };
118              
119             if ($ppi_document->find_first($finder)) {
120             $self->log(
121             sprintf "can't invoke %s on %s: there is POD inside string literals",
122             $self->plugin_name,
123             (defined $arg->{filename} ? $arg->{filename} : 'input')
124             );
125             }
126              
127             # TODO: I should add a $weaver->weave_* like the Linewise methods to take the
128             # input, get a Document, perform the stock transformations, and then weave.
129             # -- rjbs, 2009-10-24
130             my $pod_str = join "\n", @pod_tokens;
131             my $pod_document = Pod::Elemental->read_string($pod_str);
132              
133             my $doc = $self->$orig(
134             {
135             ppi => $ppi_document,
136             pod => $pod_document,
137             },
138             $arg,
139             );
140              
141             my $new_pod = $doc->{pod}->as_pod_string;
142              
143             my $end_finder = sub {
144             return 1 if $_[1]->isa('PPI::Statement::End')
145             || $_[1]->isa('PPI::Statement::Data');
146             return 0;
147             };
148              
149             my $end = do {
150             my $end_elem = $doc->{ppi}->find($end_finder);
151              
152             # If there's nothing after __END__, we can put the POD there:
153             if (not $end_elem or (@$end_elem == 1 and
154             $end_elem->[0]->isa('PPI::Statement::End') and
155             $end_elem->[0] =~ /^__END__\s*\z/)) {
156             $end_elem = [];
157             }
158              
159             @$end_elem ? join q{}, @$end_elem : undef;
160             };
161              
162             $doc->{ppi}->prune($end_finder);
163              
164             my $new_perl = Encode::decode(
165             'utf-8',
166             $doc->{ppi}->serialize,
167             Encode::FB_CROAK,
168             );
169              
170             s/\n\s*\z// for $new_perl, $new_pod;
171              
172             return defined $end
173             ? "$new_perl\n\n$new_pod\n\n$end"
174             : "$new_perl\n\n__END__\n\n$new_pod\n";
175             };
176              
177             #pod =attr replacer
178             #pod
179             #pod The replacer is either a method name or code reference used to produces PPI
180             #pod elements used to replace removed Pod. By default, it is
181             #pod C<L</replace_with_nothing>>, which just removes Pod tokens entirely. This
182             #pod means that the line numbers of the code in the newly-produced document are
183             #pod changed, if the Pod had been interleaved with the code.
184             #pod
185             #pod See also C<L</replace_with_comment>> and C<L</replace_with_blank>>.
186             #pod
187             #pod If no further code follows the Pod being replaced, C<L</post_code_replacer>> is
188             #pod used instead.
189             #pod
190             #pod =attr post_code_replacer
191             #pod
192             #pod This attribute is used just like C<L</replacer>>, and defaults to its value,
193             #pod but is used for building replacements for Pod removed after the last hunk of
194             #pod code. The idea is that if you're only concerned about altering your code's
195             #pod line numbers, you can stop replacing stuff after there's no more code to be
196             #pod affected.
197             #pod
198             #pod =cut
199              
200             has replacer => (
201             is => 'ro',
202             default => 'replace_with_nothing',
203             );
204              
205             has post_code_replacer => (
206             is => 'ro',
207             lazy => 1,
208             default => sub { $_[0]->replacer },
209             );
210              
211             sub _replacements_for {
212 13     13   22 my ($self, $element, $after_last) = @_;
213              
214 13 100       526 my $replacer = $after_last ? $self->replacer : $self->post_code_replacer;
215 13         43 return $self->$replacer($element);
216             }
217              
218             #pod =method replace_with_nothing
219             #pod
220             #pod This method returns nothing. It's the default C<L</replacer>>. It's not very
221             #pod interesting.
222             #pod
223             #pod =cut
224              
225 9     9 1 21 sub replace_with_nothing { return }
226              
227             #pod =method replace_with_comment
228             #pod
229             #pod This replacer replaces removed Pod elements with a comment containing their
230             #pod text. In other words:
231             #pod
232             #pod =head1 A header!
233             #pod
234             #pod This is great!
235             #pod
236             #pod =cut
237             #pod
238             #pod ...is replaced with:
239             #pod
240             #pod # =head1 A header!
241             #pod #
242             #pod # This is great!
243             #pod #
244             #pod # =cut
245             #pod
246             #pod =cut
247              
248             sub replace_with_comment {
249 4     4 1 5 my ($self, $element) = @_;
250              
251 4         12 my $text = "$element";
252              
253 4         49 (my $pod = $text) =~ s/^(.)/#pod $1/mg;
254 4         24 $pod =~ s/^$/#pod/mg;
255 4         20 my $commented_out = PPI::Token::Comment->new($pod);
256              
257 4         33 return $commented_out;
258             }
259              
260             #pod =method replace_with_blank
261             #pod
262             #pod This replacer replaces removed Pod elements with vertical whitespace of equal
263             #pod line count. In other words:
264             #pod
265             #pod =head1 A header!
266             #pod
267             #pod This is great!
268             #pod
269             #pod =cut
270             #pod
271             #pod ...is replaced with five blank lines.
272             #pod
273             #pod =cut
274              
275             sub replace_with_blank {
276 0     0 1   my ($self, $element) = @_;
277              
278 0           my $text = "$element";
279 0           my @lines = split /\n/, $text;
280 0           my $blank = PPI::Token::Whitespace->new("\n" x (@lines));
281              
282 0           return $blank;
283             }
284              
285              
286             1;
287              
288             __END__
289              
290             =pod
291              
292             =encoding UTF-8
293              
294             =head1 NAME
295              
296             Pod::Elemental::PerlMunger - a thing that takes a string of Perl and rewrites its documentation
297              
298             =head1 VERSION
299              
300             version 0.200003
301              
302             =head1 OVERVIEW
303              
304             This role is to be included in classes that rewrite the documentation of a Perl
305             document, stripping out all the Pod, munging it, and replacing it into the
306             Perl.
307              
308             The only relevant method is C<munge_perl_string>, which must be implemented
309             with a different interface than will be exposed.
310              
311             When calling the C<munge_perl_string> method, arguments should be passed like
312             this:
313              
314             $object->munge_perl_string($perl_string, \%arg);
315              
316             C<$perl_string> should be a character string containing Perl source code.
317              
318             C<%arg> may contain any input for the underlying procedure. The only key with
319             associated meaning is C<filename> which may be omitted. If given, it should be
320             the name of the file whose contents are being munged.
321              
322             The method will return a character string containing the rewritten and combined
323             document.
324              
325             Classes including this role must implement a C<munge_perl_string> that expects
326             to be called like this:
327              
328             $object->munge_perl_string(\%doc, \%arg);
329              
330             C<%doc> will have two entries:
331              
332             ppi - a PPI::Document of the Perl document with all its Pod removed
333             pod - a Pod::Elemental::Document with no transformations yet performed
334              
335             This C<munge_perl_string> method should return a hashref in the same format as
336             C<%doc>.
337              
338             =head1 ATTRIBUTES
339              
340             =head2 replacer
341              
342             The replacer is either a method name or code reference used to produces PPI
343             elements used to replace removed Pod. By default, it is
344             C<L</replace_with_nothing>>, which just removes Pod tokens entirely. This
345             means that the line numbers of the code in the newly-produced document are
346             changed, if the Pod had been interleaved with the code.
347              
348             See also C<L</replace_with_comment>> and C<L</replace_with_blank>>.
349              
350             If no further code follows the Pod being replaced, C<L</post_code_replacer>> is
351             used instead.
352              
353             =head2 post_code_replacer
354              
355             This attribute is used just like C<L</replacer>>, and defaults to its value,
356             but is used for building replacements for Pod removed after the last hunk of
357             code. The idea is that if you're only concerned about altering your code's
358             line numbers, you can stop replacing stuff after there's no more code to be
359             affected.
360              
361             =head1 METHODS
362              
363             =head2 replace_with_nothing
364              
365             This method returns nothing. It's the default C<L</replacer>>. It's not very
366             interesting.
367              
368             =head2 replace_with_comment
369              
370             This replacer replaces removed Pod elements with a comment containing their
371             text. In other words:
372              
373             =head1 A header!
374              
375             This is great!
376              
377             =cut
378              
379             ...is replaced with:
380              
381             # =head1 A header!
382             #
383             # This is great!
384             #
385             # =cut
386              
387             =head2 replace_with_blank
388              
389             This replacer replaces removed Pod elements with vertical whitespace of equal
390             line count. In other words:
391              
392             =head1 A header!
393              
394             This is great!
395              
396             =cut
397              
398             ...is replaced with five blank lines.
399              
400             =head1 AUTHOR
401              
402             Ricardo SIGNES <rjbs@cpan.org>
403              
404             =head1 CONTRIBUTORS
405              
406             =for stopwords Christopher J. Madsen Dave Rolsky Karen Etheridge
407              
408             =over 4
409              
410             =item *
411              
412             Christopher J. Madsen <perl@cjmweb.net>
413              
414             =item *
415              
416             Dave Rolsky <autarch@urth.org>
417              
418             =item *
419              
420             Karen Etheridge <ether@cpan.org>
421              
422             =back
423              
424             =head1 COPYRIGHT AND LICENSE
425              
426             This software is copyright (c) 2015 by Ricardo SIGNES.
427              
428             This is free software; you can redistribute it and/or modify it under
429             the same terms as the Perl 5 programming language system itself.
430              
431             =cut