File Coverage

blib/lib/Dist/Zilla/Plugin/ReadmeAnyFromPod.pm
Criterion Covered Total %
statement 120 123 97.5
branch 32 38 84.2
condition 15 15 100.0
subroutine 25 26 96.1
pod 7 8 87.5
total 199 210 94.7


line stmt bran cond sub pod time code
1 12     12   17542206 use strict;
  12         17  
  12         306  
2 12     12   40 use warnings;
  12         13  
  12         574  
3              
4             package Dist::Zilla::Plugin::ReadmeAnyFromPod;
5             # ABSTRACT: Automatically convert POD to a README in any format for Dist::Zilla
6             $Dist::Zilla::Plugin::ReadmeAnyFromPod::VERSION = '0.161150';
7 12     12   45 use List::Util 1.33 qw( none first );
  12         179  
  12         712  
8 12     12   590 use Moose::Util::TypeConstraints qw(enum);
  12         198460  
  12         101  
9 12     12   4205 use Moose;
  12         100577  
  12         61  
10 12     12   55798 use MooseX::Has::Sugar;
  12         5712  
  12         45  
11 12     12   1609 use Path::Tiny 0.004;
  12         7173  
  12         475  
12 12     12   49 use Scalar::Util 'blessed';
  12         16  
  12         16368  
