File Coverage

blib/lib/Dist/Zilla/Plugin/Readme/Brief.pm
Criterion Covered Total %
statement 118 137 86.1
branch 15 26 57.6
condition n/a
subroutine 27 30 90.0
pod 0 1 0.0
total 160 194 82.4


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