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