13              
14             with 'Dist::Zilla::Role::AfterBuild',
15             'Dist::Zilla::Role::AfterRelease',
16             'Dist::Zilla::Role::FileGatherer',
17             'Dist::Zilla::Role::FileMunger',
18             'Dist::Zilla::Role::FilePruner',
19             'Dist::Zilla::Role::FileWatcher',
20             'Dist::Zilla::Role::PPI',
21             ;
22              
23             # TODO: Should these be separate modules?
24             our $_types = {
25             pod => {
26             filename => 'README.pod',
27             parser => sub {
28             return $_[0];
29             },
30             },
31             text => {
32             filename => 'README',
33             parser => sub {
34             my $pod = $_[0];
35              
36             require Pod::Simple::Text;
37             Pod::Simple::Text->VERSION('3.23');
38             my $parser = Pod::Simple::Text->new;
39             $parser->output_string( \my $content );
40             $parser->parse_characters(1);
41             $parser->parse_string_document($pod);
42             return $content;
43             },
44             },
45             markdown => {
46             filename => 'README.mkdn',
47             parser => sub {
48             my $pod = $_[0];
49              
50             require Pod::Markdown;
51             Pod::Markdown->VERSION('2.000');
52             my $parser = Pod::Markdown->new();
53             $parser->output_string( \my $content );
54             $parser->parse_characters(1);
55             $parser->parse_string_document($pod);
56             return $content;
57             },
58             },
59             html => {
60             filename => 'README.html',
61             parser => sub {
62             my $pod = $_[0];
63              
64             require Pod::Simple::HTML;
65             Pod::Simple::HTML->VERSION('3.23');
66             my $parser = Pod::Simple::HTML->new;
67             $parser->output_string( \my $content );
68             $parser->parse_characters(1);
69             $parser->parse_string_document($pod);
70             return $content;
71             }
72             }
73             };
74              
75              
76             has type => (
77             ro, lazy,
78             isa => enum([keys %$_types]),
79             default => sub { $_[0]->__from_name()->[0] || 'text' },
80             );
81              
82              
83             has filename => (
84             ro, lazy,
85             isa => 'Str',
86             default => sub { $_types->{$_[0]->type}->{filename}; }
87             );
88              
89              
90             has source_filename => (
91             ro, lazy,
92             isa => 'Str',
93             builder => '_build_source_filename',
94             );
95              
96             sub _build_source_filename {
97 39     39   51 my $self = shift;
98 39         881 my $pm = $self->zilla->main_module->name;
99 39         21981 (my $pod = $pm) =~ s/\.pm$/\.pod/;
100 39 50       1927 return -e $pod ? $pod : $pm;
101             }
102              
103              
104             has location => (
105             ro, lazy,
106             isa => enum([qw(build root)]),
107             default => sub { $_[0]->__from_name()->[1] || 'build' },
108             );
109              
110              
111             has phase => (
112             ro, lazy,
113             isa => enum([qw(build release)]),
114             default => 'build',
115             );
116              
117              
118             sub BUILD {
119 42     42 0 44 my $self = shift;
120              
121 42 100 100     1498 $self->log_fatal('You cannot use location=build with phase=release!')
122             if $self->location eq 'build' and $self->phase eq 'release';
123              
124 41 100 100     1225 $self->log('You are creating a .pod directly in the build - be aware that this will be installed like a .pm file and as a manpage')
125             if $self->location eq 'build' and $self->type eq 'pod';
126             }
127              
128              
129             sub gather_files {
130 41     41 1 703990 my ($self) = @_;
131              
132 41         1264 my $filename = $self->filename;
133 41 100 100     1231 if ( $self->location eq 'build'
134             # allow for the file to also exist in the dist
135 99     99   2905 and none { $_->name eq $filename } @{ $self->zilla->files }
  22         532  
136             ) {
137 21         4582 require Dist::Zilla::File::InMemory;
138 21         462349 my $file = Dist::Zilla::File::InMemory->new({
139             content => 'this will be overwritten',
140             name => $self->filename,
141             });
142              
143 21         4809 $self->add_file($file);
144             }
145 41         6386 return;
146             }
147              
148              
149             sub prune_files {
150 41     41 1 15717 my ($self) = @_;
151              
152             # leave the file in the dist if another instance of us is adding it there.
153 41 100 100     1284 if ($self->location eq 'root'
154             and not grep {
155 289 100 100     3334 blessed($self) eq blessed($_)
156             and $_->location eq 'build'
157             and $_->filename eq $self->filename
158 19         428 } @{$self->zilla->plugins}) {
159 10         14 for my $file (@{ $self->zilla->files }) {
  10         225  
160 41 100       388 next unless $file->name eq $self->filename;
161 1         4 $self->log_debug([ 'pruning %s', $file->name ]);
162 1         193 $self->zilla->prune_file($file);
163             }
164             }
165 41         81 return;
166             }
167              
168              
169             sub munge_files {
170 41     41 1 18136 my $self = shift;
171              
172 41 100       1271 if ( $self->location eq 'build' ) {
173 22         656 my $filename = $self->filename;
174 22     118   85 my $file = first { $_->name eq $filename } @{ $self->zilla->files };
  118         3483  
  22         515  
175 22 100       734 if ($file) {
176 21         63 $self->munge_file($file);
177             }
178             else {
179 1         7 $self->log_fatal(
180             "Could not find a $filename file during the build"
181             . ' - did you prune it away with a PruneFiles block?' );
182             }
183             }
184 40         94 return;
185             }
186              
187              
188             my %watching;
189             sub munge_file {
190 21     21 1 31 my ($self, $target_file) = @_;
191              
192             # Ensure that we repeat the munging if the source file is modified
193             # after we run.
194 21         54 my $source_file = $self->_source_file();
195             $self->watch_file($source_file, sub {
196 0     0   0 my ($self, $watched_file) = @_;
197              
198             # recalculate the content based on the updates
199 0         0 $self->log('someone tried to munge ' . $watched_file->name . ' after we read from it. Making modifications again...');
200 0         0 $self->munge_file($target_file);
201 21 100       703 }) if not $watching{$source_file->name}++;
202              
203 21         275490 $self->log_debug([ 'ReadmeAnyFromPod updating contents of %s in dist', $target_file->name ]);
204 21         1822 $target_file->content($self->get_readme_content);
205 21         5626 return;
206             }
207              
208              
209             sub after_build {
210 40     40 1 388838 my $self = shift;
211 40 100       1372 $self->_create_readme if $self->phase eq 'build';
212             }
213              
214              
215             sub after_release {
216 1     1 1 79281 my $self = shift;
217 1 50       42 $self->_create_readme if $self->phase eq 'release';
218             }
219              
220             sub _create_readme {
221 40     40   62 my $self = shift;
222              
223 40 100       1206 if ( $self->location eq 'root' ) {
224 19         570 my $filename = $self->filename;
225 19         86 $self->log_debug([ 'ReadmeAnyFromPod updating contents of %s in root', $filename ]);
226              
227 19         945 my $content = $self->get_readme_content();
228              
229 19         611 my $destination_file = path($self->zilla->root)->child($filename);
230 19 100       1997 if (-e $destination_file) {
231 2         94 $self->log("overriding $filename in root");
232             }
233 19         1194 my $encoding = $self->_get_source_encoding();
234             $destination_file->spew_raw(
235             $encoding eq 'raw'
236             ? $content
237 19 50       128 : do { require Encode; Encode::encode($encoding, $content) }
  19         75  
  19         59  
238             );
239             }
240              
241 40         6173 return;
242             }
243              
244             sub _source_file {
245 120     120   126 my ($self) = shift;
246              
247 120         3999 my $filename = $self->source_filename;
248 120     486   365 first { $_->name eq $filename } @{ $self->zilla->files };
  486         14755  
  120         2907  
249             }
250              
251             # Holds the contents of the source file as of the last time we
252             # generated a readme from it. We use this to detect when the source
253             # file is modified so we can update the README file again.
254             has _last_source_content => (
255             is => 'rw', isa => 'Str',
256             default => '',
257             );
258              
259             sub _get_source_pod {
260 40     40   149 my ($self) = shift;
261              
262 40         76 my $source_file = $self->_source_file;
263              
264             # cache contents before we alter it, for later comparison
265 40         1318 $self->_last_source_content($source_file->content);
266              
267 40         4985 require PPI::Document; # for Dist::Zilla::Role::PPI < 5.009
268 40         773708 my $doc = $self->ppi_document_for_file($source_file);
269              
270 40         60569 my $pod_elems = $doc->find('PPI::Token::Pod');
271 40         43809 my $pod_content = "";
272 40 50       114 if ($pod_elems) {
273             # Concatenation should stringify it
274 40         155 $pod_content .= PPI::Token::Pod->merge(@$pod_elems);
275             }
276              
277 40 50       3211 if ((my $encoding = $self->_get_source_encoding) ne 'raw') {
278 40         398 require Encode;
279 40         133 $pod_content = Encode::decode($encoding, $pod_content);
280             }
281              
282 40         1745 return $pod_content;
283             }
284              
285             sub _get_source_encoding {
286 59     59   310 my ($self) = shift;
287 59         123 my $source_file = $self->_source_file;
288             return
289 59 50       3221 $source_file->can('encoding')
290             ? $source_file->encoding
291             : 'raw'; # Dist::Zilla pre-5.0
292             }
293              
294              
295             sub get_readme_content {
296 40     40 1 68 my ($self) = shift;
297 40         111 my $source_pod = $self->_get_source_pod();
298 40         9857 my $parser = $_types->{$self->type}->{parser};
299             # Save the POD text used to generate the README.
300 40         113 return $parser->($source_pod);
301             }
302              
303             {
304             my %cache;
305             sub __from_name {
306 65     65   76 my ($self) = @_;
307 65         1551 my $name = $self->plugin_name;
308              
309             # Use cached values if available
310 65 100       371 if ($cache{$name}) {
311 32         988 return $cache{$name};
312             }
313              
314             # qr{TYPE1|TYPE2|...}
315 33         105 my $type_regex = join('|', map {quotemeta} keys %$_types);
  132         198  
316             # qr{LOC1|LOC2|...}
317 33         63 my $location_regex = join('|', map {quotemeta} qw(build root));
  66         91  
318             # qr{(?:Readme)? (TYPE1|TYPE2|...) (?:In)? (LOC1|LOC2|...) }x
319 33         701 my $complete_regex = qr{ (?:Readme)? ($type_regex) (?:(?:In)? ($location_regex))? }ix;
320 33         918 my ($type, $location) = (lc $name) =~ m{(?:\A|/) \s* $complete_regex \s* \Z}ix;
321 33         107 $cache{$name} = [$type, $location];
322 33         1175 return $cache{$name};
323             }
324             }
325              
326             __PACKAGE__->meta->make_immutable;
327              
328             __END__
329              
330             =pod
331              
332             =head1 NAME
333              
334             Dist::Zilla::Plugin::ReadmeAnyFromPod - Automatically convert POD to a README in any format for Dist::Zilla
335              
336             =head1 VERSION
337              
338             version 0.161150
339              
340             =head1 SYNOPSIS
341              
342             In your F<dist.ini>
343              
344             [ReadmeAnyFromPod]
345             ; Default is plaintext README in build dir
346              
347             ; Using non-default options: POD format with custom filename in
348             ; dist root, outside of build. Including this README in version
349             ; control makes Github happy.
350             [ReadmeAnyFromPod / ReadmePodInRoot]
351             type = pod
352             filename = README.pod
353             location = root
354              
355             ; Using plugin name autodetection: Produces README.html in root
356             [ ReadmeAnyFromPod / HtmlInRoot ]
357              
358             =head1 DESCRIPTION
359              
360             Generates a README for your L<Dist::Zilla> powered dist from its
361             C<main_module> in any of several formats. The generated README can be
362             included in the build or created in the root of your dist for e.g.
363             inclusion into version control.
364              
365             =head2 PLUGIN NAME AUTODETECTION
366              
367             If you give the plugin an appropriate name (a string after the slash)
368             in your dist.ini, it will can parse the C<type> and C<location>
369             attributes from it. The format is "Readme[TYPE]In[LOCATION]". The
370             words "Readme" and "In" are optional, and the whole name is
371             case-insensitive. The SYNOPSIS section above gives one example.
372              
373             When run with C<location = dist>, this plugin runs in the C<FileMunger> phase
374             to create the new file. If it runs before another C<FileMunger> plugin does,
375             that happens to modify the input pod (like, say,
376             L<C<[PodWeaver]>|Dist::Zilla::Plugin::PodWeaver>), the README file contents
377             will be recalculated, along with a warning that you should modify your
378             F<dist.ini> by referencing C<[ReadmeAnyFromPod]> lower down in the file (the
379             build still works, but is less efficient).
380              
381             =head1 ATTRIBUTES
382              
383             =head2 type
384              
385             The file format for the readme. Supported types are "text",
386             "markdown", "pod", and "html". Note that you are not advised to
387             create a F<.pod> file in the dist itself, as L<ExtUtils::MakeMaker>
388             will install that, both into C<PERL5LIB> and C<MAN3DIR>.
389              
390             =head2 filename
391              
392             The file name of the README file to produce. The default depends on
393             the selected format.
394              
395             =head2 source_filename
396              
397             The file from which to extract POD for the content of the README. The
398             default is the file of the main module of the dist. If the main module
399             has a companion ".pod" file with the same basename, that is used as
400             the default instead.
401              
402             =head2 location
403              
404             Where to put the generated README file. Choices are:
405              
406             =over 4
407              
408             =item build
409              
410             This puts the README in the directory where the dist is currently
411             being built, where it will be incorporated into the dist.
412              
413             =item root
414              
415             This puts the README in the root directory (the same directory that
416             contains F<dist.ini>). The README will not be incorporated into the
417             built dist.
418              
419             =back
420              
421             If you want to generate the same README file in both the build
422             directory and the root directory, simply generate it in the build
423             directory and use the
424             L<C<[CopyFilesFromBuild]>|Dist::Zilla::Plugin::CopyFilesFromBuild>
425             plugin to copy it to the dist root.
426              
427             =head2 phase
428              
429             At what phase to generate the README file. Choices are:
430              
431             =over 4
432              
433             =item build
434              
435             (Default) This generates the README at 'after build' time. A new
436             README will be generated each time you build the dist.
437              
438             =item release
439              
440             This generates the README at 'after release' time. Note that this is
441             too late to get the file into the generated tarball, and is therefore
442             incompatible with C<location = build>. However, this is ideal if you
443             are using C<location = root> and only want to update the README upon
444             each release of your module.
445              
446             =back
447              
448             =head1 METHODS
449              
450             =head2 gather_files
451              
452             We create the file early, so other plugins that need to have the full list of
453             files are aware of what we will be generating.
454              
455             =head2 prune_files
456              
457             Files with C<location = root> must also be pruned, so that they don't
458             sneak into the I<next> build by virtue of already existing in the root
459             dir. (The alternative is that the user doesn't add them to the build in the
460             first place, with an option to their C<GatherDir> plugin.)
461              
462             =head2 munge_files
463              
464             =head2 munge_file
465              
466             Edits the content into the requested README file in the dist.
467              
468             =head2 after_build
469              
470             Create the requested README file at build time, if requested.
471              
472             =head2 after_release
473              
474             Create the requested README file at release time, if requested.
475              
476             =head2 get_readme_content
477              
478             Get the content of the README in the desired format.
479              
480             =for Pod::Coverage BUILD
481              
482             =head1 BUGS AND LIMITATIONS
483              
484             Please report any bugs or feature requests to
485             C<rct+perlbug@thompsonclan.org>.
486              
487             =head1 SEE ALSO
488              
489             =over 4
490              
491             =item *
492              
493             L<Dist::Zilla::Plugin::ReadmeFromPod> - The base for this module
494              
495             =item *
496              
497             L<Dist::Zilla::Plugin::ReadmeMarkdownFromPod> - Functionality subsumed by this module
498              
499             =item *
500              
501             L<Dist::Zilla::Plugin::CopyReadmeFromBuild> - Functionality partly subsumed by this module
502              
503             =back
504              
505             =head1 INSTALLATION
506              
507             See perlmodinstall for information and options on installing Perl modules.
508              
509             =head1 AUTHORS
510              
511             =over 4
512              
513             =item *
514              
515             Ryan C. Thompson <rct@thompsonclan.org>
516              
517             =item *
518              
519             Karen Etheridge <ether@cpan.org>
520              
521             =back
522              
523             =head1 COPYRIGHT AND LICENSE
524              
525             This software is copyright (c) 2016 by Ryan C. Thompson.
526              
527             This is free software; you can redistribute it and/or modify it under
528             the same terms as the Perl 5 programming language system itself.
529              
530             =head1 DISCLAIMER OF WARRANTY
531              
532             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
533             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT
534             WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER
535             PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND,
536             EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
537             IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
538             PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
539             SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME
540             THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.
541              
542             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
543             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
544             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE
545             TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR
546             CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
547             SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
548             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
549             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
550             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
551             DAMAGES.
552              
553             =cut