File Coverage

blib/lib/Dist/Zilla/Plugin/Manifest/Read.pm
Criterion Covered Total %
statement 114 117 97.4
branch 24 26 92.3
condition 10 16 62.5
subroutine 19 19 100.0
pod 3 3 100.0
total 170 181 93.9


line stmt bran cond sub pod time code
1             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2             #
3             # file: lib/Dist/Zilla/Plugin/Manifest/Read.pm
4             #
5             # Copyright © 2015 Van de Bugger
6             #
7             # This file is part of perl-Dist-Zilla-Plugin-Manifest-Read.
8             #
9             # perl-Dist-Zilla-Plugin-Manifest-Read is free software: you can redistribute it and/or modify it
10             # under the terms of the GNU General Public License as published by the Free Software Foundation,
11             # either version 3 of the License, or (at your option) any later version.
12             #
13             # perl-Dist-Zilla-Plugin-Manifest-Read is distributed in the hope that it will be useful, but
14             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15             # PARTICULAR PURPOSE. See the GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License along with
18             # perl-Dist-Zilla-Plugin-Manifest-Read. If not, see <http://www.gnu.org/licenses/>.
19             #
20             # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
21              
22             #pod =for :this This is C<Dist::Zilla::Plugin::Manifest::Read> module documentation. Read this if you are going to hack or
23             #pod extend C<Manifest::Read>, or use it programmatically.
24             #pod
25             #pod =for :those If you want to have annotated source manifest, read the L<user manual|Dist::Zilla::Plugin::Manifest::Read::Manual>.
26             #pod General topics like getting source, building, installing, bug reporting and some others are covered
27             #pod in the F<README>.
28             #pod
29             #pod =head1 SYNOPSIS
30             #pod
31             #pod =for test_synopsis my $self;
32             #pod
33             #pod In your plugin:
34             #pod
35             #pod # Iterate through the distribution files listed in MANIFEST
36             #pod # (files not included into distrubution are not iterated):
37             #pod my $files = $self->zilla->plugin_named( 'Manifest::Read' )->find_files();
38             #pod for my $file ( @$files ) {
39             #pod ...
40             #pod };
41             #pod
42             #pod =head1 DESCRIPTION
43             #pod
44             #pod This class consumes L<Dist::Zilla::Role::FileGatherer> and C<Dist::Zilla::Role::FileFinder> role.
45             #pod In order to fulfill requirements, the class implements C<gather_files> and C<find_files> methods.
46             #pod Other methods are supporting.
47             #pod
48             #pod The class also consumes L<Dist::Zilla::Role::ErrorLogger> role. It allows the class not to stop
49             #pod at the first problem but continue and report multiple errors to user.
50             #pod
51             #pod =cut
52              
53             # --------------------------------------------------------------------------------------------------
54              
55             package Dist::Zilla::Plugin::Manifest::Read;
56              
57 1     1   1697496 use Moose;
  1         2  
  1         6  
58 1     1   4139 use namespace::autoclean;
  1         1  
  1         8  
59 1     1   59 use version 0.77;
  1         19  
  1         6  
60              
61             # ABSTRACT: Read annotated source manifest
62             our $VERSION = 'v0.4.2_01'; # TRIAL VERSION
63              
64             with 'Dist::Zilla::Role::FileGatherer';
65             with 'Dist::Zilla::Role::FileFinder';
66             with 'Dist::Zilla::Role::ErrorLogger' => { -version => 0.006 }; # need log_errors_in_file
67              
68 1     1   81 use Dist::Zilla::File::OnDisk;
  1         0  
  1         21  
69 1     1   3 use List::Util qw{ min max };
  1         1  
  1         110  
70 1     1   4 use Path::Tiny;
  1         1  
  1         35  
71 1     1   4 use Try::Tiny;
  1         1  
  1         1052  
