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 0.200007;
2             # ABSTRACT: a thing that takes a string of Perl and rewrites its documentation
3              
4 1     1   614 use Moose::Role;
  1         2  
  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   4855 use namespace::autoclean;
  1         2  
  1         9  
52              
53 1     1   63 use Encode ();
  1         2  
  1         20  
54 1     1   5 use List::Util 1.33 qw(any max);
  1         19  
  1         52  
55 1     1   6 use Params::Util qw(_INSTANCE);
  1         2  
  1         32  
56 1     1   439 use PPI;
  1         82748  
  1         992  
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   26 my ($self, $element, $after_last) = @_;
217              
218 13 100       459 my $replacer = $after_last ? $self->replacer : $self->post_code_replacer;
219 13         44 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 16 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 10 my ($self, $element) = @_;
254              
255 4         12 my $text = "$element";
256              
257 4         45 (my $pod = $text) =~ s/^(.)/#pod $1/mg;
258 4         21 $pod =~ s/^$/#pod/mg;
259 4         21 my $commented_out = PPI::Token::Comment->new($pod);
260              
261 4         26 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.200007
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 PERL VERSION
356              
357             This library should run on perls released even a long time ago. It should work
358             on any version of perl released in the last five years.
359              
360             Although it may work on older versions of perl, no guarantee is made that the
361             minimum required version will not be increased. The version may be increased
362             for any reason, and there is no promise that patches will be accepted to lower
363             the minimum required perl.
364              
365             =head1 ATTRIBUTES
366              
367             =head2 replacer
368              
369             The replacer is either a method name or code reference used to produces PPI
370             elements used to replace removed Pod. By default, it is
371             C<L</replace_with_nothing>>, which just removes Pod tokens entirely. This
372             means that the line numbers of the code in the newly-produced document are
373             changed, if the Pod had been interleaved with the code.
374              
375             See also C<L</replace_with_comment>> and C<L</replace_with_blank>>.
376              
377             If no further code follows the Pod being replaced, C<L</post_code_replacer>> is
378             used instead.
379              
380             =head2 post_code_replacer
381              
382             This attribute is used just like C<L</replacer>>, and defaults to its value,
383             but is used for building replacements for Pod removed after the last hunk of
384             code. The idea is that if you're only concerned about altering your code's
385             line numbers, you can stop replacing stuff after there's no more code to be
386             affected.
387              
388             =head1 METHODS
389              
390             =head2 replace_with_nothing
391              
392             This method returns nothing. It's the default C<L</replacer>>. It's not very
393             interesting.
394              
395             =head2 replace_with_comment
396              
397             This replacer replaces removed Pod elements with a comment containing their
398             text. In other words:
399              
400             =head1 A header!
401              
402             This is great!
403              
404             =cut
405              
406             ...is replaced with:
407              
408             # =head1 A header!
409             #
410             # This is great!
411             #
412             # =cut
413              
414             =head2 replace_with_blank
415              
416             This replacer replaces removed Pod elements with vertical whitespace of equal
417             line count. In other words:
418              
419             =head1 A header!
420              
421             This is great!
422              
423             =cut
424              
425             ...is replaced with five blank lines.
426              
427             =head1 AUTHOR
428              
429             Ricardo SIGNES <cpan@semiotic.systems>
430              
431             =head1 CONTRIBUTORS
432              
433             =for stopwords Christopher J. Madsen Dave Rolsky Karen Etheridge perlancar (on PC, Bandung) Ricardo Signes
434              
435             =over 4
436              
437             =item *
438              
439             Christopher J. Madsen <perl@cjmweb.net>
440              
441             =item *
442              
443             Dave Rolsky <autarch@urth.org>
444              
445             =item *
446              
447             Karen Etheridge <ether@cpan.org>
448              
449             =item *
450              
451             perlancar (on PC, Bandung) <perlancar@gmail.com>
452              
453             =item *
454              
455             Ricardo Signes <rjbs@semiotic.systems>
456              
457             =back
458              
459             =head1 COPYRIGHT AND LICENSE
460              
461             This software is copyright (c) 2022 by Ricardo SIGNES.
462              
463             This is free software; you can redistribute it and/or modify it under
464             the same terms as the Perl 5 programming language system itself.
465              
466             =cut