File Coverage

blib/lib/Dist/Zilla/Plugin/Readme/Brief.pm
Criterion Covered Total %
statement 115 134 85.8
branch 15 26 57.6
condition n/a
subroutine 26 29 89.6
pod 0 1 0.0
total 156 190 82.1


line stmt bran cond sub pod time code
1 7     7   12797601 use 5.010; # m regexp propagation
  7         20  
2 7     7   26 use strict;
  7         12  
  7         157  
3 7     7   35 use warnings;
  7         8  
  7         497  
4              
5             package Dist::Zilla::Plugin::Readme::Brief;
6              
7             our $VERSION = '0.003002';
8              
9             # ABSTRACT: Provide a short simple README with just the essentials
10              
11             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
12              
13 7     7   522 use Moose qw( with has around );
  7         289611  
  7         56  
14 7     7   26801 use List::Util qw( first );
  7         12  
  7         477  
15 7     7   527 use MooseX::Types::Moose qw( ArrayRef Str );
  7         38332  
  7         71  
16 7     7   22417 use Moose::Util::TypeConstraints qw( enum );
  7         8  
  7         64  
17 7     7   5333 use PPIx::DocumentName;
  7         12241  
  7         1795  
18              
19             with 'Dist::Zilla::Role::PPI';
20             with 'Dist::Zilla::Role::FileGatherer';
21              
22             my %installers = (
23             'eumm' => '_install_eumm',
24             'mb' => '_install_mb',
25             );
26              
27              
28              
29              
30              
31              
32              
33              
34              
35              
36              
37              
38             has _source_file_override => (
39             isa => Str,
40             is => 'ro',
41             init_arg => 'source_file',
42             predicate => '_has_source_file_override',
43             );
44              
45             has source_file => (
46             is => 'ro',
47             isa => 'Dist::Zilla::Role::File',
48             lazy => 1,
49             init_arg => undef,
50             default => sub {
51             my ($self) = @_;
52             my $file =
53             $self->_has_source_file_override
54             ? first { $_->name eq $self->_source_file_override } @{ $self->zilla->files }
55             : do {
56             my $main_module = $self->zilla->main_module;
57             my $alt = $main_module->name;
58             my $pod = ( $alt =~ s/\.pm\z/.pod/ ) && first { $_->name eq $alt } @{ $self->zilla->files };
59             $pod or $main_module;
60             };
61             $self->log_fatal('Unable to find source_file in the distribution') if not $file;
62             $self->log_debug( 'Using POD from ' . $file->name ) unless $self->_has_source_file_override;
63             return $file;
64             },
65             );
66              
67              
68              
69              
70              
71              
72              
73              
74              
75              
76              
77              
78              
79              
80              
81              
82              
83              
84              
85              
86              
87              
88              
89              
90              
91             has 'installer' => (
92             isa => ArrayRef [ enum( [ keys %installers ] ) ],
93             is => 'ro',
94             traits => ['Array'],
95             predicate => 'has_installer',
96             handles => {
97             '_installers' => 'elements',
98             },
99             );
100              
101 7     7   39 no Moose::Util::TypeConstraints;
  7         10  
  7         51  
102              
103              
104              
105              
106              
107              
108              
109              
110              
111              
112              
113             has 'description_label' => (
114             isa => Str,
115             is => 'ro',
116             lazy => 1,
117             default => sub { 'DESCRIPTION' },
118             );
119              
120             around 'mvp_multivalue_args' => sub {
121             my ( $orig, $self, @rest ) = @_;
122             return ( $self->$orig(@rest), 'installer' );
123             };
124              
125             around 'mvp_aliases' => sub {
126             my ( $orig, $self, @rest ) = @_;
127             return { %{ $self->$orig(@rest) }, installers => 'installer' };
128             };
129              
130             around dump_config => sub {
131             my ( $orig, $self, @args ) = @_;
132             my $config = $self->$orig(@args);
133             my $localconf = {};
134              
135             for my $attrname (qw( installer source_file _source_file_override description_label )) {
136             if ( $self->meta->find_attribute_by_name($attrname)->has_value($self)) {
137             $localconf->{ $attrname } = $self->can($attrname)->();
138             }
139             }
140              
141             $localconf->{ q[$] . __PACKAGE__ . '::VERSION' } = $VERSION unless __PACKAGE__ eq ref $self;
142             $config->{ +__PACKAGE__ } = $localconf if keys %{$localconf};
143             return $config;
144             };
145              
146             __PACKAGE__->meta->make_immutable;
147 7     7   2150 no Moose;
  7         10  
  7         39  