72              
73             # --------------------------------------------------------------------------------------------------
74              
75             #pod =attr manifest_name
76             #pod
77             #pod Name of manifest file to read.
78             #pod
79             #pod C<Str>, read-only, default value is C<MANIFEST>, C<init_arg> is C<manifest>.
80             #pod
81             #pod =cut
82              
83             has manifest_name => (
84             isa => 'Str',
85             is => 'ro',
86             default => 'MANIFEST',
87             init_arg => 'manifest',
88             );
89              
90             # --------------------------------------------------------------------------------------------------
91              
92             #pod =attr manifest_file
93             #pod
94             #pod Manifest file as a C<Dist::Zilla> file object (C<Dist::Zilla::File::OnDisk>).
95             #pod
96             #pod C<Object>, read-only.
97             #pod
98             #pod =cut
99              
100             has manifest_file => (
101             isa => 'Dist::Zilla::File::OnDisk',
102             is => 'ro',
103             lazy => 1,
104             builder => '_build_manifest_file',
105             init_arg => undef,
106             );
107              
108             sub _build_manifest_file {
109 7     7   8 my ( $self ) = @_;
110             # Straightforward appoach
111             # path( $self->manifest_name )
112             # is incorrect: it works only if the root directory is the current one, which is not always
113             # true. Following expression for manifest path is correct, but gives absolute (and often too
114             # long) name:
115             # path( $self->zilla->root )->child( $self->manifest )
116             # Let's try to shorten it. Hope this works:
117             # path( $self->zilla->root )->child( $self->manifest )->relative
118             # ...until someone changes the current directory...
119 7         162 return Dist::Zilla::File::OnDisk->new( {
120             name => path( $self->zilla->root )->child( $self->manifest_name )->relative . '',
121             } );
122             };
123              
124             # --------------------------------------------------------------------------------------------------
125              
126             #pod =method BUILD
127             #pod
128             #pod This method creates bunch of file finders: C<Manifest::Write/AllFiles>, C<Manifest::Write/ExecFiles>, C<Manifest::Write/ExtraTestFiles>, C<Manifest::Write/InstallModules>, C<Manifest::Write/PerlExecFiles>, C<Manifest::Write/TestFiles>.
129             #pod
130             #pod =cut
131              
132             sub BUILD {
133 7     7 1 10 my ( $self ) = @_;
134 7         580 require Dist::Zilla::Plugin::FinderCode;
135 7         23950 my $parent = $self;
136 7         183 my $zilla = $self->zilla;
137 7         49 my @finders = qw{ AllFiles ExecFiles ExtraTestFiles InstallModules PerlExecFiles TestFiles };
138 7         14 for my $name ( @finders ) {
139             my $finder = Dist::Zilla::Plugin::FinderCode->new( {
140             plugin_name => $self->plugin_name . '/' . $name,
141             zilla => $zilla,
142             style => 'list',
143             code => sub {
144 9     9   13709 my ( $self ) = @_;
145 9         189 my $plugin = $self->zilla->plugin_named( ':' . $name );
146 9 50       3581 if ( not defined( $plugin ) ) {
147 0         0 $parent->abort( [ "Can't find plugin %s", ':' . $name ] );
148             };
149             my @files = grep(
150 39   100     8777 { ( $parent->_file_hash->{ $_->name } || 0 ) == $_ }
151 9         9 @{ $plugin->find_files() }
  9         20  
152             );
153 9         301 return \@files;
154             },
155 42         1016 } );
156 42         2515 push( @{ $zilla->plugins }, $finder );
  42         788  
157             };
158 7         251 return;
159             };
160              
161             # --------------------------------------------------------------------------------------------------
162              
163             #pod =method gather_files
164             #pod
165             #pod This method fulfills L<Dist::Zilla::Role::FileGatherer> role requirement. It adds files listed in
166             #pod manifest to distribution (and to C<_file_list>). Files marked to exclude from distribution and
167             #pod directories are not added.
168             #pod
169             #pod =cut
170              
171             sub gather_files {
172 7     7 1 229129 my ( $self ) = @_;
173 7         11 for my $file ( @{ $self->_file_list } ) {
  7         237  
174 46         11261 $self->add_file( $file );
175             };
176 4         1085 return;
177             };
178              
179             # --------------------------------------------------------------------------------------------------
180              
181             #pod =method find_files
182             #pod
183             #pod This method fulfills L<Dist::Zilla::Role::FileFinder> role requirement. It simply returns a copy of
184             #pod C<_file_list> attribute.
185             #pod
186             #pod This method can be called by other plugins to iterate through files added by C<Manifest::Read>,
187             #pod see L</"SYNOPSIS">.
188             #pod
189             #pod Note: The method always returns the same list of files. Plugins which remove files from
190             #pod distribution (i. e. plugins which do C<Dist::Zilla::Role::FilePruner> role) do not affect result of
191             #pod the method.
192             #pod
193             #pod =cut
194              
195             sub find_files {
196 4     4 1 167332 my ( $self ) = @_;
197 4         7 return [ @{ $self->_file_list } ];
  4         182  
198             };
199              
200             # --------------------------------------------------------------------------------------------------
201              
202             #pod =attr _file_list
203             #pod
204             #pod Array of files (object which do C<Dist::Zilla::Role::File> role) listed in the manifest I<and>
205             #pod marked for inclusion to the distribution.
206             #pod
207             #pod C<ArrayRef>, read-only, lazy, initialized with builder.
208             #pod
209             #pod =cut
210              
211             has _file_list => (
212             isa => 'ArrayRef[Object]',
213             is => 'ro',
214             lazy => 1,
215             builder => '_build_file_list',
216             init_arg => undef,
217             );
218              
219             sub _build_file_list {
220 7     7   11 my ( $self ) = @_;
221 7         12 my $files = [];
222 7         9 my @errors;
223             my $error = sub {
224 6     6   7 my ( $item, $message ) = @_;
225             my $err = sprintf(
226             '%s %s at %s line %d.',
227             $item->{ filename }, $message, $self->manifest_name, $item->{ line },
228 6         209 );
229 6         14 push( @errors, $item->{ line } => $err );
230 6         15 return $self->log_error( $err );
231 7         29 };
232 7         21 foreach my $item ( $self->_parse_lines() ) {
233 59 100 50     1632 -e $item->{ filename } or $error->( $item, 'does not exist' ) and next;
234 55 100       85 if ( $item->{ mark } eq '/' ) {
235 2 100 50     8 -d _ or $error->( $item, 'is not a directory' ) and next;
236             } else {
237 53 100 50     75 -f _ or $error->( $item, 'is not a plain file' ) and next;
238 52 100       79 if ( $item->{ mark } ne '-' ) {
239 46         1057 my $file = Dist::Zilla::File::OnDisk->new( { name => $item->{ filename } } );
240 46         8742 push( @$files, $file );
241             };
242             };
243             };
244 5 100       264 if ( @errors ) {
245 1         36 $self->log_errors_in_file( $self->manifest_file, @errors );
246             };
247 5         3002 $self->abort_if_error();
248 4         271 return $files;
249             };
250              
251             # --------------------------------------------------------------------------------------------------
252              
253             #pod =attr _file_hash
254             #pod
255             #pod Hash of files (object which do C<Dist::Zilla::Role::File> role) listed in the manifest I<and>
256             #pod marked for inclusion to the distribution. Hash keys are file names, hash values are references to
257             #pod file objects. Built from C<_file_list>.
258             #pod
259             #pod C<HasfRef>, read-only, lazy, initialized with builder.
260             #pod
261             #pod =cut
262              
263             has _file_hash => (
264             isa => 'HashRef[Object]',
265             is => 'ro',
266             lazy => 1,
267             builder => '_build_file_hash',
268             init_arg => undef,
269             );
270              
271             sub _build_file_hash {
272 2     2   2 my ( $self ) = @_;
273 2         3 return { map( { $_->name => $_ } @{ $self->_file_list } ) };
  14         348  
  2         58  
274             };
275              
276             # --------------------------------------------------------------------------------------------------
277              
278             #pod =attr _lines
279             #pod
280             #pod Array of chomped manifest lines, including comments and empty lines.
281             #pod
282             #pod C<ArrayRef[Str]>, read-only, lazy, initialized with builder.
283             #pod
284             #pod =cut
285              
286             has _lines => (
287             isa => 'ArrayRef[Str]',
288             is => 'ro',
289             lazy => 1,
290             init_arg => undef,
291             builder => '_build_lines',
292             );
293              
294             sub _build_lines {
295 7     7   8 my ( $self ) = @_;
296 7         7 my $lines = [];
297             try {
298 7     7   401 @$lines = split( "\n", $self->manifest_file->content );
299             } catch {
300 1     1   516 my $ex = $_;
301 1 50 33     15 if ( blessed( $ex ) and $ex->isa( 'Path::Tiny::Error' ) ) {
302 1         25 $self->abort( [ '%s: %s', $ex->{ file }, $ex->{ err } ] );
303             } else {
304 0         0 $self->abort( "$ex" );
305             };
306 7         56 };
307 6         3906 chomp( @$lines );
308 6         185 return $lines;
309             };
310              
311             # --------------------------------------------------------------------------------------------------
312              
313             #pod =method _parse_lines
314             #pod
315             #pod This method parses manifest lines. Each line is parsed separately (there is no line continuation).
316             #pod
317             #pod If the method fails to parse a line, error is reported by calling method C<log_error> (implemented
318             #pod in L<Dist::Zilla::Role::ErrorLogger>). This means that parsing is not stopped at the first failure,
319             #pod but entire manifest will be parsed and all the found errors will be reported.
320             #pod
321             #pod The method returns list of hashrefs, a hash per file. Each hash has following keys and values:
322             #pod
323             #pod =for :list
324             #pod = filename
325             #pod Parsed filename (single-quoted filenames are unquoted, escape sequences are evaluated, if any).
326             #pod = mark
327             #pod Mark.
328             #pod = comment
329             #pod File comment, leading and trailing whitespaces are stripped.
330             #pod = line
331             #pod Number of manifest line the file listed in.
332             #pod
333             #pod =cut
334              
335             my %RE = (
336             filename => qr{ ' (*PRUNE) (?: [^'\\] ++ | \\ ['\\] ?+ ) ++ ' | \S ++ }x,
337             # ^ TODO: Use Regexp::Common for quoted filename?
338             mark => qr{ [#/+-] }x,
339             comment => qr{ . *? }x,
340             );
341              
342             sub _parse_lines {
343 7     7   8 my ( $self ) = @_;
344 7         214 my $manifest = $self->manifest_name; # Shorter name.
345 7         10 my ( %files, @files );
346 0         0 my @errors;
347 7         10 my $n = 0;
348 7         6 for my $line ( @{ $self->_lines } ) {
  7         201  
349 74         50 ++ $n;
350 74 100       135 if ( $line =~ m{ \A \s * (?: \# | \z ) }x ) { # Comment or empty line.
351 9         8 next;
352             };
353             ## no critic ( ProhibitComplexRegexes )
354             $line =~ m{
355             \A
356             \s *+ # requires perl v5.10
357             ( $RE{ filename } )
358             (*PRUNE) # requires perl v5.10
359             (?:
360             \s ++
361             ( $RE{ mark } )
362             (*PRUNE)
363             (?:
364             \s ++
365             ( $RE{ comment } )
366             ) ?
367             ) ?
368             \s *
369             \z
370             }x and do {
371 62         91 my ( $filename, $mark, $comment ) = ( $1, $2, $3 );
372 62 100       113 if ( $filename =~ s{ \A ' ( . * ) ' \z }{ $1 }ex ) {
  15         35  
373 15         21 $filename =~ s{ \\ ( ['\\] ) }{ $1 }gex;
  6         10  
374             };
375 62 100       104 if ( exists( $files{ $filename } ) ) {
376 2         2 my $f = $files{ $filename };
377 2         8 $self->log_error( [ '%s at %s line %d', $filename, $manifest, $n ] );
378 2         431 $self->log_error( [ ' also listed at %s line %d.', $manifest, $f->{ line } ] );
379             push( @errors,
380             $n => 'The file also listed at line ' . $f->{ line } . '.',
381 2         413 $f->{ line } => 'The file also listed at line ' . $n . '.',
382             );
383 2         4 next;
384             };
385 60   100     170 my $file = {
386             filename => $filename,
387             mark => $mark // '+', # requires perl v5.10
388             comment => $comment,
389             line => $n,
390             };
391 60         90 $files{ $filename } = $file;
392 60         51 push( @files, $file );
393 60         124 1;
394 65 100 66     545 } or do {
395 3         13 my $error = sprintf( 'Syntax error at %s line %d.', $manifest, $n );
396 3         10 $self->log_error( $error );
397 3         610 push( @errors, $n => $error );
398 3         7 next;
399             };
400             };
401 6 100       14 if ( @errors ) {
402 1         35 $self->log_errors_in_file( $self->manifest_file, @errors );
403 1         3139 $self->abort();
404             };
405 5         27 return @files;
406             };
407              
408             # --------------------------------------------------------------------------------------------------
409              
410             __PACKAGE__->meta->make_immutable;
411              
412             1;
413              
414             # --------------------------------------------------------------------------------------------------
415              
416             #pod =pod
417             #pod
418             #pod =encoding UTF-8
419             #pod
420             #pod =head1 WHAT?
421             #pod
422             #pod C<Dist-Zilla-Plugin-Manifest-Read> (or C<Manifest::Read> for brevity) is a C<Dist::Zilla> plugin. It reads
423             #pod I<annotated source> manifest, checks existence of all listed files and directories, and adds
424             #pod selected files to the distribution. C<Manifest::Read> also does C<FileFinder> role, providing the
425             #pod list of added files for other plugins.
426             #pod
427             #pod =cut
428              
429              
430             #pod =head1 SEE ALSO
431             #pod
432             #pod =for :list
433             #pod = L<Dist::Zilla>
434             #pod = L<Dist::Zilla::Role::FileGatherer>
435             #pod = L<Dist::Zilla::Role::ErrorLogger>
436             #pod
437             #pod =head1 COPYRIGHT AND LICENSE
438             #pod
439             #pod Copyright (C) 2015 Van de Bugger
440             #pod
441             #pod License GPLv3+: The GNU General Public License version 3 or later
442             #pod <http://www.gnu.org/licenses/gpl-3.0.txt>.
443             #pod
444             #pod This is free software: you are free to change and redistribute it. There is
445             #pod NO WARRANTY, to the extent permitted by law.
446             #pod
447             #pod
448             #pod =cut
449              
450             # end of file #
451              
452             __END__
453              
454             =pod
455              
456             =encoding UTF-8
457              
458             =head1 NAME
459              
460             Dist::Zilla::Plugin::Manifest::Read - Read annotated source manifest
461              
462             =head1 VERSION
463              
464             Version v0.4.2_01, released on 2016-10-11 00:15 UTC.
465             This is a B<trial release>.
466              
467             =head1 WHAT?
468              
469             C<Dist-Zilla-Plugin-Manifest-Read> (or C<Manifest::Read> for brevity) is a C<Dist::Zilla> plugin. It reads
470             I<annotated source> manifest, checks existence of all listed files and directories, and adds
471             selected files to the distribution. C<Manifest::Read> also does C<FileFinder> role, providing the
472             list of added files for other plugins.
473              
474             This is C<Dist::Zilla::Plugin::Manifest::Read> module documentation. Read this if you are going to hack or
475             extend C<Manifest::Read>, or use it programmatically.
476              
477             If you want to have annotated source manifest, read the L<user manual|Dist::Zilla::Plugin::Manifest::Read::Manual>.
478             General topics like getting source, building, installing, bug reporting and some others are covered
479             in the F<README>.
480              
481             =head1 SYNOPSIS
482              
483             =head1 DESCRIPTION
484              
485             This class consumes L<Dist::Zilla::Role::FileGatherer> and C<Dist::Zilla::Role::FileFinder> role.
486             In order to fulfill requirements, the class implements C<gather_files> and C<find_files> methods.
487             Other methods are supporting.
488              
489             The class also consumes L<Dist::Zilla::Role::ErrorLogger> role. It allows the class not to stop
490             at the first problem but continue and report multiple errors to user.
491              
492             =head1 OBJECT ATTRIBUTES
493              
494             =head2 manifest_name
495              
496             Name of manifest file to read.
497              
498             C<Str>, read-only, default value is C<MANIFEST>, C<init_arg> is C<manifest>.
499              
500             =head2 manifest_file
501              
502             Manifest file as a C<Dist::Zilla> file object (C<Dist::Zilla::File::OnDisk>).
503              
504             C<Object>, read-only.
505              
506             =head2 _file_list
507              
508             Array of files (object which do C<Dist::Zilla::Role::File> role) listed in the manifest I<and>
509             marked for inclusion to the distribution.
510              
511             C<ArrayRef>, read-only, lazy, initialized with builder.
512              
513             =head2 _file_hash
514              
515             Hash of files (object which do C<Dist::Zilla::Role::File> role) listed in the manifest I<and>
516             marked for inclusion to the distribution. Hash keys are file names, hash values are references to
517             file objects. Built from C<_file_list>.
518              
519             C<HasfRef>, read-only, lazy, initialized with builder.
520              
521             =head2 _lines
522              
523             Array of chomped manifest lines, including comments and empty lines.
524              
525             C<ArrayRef[Str]>, read-only, lazy, initialized with builder.
526              
527             =head1 OBJECT METHODS
528              
529             =head2 BUILD
530              
531             This method creates bunch of file finders: C<Manifest::Write/AllFiles>, C<Manifest::Write/ExecFiles>, C<Manifest::Write/ExtraTestFiles>, C<Manifest::Write/InstallModules>, C<Manifest::Write/PerlExecFiles>, C<Manifest::Write/TestFiles>.
532              
533             =head2 gather_files
534              
535             This method fulfills L<Dist::Zilla::Role::FileGatherer> role requirement. It adds files listed in
536             manifest to distribution (and to C<_file_list>). Files marked to exclude from distribution and
537             directories are not added.
538              
539             =head2 find_files
540              
541             This method fulfills L<Dist::Zilla::Role::FileFinder> role requirement. It simply returns a copy of
542             C<_file_list> attribute.
543              
544             This method can be called by other plugins to iterate through files added by C<Manifest::Read>,
545             see L</"SYNOPSIS">.
546              
547             Note: The method always returns the same list of files. Plugins which remove files from
548             distribution (i. e. plugins which do C<Dist::Zilla::Role::FilePruner> role) do not affect result of
549             the method.
550              
551             =head2 _parse_lines
552              
553             This method parses manifest lines. Each line is parsed separately (there is no line continuation).
554              
555             If the method fails to parse a line, error is reported by calling method C<log_error> (implemented
556             in L<Dist::Zilla::Role::ErrorLogger>). This means that parsing is not stopped at the first failure,
557             but entire manifest will be parsed and all the found errors will be reported.
558              
559             The method returns list of hashrefs, a hash per file. Each hash has following keys and values:
560              
561             =over 4
562              
563             =item filename
564              
565             Parsed filename (single-quoted filenames are unquoted, escape sequences are evaluated, if any).
566              
567             =item mark
568              
569             Mark.
570              
571             =item comment
572              
573             File comment, leading and trailing whitespaces are stripped.
574              
575             =item line
576              
577             Number of manifest line the file listed in.
578              
579             =back
580              
581             =for test_synopsis my $self;
582              
583             In your plugin:
584              
585             # Iterate through the distribution files listed in MANIFEST
586             # (files not included into distrubution are not iterated):
587             my $files = $self->zilla->plugin_named( 'Manifest::Read' )->find_files();
588             for my $file ( @$files ) {
589             ...
590             };
591              
592             =head1 SEE ALSO
593              
594             =over 4
595              
596             =item L<Dist::Zilla>
597              
598             =item L<Dist::Zilla::Role::FileGatherer>
599              
600             =item L<Dist::Zilla::Role::ErrorLogger>
601              
602             =back
603              
604             =head1 AUTHOR
605              
606             Van de Bugger <van.de.bugger@gmail.com>
607              
608             =head1 COPYRIGHT AND LICENSE
609              
610             Copyright (C) 2015 Van de Bugger
611              
612             License GPLv3+: The GNU General Public License version 3 or later
613             <http://www.gnu.org/licenses/gpl-3.0.txt>.
614              
615             This is free software: you are free to change and redistribute it. There is
616             NO WARRANTY, to the extent permitted by law.
617              
618             =cut