File Coverage

blib/lib/Dist/Zilla/Plugin/ReadmeAnyFromPod.pm
Criterion Covered Total %
statement 121 124 97.5
branch 32 38 84.2
condition 16 18 88.8
subroutine 25 26 96.1
pod 7 8 87.5
total 201 214 93.9


line stmt bran cond sub pod time code
1 12     12   17454047 use strict;
  12         15  
  12         291  
2 12     12   44 use warnings;
  12         12  
  12         537  
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.161170';
7 12     12   44 use List::Util 1.33 qw( none first );
  12         181  
  12         702  
8 12     12   736 use Moose::Util::TypeConstraints qw(enum);
  12         209405  
  12         100  
9 12     12   4400 use Moose;
  12         108828  
  12         61  
10 12     12   58319 use MooseX::Has::Sugar;
  12         5847  
  12         44  
11 12     12   2112 use Path::Tiny 0.004;
  12         9504  
  12         493  
12 12     12   52 use Scalar::Util 'blessed';
  12         11  
  12         16435  
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   48 my $self = shift;
98 39         874 my $pm = $self->zilla->main_module->name;
99 39         22376 (my $pod = $pm) =~ s/\.pm$/\.pod/;
100 39 50       1972 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 46 my $self = shift;
120              
121 42 100 100     1387 $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     1189 $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 707845 my ($self) = @_;
131              
132 41         1283 my $filename = $self->filename;
133 41 100 100     1257 if ( $self->location eq 'build'
134             # allow for the file to also exist in the dist
135 99     99   2927 and none { $_->name eq $filename } @{ $self->zilla->files }
  22         512  
136             ) {
137 21         5277 require Dist::Zilla::File::InMemory;
138 21         487461 my $file = Dist::Zilla::File::InMemory->new({
139             content => 'this will be overwritten',
140             name => $self->filename,
141             });
142              
143 21         4863 $self->add_file($file);
144             }
145 41         6554 return;
146             }
147              
148              
149             sub prune_files {
150 41     41 1 15459 my ($self) = @_;
151              
152             # leave the file in the dist if another instance of us is adding it there.
153 41 100 100     1283 if ($self->location eq 'root'
154             and not grep {
155 289 100 100     3322 blessed($self) eq blessed($_)
156             and $_->location eq 'build'
157             and $_->filename eq $self->filename
158 19         421 } @{$self->zilla->plugins}) {
159 10         12 for my $file (@{ $self->zilla->files }) {
  10         221  
160 41 100       390 next unless $file->name eq $self->filename;
161 1         3 $self->log_debug([ 'pruning %s', $file->name ]);
162 1         95 $self->zilla->prune_file($file);
163             }
164             }
165 41         80 return;
166             }
167              
168              
169             sub munge_files {
170 41     41 1 18211 my $self = shift;
171              
172 41 100       1282 if ( $self->location eq 'build' ) {
173 22         657 my $filename = $self->filename;
174 22     118   84 my $file = first { $_->name eq $filename } @{ $self->zilla->files };
  118         3446  
  22         501  
175 22 100       767 if ($file) {
176 21         58 $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         108 return;
185             }
186              
187              
188             my %watching;
189             sub munge_file {
190 21     21 1 33 my ($self, $target_file) = @_;
191              
192             # Ensure that we repeat the munging if the source file is modified
193             # after we run.
194 21         56 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       672 }) if not $watching{$source_file->name}++;
202              
203 21         279026 $self->log_debug([ 'ReadmeAnyFromPod updating contents of %s in dist', $target_file->name ]);
204 21         1928 $target_file->content($self->get_readme_content);
205 21         5577 return;
206             }
207              
208              
209             sub after_build {
210 40     40 1 396399 my $self = shift;
211 40 100       1362 $self->_create_readme if $self->phase eq 'build';
212             }
213              
214              
215             sub after_release {
216 1     1 1 81291 my $self = shift;
217 1 50       40 $self->_create_readme if $self->phase eq 'release';
218             }
219              
220             sub _create_readme {
221 40     40   52 my $self = shift;
222              
223 40 100       1205 if ( $self->location eq 'root' ) {
224 19         577 my $filename = $self->filename;
225 19         100 $self->log_debug([ 'ReadmeAnyFromPod updating contents of %s in root', $filename ]);
226              
227 19         970 my $content = $self->get_readme_content();
228              
229 19         600 my $destination_file = path($self->zilla->root)->child($filename);
230 19 100       1949 if (-e $destination_file) {
231 2         60 $self->log("overriding $filename in root");
232             }
233 19         1085 my $encoding = $self->_get_source_encoding();
234             $destination_file->spew_raw(
235             $encoding eq 'raw'
236             ? $content
237 19 50       125 : do { require Encode; Encode::encode($encoding, $content) }
  19         89  
  19         64  
238             );
239             }
240              
241 40         6116 return;
242             }
243              
244             sub _source_file {
245 120     120   139 my ($self) = shift;
246              
247 120         3994 my $filename = $self->source_filename;
248 120     486   382 first { $_->name eq $filename } @{ $self->zilla->files };
  486         14533  
  120         2806  
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   164 my ($self) = shift;
261              
262 40         83 my $source_file = $self->_source_file;
263              
264             # cache contents before we alter it, for later comparison
265 40         1309 $self->_last_source_content($source_file->content);
266              
267 40         5606 require PPI::Document; # for Dist::Zilla::Role::PPI < 5.009
268 40         825167 my $doc = $self->ppi_document_for_file($source_file);
269              
270 40         61760 my $pod_elems = $doc->find('PPI::Token::Pod');
271 40         43331 my $pod_content = "";
272 40 50       122 if ($pod_elems) {
273             # Concatenation should stringify it
274 40         196 $pod_content .= PPI::Token::Pod->merge(@$pod_elems);
275             }
276              
277 40 50 33     3291 if ((my $encoding = $self->_get_source_encoding) ne 'raw'
278 40         1110 and not eval { Dist::Zilla::Role::PPI->VERSION('6.003') }
279             ) {
280             # older Dist::Zilla::Role::PPI passes encoded content to PPI
281 40         198 require Encode;
282 40         135 $pod_content = Encode::decode($encoding, $pod_content);
283             }
284              
285 40         1913 return $pod_content;
286             }
287              
288             sub _get_source_encoding {
289 59     59   323 my ($self) = shift;
290 59         123 my $source_file = $self->_source_file;
291             return
292 59 50       3173 $source_file->can('encoding')
293             ? $source_file->encoding
294             : 'raw'; # Dist::Zilla pre-5.0
295             }
296              
297              
298             sub get_readme_content {
299 40     40 1 66 my ($self) = shift;
300 40         112 my $source_pod = $self->_get_source_pod();
301 40         9855 my $parser = $_types->{$self->type}->{parser};
302             # Save the POD text used to generate the README.
303 40         117 return $parser->($source_pod);
304             }
305              
306             {
307             my %cache;
308             sub __from_name {
309 65     65   73 my ($self) = @_;
310 65         1515 my $name = $self->plugin_name;
311              
312             # Use cached values if available
313 65 100       361 if ($cache{$name}) {
314 32         964 return $cache{$name};
315             }
316              
317             # qr{TYPE1|TYPE2|...}
318 33         105 my $type_regex = join('|', map {quotemeta} keys %$_types);
  132         223  
319             # qr{LOC1|LOC2|...}
320 33         60 my $location_regex = join('|', map {quotemeta} qw(build root));
  66         96  
321             # qr{(?:Readme)? (TYPE1|TYPE2|...) (?:In)? (LOC1|LOC2|...) }x
322 33         729 my $complete_regex = qr{ (?:Readme)? ($type_regex) (?:(?:In)? ($location_regex))? }ix;
323 33         885 my ($type, $location) = (lc $name) =~ m{(?:\A|/) \s* $complete_regex \s* \Z}ix;
324 33         138 $cache{$name} = [$type, $location];
325 33         1139 return $cache{$name};
326             }
327             }
328              
329             __PACKAGE__->meta->make_immutable;
330              
331             __END__
332              
333             =pod
334              
335             =head1 NAME
336              
337             Dist::Zilla::Plugin::ReadmeAnyFromPod - Automatically convert POD to a README in any format for Dist::Zilla
338              
339             =head1 VERSION
340              
341             version 0.161170
342              
343             =head1 SYNOPSIS
344              
345             In your F<dist.ini>
346              
347             [ReadmeAnyFromPod]
348             ; Default is plaintext README in build dir
349              
350             ; Using non-default options: POD format with custom filename in
351             ; dist root, outside of build. Including this README in version
352             ; control makes Github happy.
353             [ReadmeAnyFromPod / ReadmePodInRoot]
354             type = pod
355             filename = README.pod
356             location = root
357              
358             ; Using plugin name autodetection: Produces README.html in root
359             [ ReadmeAnyFromPod / HtmlInRoot ]
360              
361             =head1 DESCRIPTION
362              
363             Generates a README for your L<Dist::Zilla> powered dist from its
364             C<main_module> in any of several formats. The generated README can be
365             included in the build or created in the root of your dist for e.g.
366             inclusion into version control.
367              
368             =head2 PLUGIN NAME AUTODETECTION
369              
370             If you give the plugin an appropriate name (a string after the slash)
371             in your dist.ini, it will can parse the C<type> and C<location>
372             attributes from it. The format is "Readme[TYPE]In[LOCATION]". The
373             words "Readme" and "In" are optional, and the whole name is
374             case-insensitive. The SYNOPSIS section above gives one example.
375              
376             When run with C<location = dist>, this plugin runs in the C<FileMunger> phase
377             to create the new file. If it runs before another C<FileMunger> plugin does,
378             that happens to modify the input pod (like, say,
379             L<C<[PodWeaver]>|Dist::Zilla::Plugin::PodWeaver>), the README file contents
380             will be recalculated, along with a warning that you should modify your
381             F<dist.ini> by referencing C<[ReadmeAnyFromPod]> lower down in the file (the
382             build still works, but is less efficient).
383              
384             =head1 ATTRIBUTES
385              
386             =head2 type
387              
388             The file format for the readme. Supported types are "text",
389             "markdown", "pod", and "html". Note that you are not advised to
390             create a F<.pod> file in the dist itself, as L<ExtUtils::MakeMaker>
391             will install that, both into C<PERL5LIB> and C<MAN3DIR>.
392              
393             =head2 filename
394              
395             The file name of the README file to produce. The default depends on
396             the selected format.
397              
398             =head2 source_filename
399              
400             The file from which to extract POD for the content of the README. The
401             default is the file of the main module of the dist. If the main module
402             has a companion ".pod" file with the same basename, that is used as
403             the default instead.
404              
405             =head2 location
406              
407             Where to put the generated README file. Choices are:
408              
409             =over 4
410              
411             =item build
412              
413             This puts the README in the directory where the dist is currently
414             being built, where it will be incorporated into the dist.
415              
416             =item root
417              
418             This puts the README in the root directory (the same directory that
419             contains F<dist.ini>). The README will not be incorporated into the
420             built dist.
421              
422             =back
423              
424             If you want to generate the same README file in both the build
425             directory and the root directory, simply generate it in the build
426             directory and use the
427             L<C<[CopyFilesFromBuild]>|Dist::Zilla::Plugin::CopyFilesFromBuild>
428             plugin to copy it to the dist root.
429              
430             =head2 phase
431              
432             At what phase to generate the README file. Choices are:
433              
434             =over 4
435              
436             =item build
437              
438             (Default) This generates the README at 'after build' time. A new
439             README will be generated each time you build the dist.
440              
441             =item release
442              
443             This generates the README at 'after release' time. Note that this is
444             too late to get the file into the generated tarball, and is therefore
445             incompatible with C<location = build>. However, this is ideal if you
446             are using C<location = root> and only want to update the README upon
447             each release of your module.
448              
449             =back
450              
451             =head1 METHODS
452              
453             =head2 gather_files
454              
455             We create the file early, so other plugins that need to have the full list of
456             files are aware of what we will be generating.
457              
458             =head2 prune_files
459              
460             Files with C<location = root> must also be pruned, so that they don't
461             sneak into the I<next> build by virtue of already existing in the root
462             dir. (The alternative is that the user doesn't add them to the build in the
463             first place, with an option to their C<GatherDir> plugin.)
464              
465             =head2 munge_files
466              
467             =head2 munge_file
468              
469             Edits the content into the requested README file in the dist.
470              
471             =head2 after_build
472              
473             Create the requested README file at build time, if requested.
474              
475             =head2 after_release
476              
477             Create the requested README file at release time, if requested.
478              
479             =head2 get_readme_content
480              
481             Get the content of the README in the desired format.
482              
483             =for Pod::Coverage BUILD
484              
485             =head1 BUGS AND LIMITATIONS
486              
487             Please report any bugs or feature requests to
488             C<rct+perlbug@thompsonclan.org>.
489              
490             =head1 SEE ALSO
491              
492             =over 4
493              
494             =item *
495              
496             L<Dist::Zilla::Plugin::ReadmeFromPod> - The base for this module
497              
498             =item *
499              
500             L<Dist::Zilla::Plugin::ReadmeMarkdownFromPod> - Functionality subsumed by this module
501              
502             =item *
503              
504             L<Dist::Zilla::Plugin::CopyReadmeFromBuild> - Functionality partly subsumed by this module
505              
506             =back
507              
508             =head1 INSTALLATION
509              
510             See perlmodinstall for information and options on installing Perl modules.
511              
512             =head1 AUTHORS
513              
514             =over 4
515              
516             =item *
517              
518             Ryan C. Thompson <rct@thompsonclan.org>
519              
520             =item *
521              
522             Karen Etheridge <ether@cpan.org>
523              
524             =back
525              
526             =head1 COPYRIGHT AND LICENSE
527              
528             This software is copyright (c) 2016 by Ryan C. Thompson.
529              
530             This is free software; you can redistribute it and/or modify it under
531             the same terms as the Perl 5 programming language system itself.
532              
533             =head1 DISCLAIMER OF WARRANTY
534              
535             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
536             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT
537             WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER
538             PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND,
539             EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
540             IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
541             PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
542             SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME
543             THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.
544              
545             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
546             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
547             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE
548             TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR
549             CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
550             SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
551             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
552             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
553             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
554             DAMAGES.
555              
556             =cut