148              
149              
150              
151              
152              
153             sub gather_files {
154 6     6 0 282614 my ($self) = @_;
155 6         3187 require Dist::Zilla::File::FromCode;
156             $self->add_file(
157             Dist::Zilla::File::FromCode->new(
158             name => 'README',
159             code => sub {
160 6     6   173175 return $self->_generate_content;
161             },
162 6         285983 ),
163             );
164 6         4541 return;
165             }
166              
167             # Internal Methods
168              
169             sub _generate_content {
170 6     6   15 my ($self) = @_;
171              
172             # each section should end with exactly one trailing newline
173 6         23 return join qq[\n], $self->_description_section, $self->_installer_section, $self->_copyright_section;
174             }
175              
176             sub _description_section {
177 6     6   12 my ($self) = @_;
178 6         33 return $self->_heading . qq[\n\n] . $self->_description . qq[\n];
179             }
180              
181             sub _installer_section {
182 6     6   11 my ($self) = @_;
183 6         14 my $out = q[];
184 6         12 $out .= qq[INSTALLATION\n\n];
185 6         22 $out .= $self->_install_auto;
186              
187 6 50       237 my $manual_instructions = ( $self->has_installer ) ? $self->_configured_installer : $self->_auto_installer;
188              
189 6 50       20 if ( defined $manual_instructions ) {
190 0         0 $out .= "Should you wish to install this module manually, the procedure is\n\n";
191 0         0 $out .= $manual_instructions;
192             }
193             else {
194 6         33 $self->log('No install method detected. Omitting Manual Installation Instructions');
195             }
196 6         1382 return $out;
197             }
198              
199             sub _copyright_section {
200 6     6   13 my ($self) = @_;
201 6 50       21 if ( my $copy = $self->_copyright_from_pod ) {
202 0         0 return $copy . qq[\n];
203             }
204 6         21 return $self->_copyright_from_dist;
205             }
206              
207             sub _auto_installer {
208 6     6   61 my ($self) = @_;
209 6         43 $self->log_debug('Autodetecting installer');
210 6 50   20   2255 if ( first { $_->name =~ /\AMakefile.PL\z/msx } @{ $self->zilla->files } ) {
  20 50       797  
  6         336  
211 0         0 return $self->_install_eumm;
212             }
213 20     20   530 elsif ( first { $_->name =~ /\ABuild.PL\z/msx } @{ $self->zilla->files } ) {
  6         430  
214 0         0 return $self->_install_mb;
215             }
216 6         194 return;
217             }
218              
219             sub _configured_installer {
220 0     0   0 my ($self) = @_;
221 0         0 $self->log_debug('Using configured installer');
222              
223 0         0 my @sections;
224 0         0 for my $installer ( $self->_installers ) {
225 0         0 my $method = $installers{$installer};
226 0         0 push @sections, $self->$method();
227             }
228 0 0       0 return unless @sections;
229 0         0 return join qq[\nor\n\n], @sections;
230             }
231              
232             sub _source_pod {
233 12     12   17 my ($self) = @_;
234 12 100       55 return $self->{_pod_cache} if exists $self->{_pod_cache};
235 6         211 my $chars = $self->source_file->content;
236              
237 6         351 require Encode;
238 6         3029 require Pod::Elemental;
239 6         2252020 require Pod::Elemental::Transformer::Pod5;
240 6         3621 require Pod::Elemental::Transformer::Nester;
241 6         459698 require Pod::Elemental::Selectors;
242              
243 6         61 my $octets = Encode::encode( 'UTF-8', $chars, Encode::FB_CROAK() );
244 6         367 my $document = Pod::Elemental->read_string($octets);
245 6         22579 Pod::Elemental::Transformer::Pod5->new->transform_node($document);
246              
247 6         12472 my $nester = Pod::Elemental::Transformer::Nester->new(
248             {
249             top_selector => Pod::Elemental::Selectors::s_command('head1'),
250             content_selectors =>
251             [ Pod::Elemental::Selectors::s_flat(), Pod::Elemental::Selectors::s_command( [qw(head2 head3 head4 over item back)] ), ],
252             },
253             );
254 6         524 $nester->transform_node($document);
255              
256 6         9391 $self->{_pod_cache} = $document;
257 6         166 return $document;
258             }
259              
260             sub _podtext_nodes {
261 6     6   52 my ( undef, @nodes ) = @_;
262 6         3744 require Pod::Text;
263 6         154287 my $parser = Pod::Text->new( loose => 1 );
264 6         1136 $parser->output_string( \( my $text ) );
265 6         5512 $parser->parse_string_document( join qq[\n], '=pod', q[], map { $_->as_pod_string } @nodes );
  12         531  
266              
267             # strip extra indent;
268 6         4723 $text =~ s{^[ ]{4}}{}msxg;
269 6         27 $text =~ s{\n+\z}{}msx;
270 6         152 return $text;
271             }
272              
273             sub _heading {
274 6     6   15 my ($self) = @_;
275 6         3060 require PPI::Document; # Historic version of dzil doesn't load PPI on its own...
276 6         526920 my $document = $self->ppi_document_for_file( $self->source_file );
277 6         13168 return PPIx::DocumentName->extract($document);
278             }
279              
280             sub _description {
281 6     6   1949 my ($self) = @_;
282 6         26 my $pod = $self->_source_pod;
283 6         85 my (@nodes) = @{ $pod->children };
  6         122  
284              
285 6         30 my @found;
286              
287 6         39 require Pod::Elemental::Selectors;
288              
289 6         18 for my $node_number ( 0 .. $#nodes ) {
290 12 100       310 next unless Pod::Elemental::Selectors::s_command( head1 => $nodes[$node_number] );
291 6 50       678 next unless uc $self->description_label eq uc $nodes[$node_number]->content;
292 6         93 push @found, $nodes[$node_number];
293             }
294 6 50       21 if ( not @found ) {
295 0         0 $self->log( $self->description_label . ' not found in ' . $self->source_file->name );
296 0         0 return q[];
297             }
298 6         14 return $self->_podtext_nodes( map { @{ $_->children } } @found );
  6         7  
  6         168  
299             }
300              
301             sub _copyright_from_dist {
302              
303             # Construct a copyright even if the POD doesn't have one
304 6     6   10 my ($self) = @_;
305 6         195 my $notice = $self->zilla->license->notice;
306 6         10252 return qq[COPYRIGHT AND LICENSE\n\n$notice];
307             }
308              
309             sub _copyright_from_pod {
310 6     6   10 my ($self) = @_;
311 6         21 my $pod = $self->_source_pod;
312 6         9 my (@nodes) = @{ $pod->children };
  6         179  
313              
314 6         42 my @found;
315              
316 6         35 require Pod::Elemental::Selectors;
317              
318 6         22 for my $node_number ( 0 .. $#nodes ) {
319 12 100       562 next unless Pod::Elemental::Selectors::s_command( head1 => $nodes[$node_number] );
320 6 50       652 next unless $nodes[$node_number]->content =~ /COPYRIGHT|LICENSE/imsx;
321 0         0 push @found, $nodes[$node_number];
322             }
323 6 50       79 if ( not @found ) {
324 6         180 $self->log( 'COPYRIGHT/LICENSE not found in ' . $self->source_file->name );
325 6         1459 return;
326             }
327 0         0 return $self->_podtext_nodes(@found);
328             }
329              
330             sub _install_auto {
331 6     6   16 return <<"EOFAUTO";
332             This is a Perl module distribution. It should be installed with whichever
333             tool you use to manage your installation of Perl, e.g. any of
334              
335             cpanm .
336             cpan .
337             cpanp -i .
338              
339             Consult http://www.cpan.org/modules/INSTALL.html for further instruction.
340             EOFAUTO
341             }
342              
343             sub _install_eumm {
344 0     0     return <<"EOFEUMM";
345             perl Makefile.PL
346             make
347             make test
348             make install
349             EOFEUMM
350             }
351              
352             sub _install_mb {
353 0     0     return <<"EOFMB";
354             perl Build.PL
355             ./Build
356             ./Build test
357             ./Build install
358             EOFMB
359             }
360              
361             1;
362              
363             __END__
364              
365             =pod
366              
367             =encoding UTF-8
368              
369             =head1 NAME
370              
371             Dist::Zilla::Plugin::Readme::Brief - Provide a short simple README with just the essentials
372              
373             =head1 VERSION
374              
375             version 0.003002
376              
377             =head1 SYNOPSIS
378              
379             [Readme::Brief]
380             ; Override autodetected install method
381             installer = eumm
382             ; Override autodetected main_module or main_module.pod as a source
383             source_file = lib/Path/To/Module.pm
384             ; Override name to use for brief body
385             description_label = WHAT IS THIS
386              
387             =head1 DESCRIPTION
388              
389             This provides a terse but informative README file for your CPAN distribution
390             that contains just the essential details about your dist a casual consumer would want to know.
391              
392             =over 4
393              
394             =item * The name of the primary module in the distribution
395              
396             =item * The distribution's main modules description
397              
398             =item * Simple installation instructions from an extracted archive
399              
400             =item * Short copyright information
401              
402             =back
403              
404             =head1 NOTE
405              
406             This is still reasonably fresh code and reasonably experimental, and feature enhancements and bug fixes
407             are actively desired.
408              
409             However, bugs are highly likely to be encountered, especially as there are no tests.
410              
411             =head1 MECHANICS
412              
413             =over 4
414              
415             =item * Heading is derived from the C<package> statement in the C<source_file>
416              
417             =item * Description is extracted as the entire C<H1Nest> of the section titled C<DESCRIPTION> ( or whatever C<description_label> is ) in the C<source_file>
418              
419             =item * Installation instructions are automatically determined by the presence of either
420              
421             =over 2
422              
423             =item * A C<Makefile.PL> file in your dist ( Where it assumes C<EUMM> style )
424              
425             =item * A C<Build.PL> file in your dist ( where it assumes C<Module::Build> style )
426              
427             =item * In the case of both, only instructions for C<Makefile.PL> will be emitted.
428              
429             =item * All of the above behavior can be overridden using the L<< C<installer>|/installer >> attribute.
430              
431             =back
432              
433             =item * I<ALL> Copyright and license details are extracted from the C<source_file> in any C<H1Nest> that has either C<COPYRIGHT> or C<LICENSE> in the heading.
434              
435             =item * Or failing such a section, a C<COPYRIGHT AND LICENSE> section will be derived from C<< zilla->license >>
436              
437             =back
438              
439             =head1 ATTRIBUTES
440              
441             =head2 source_file
442              
443             Determines the file that will be parsed for POD to populate the README from.
444              
445             By default, it uses your C<main_module>, except if you have a C<.pod> file with
446             the same basename and path as your C<main_module>, in which case it uses that.
447              
448             This parameter and associated C<.pod> support is new in C<v0.003000>
449              
450             =head2 installer
451              
452             Determines what installers to document in the C<INSTALLATION> section.
453              
454             By default, that section is determined based on the presence of certain
455             files in your C<dist>.
456              
457             However, in the event you have multiple installers supported, manually specifying
458             this attribute allows you to control which, or all, and the order.
459              
460             installer = eumm ; # only eumm
461              
462             installer = eumm
463             installer = mb ; EUMM shown first, MB shown second
464              
465             installer = mb
466             installer = eumm ; EUMM shown second, MB shown first
467              
468             The verbiage however has not yet been cleaned up such that having both is completely lucid.
469              
470             This parameter was introduced in version C<v0.002000>
471              
472             =head2 description_label
473              
474             This case-insensitive attribute defines what C<=head1> node will be used for the description section of the brief.
475              
476             By default, this is C<DESCRIPTION>.
477              
478             This parameter was introduced in version C<v0.003000>
479              
480             =for Pod::Coverage gather_files
481              
482             =head1 SEE ALSO
483              
484             Here are some competing modules and how this module differs from them.
485              
486             =over 4
487              
488             =item * L<< C<[Readme]>|Dist::Zilla::Plugin::Readme >>
489              
490             Gives a much briefer more generic C<README> file, which lacks quite as much readable content,
491             and contains no installation instructions.
492              
493             =item * L<< C<[ReadmeFromPod]>|Dist::Zilla::Plugin::ReadmeFromPod >>
494              
495             Provides various output formats, but ultimately is a transformer of your C<source_file>'s C<POD>,
496             which is excessive for some peoples tastes. ( And lacks install instructions )
497              
498             =item * L<< C<[ReadmeAnyFromPod]>|Dist::Zilla::Plugin::ReadmeAnyFromPod >>
499              
500             Based on the above provides a bunch of extra features, but is ultimately limited
501             in similar ways with regards to install details and verbosity.
502              
503             =item * L<< C<[Pod2Readme]>|Dist::Zilla::Plugin::Pod2Readme >>
504              
505             Possibly the most straight forward C<POD> → C<README> translator, but limited like the above
506             in that it is I<only> a C<POD> translator, but lacks the install instructions aspect.
507              
508             =item * L<< C<[InstallGuide]>|Dist::Zilla::Plugin::InstallGuide >>
509              
510             The polar opposite approach that only focuses on elaborate installation instructions in C<INSTALL>,
511             but lacks any of the C<POD> and C<COPYRIGHT> elements.
512              
513             =back
514              
515             =head1 AUTHOR
516              
517             Kent Fredric <kentnl@cpan.org>
518              
519             =head1 COPYRIGHT AND LICENSE
520              
521             This software is copyright (c) 2017 by Kent Fredric <kentfredric@gmail.com>.
522              
523             This is free software; you can redistribute it and/or modify it under
524             the same terms as the Perl 5 programming language system itself.
525              
526             =cut