File Coverage

blib/lib/Dist/Zilla/Plugin/GatherDir.pm
Criterion Covered Total %
statement 68 69 98.5
branch 17 18 94.4
condition n/a
subroutine 16 16 100.0
pod 0 2 0.0
total 101 105 96.1


line stmt bran cond sub pod time code
1             package Dist::Zilla::Plugin::GatherDir 6.029;
2             # ABSTRACT: gather all the files in a directory
3              
4 43     43   34226 use Moose;
  43         120  
  43         338  
5             with 'Dist::Zilla::Role::FileGatherer';
6              
7 43     43   303109 use Dist::Zilla::Pragmas;
  43         126  
  43         480  
8              
9 43     43   384 use namespace::autoclean;
  43         129  
  43         457  
10              
11 43     43   4253 use Dist::Zilla::Types qw(Path);
  43         121  
  43         612  
12 43     43   108343 use Dist::Zilla::Util;
  43         118  
  43         1690  
13              
14             #pod =head1 DESCRIPTION
15             #pod
16             #pod This is a very, very simple L<FileGatherer|Dist::Zilla::Role::FileGatherer>
17             #pod plugin. It looks in the directory named in the L</root> attribute and adds all
18             #pod the files it finds there. If the root begins with a tilde, the tilde is
19             #pod passed through C<glob()> first.
20             #pod
21             #pod Almost every dist will be built with one GatherDir plugin, since it's the
22             #pod easiest way to get files from disk into your dist. Most users just need:
23             #pod
24             #pod [GatherDir]
25             #pod [PruneCruft]
26             #pod
27             #pod ...and this will pick up all the files from the current directory into the
28             #pod dist. (L<PruneCruft|Dist::Zilla::Plugin::PruneCruft> is needed, here, to drop
29             #pod files that might present as build artifacts, but should not be shipped.) You
30             #pod can use it multiple times, as you can any other plugin, by providing a plugin
31             #pod name. For example, if you want to include external specification files into a
32             #pod subdir of your dist, you might write:
33             #pod
34             #pod [GatherDir]
35             #pod ; this plugin needs no config and gathers most of your files
36             #pod
37             #pod [GatherDir / SpecFiles]
38             #pod ; this plugin gets all the files in the root dir and adds them under ./spec
39             #pod root = ~/projects/my-project/spec
40             #pod prefix = spec
41             #pod
42             #pod =cut
43              
44 43     43   26144 use File::Find::Rule;
  43         376599  
  43         423  
45 43     43   3272 use File::Spec;
  43         131  
  43         933  
46 43     43   288 use Path::Tiny;
  43         115  
  43         2792  
47 43     43   353 use List::Util 1.33 'all';
  43         1255  
  43         62990  
48              
49             #pod =attr root
50             #pod
51             #pod This is the directory in which to look for files. If not given, it defaults to
52             #pod the dist root -- generally, the place where your F<dist.ini> or other
53             #pod configuration file is located.
54             #pod
55             #pod =cut
56              
57             has root => (
58             is => 'ro',
59             isa => Path,
60             lazy => 1,
61             coerce => 1,
62             required => 1,
63             default => sub { shift->zilla->root },
64             );
65              
66             #pod =attr prefix
67             #pod
68             #pod This parameter can be set to place the gathered files under a particular
69             #pod directory. See the L<description|DESCRIPTION> above for an example.
70             #pod
71             #pod =cut
72              
73             has prefix => (
74             is => 'ro',
75             isa => 'Str',
76             default => '',
77             );
78              
79             #pod =attr include_dotfiles
80             #pod
81             #pod By default, files will not be included if they begin with a dot. This goes
82             #pod both for files and for directories relative to the C<root>.
83             #pod
84             #pod In almost all cases, the default value (false) is correct.
85             #pod
86             #pod =cut
87              
88             has include_dotfiles => (
89             is => 'ro',
90             isa => 'Bool',
91             default => 0,
92             );
93              
94             #pod =attr follow_symlinks
95             #pod
96             #pod By default, symlinks pointing to directories will not be followed; set
97             #pod C<< follow_symlinks = 1 >> to traverse these links as if they were normal
98             #pod directories.
99             #pod
100             #pod In all followed directories, files which are symlinks are B<always> gathered,
101             #pod with the link turning into a normal file.
102             #pod
103             #pod =cut
104              
105             has follow_symlinks => (
106             is => 'ro',
107             isa => 'Bool',
108             default => 0,
109             );
110              
111 172     172 0 52679 sub mvp_multivalue_args { qw(exclude_filename exclude_match prune_directory) }
112              
113             #pod =attr exclude_filename
114             #pod
115             #pod To exclude certain files from being gathered, use the C<exclude_filename>
116             #pod option. The filename is matched exactly, relative to C<root>.
117             #pod This may be used multiple times to specify multiple files to exclude.
118             #pod
119             #pod =cut
120              
121             has exclude_filename => (
122             is => 'ro',
123             isa => 'ArrayRef',
124             default => sub { [] },
125             );
126              
127             #pod =attr exclude_match
128             #pod
129             #pod This is just like C<exclude_filename> but provides a regular expression
130             #pod pattern. Filenames matching the pattern (relative to C<root>) are not
131             #pod gathered. This may be used
132             #pod multiple times to specify multiple patterns to exclude.
133             #pod
134             #pod =cut
135              
136             has exclude_match => (
137             is => 'ro',
138             isa => 'ArrayRef',
139             default => sub { [] },
140             );
141              
142             #pod =attr prune_directory
143             #pod
144             #pod While traversing, any directory matching the regular expression pattern will
145             #pod not be traversed further. This may be used multiple times to specify multiple
146             #pod directories to skip.
147             #pod
148             #pod =cut
149              
150             has prune_directory => (
151             is => 'ro',
152             isa => 'ArrayRef',
153             default => sub { [] },
154             );
155              
156             around dump_config => sub {
157             my $orig = shift;
158             my $self = shift;
159              
160             my $config = $self->$orig;
161              
162             $config->{+__PACKAGE__} = {
163             prefix => $self->prefix,
164             # only report relative to dist root to avoid leaking private info
165             root => path($self->root)->relative($self->zilla->root),
166             (map { $_ => $self->$_ ? 1 : 0 } qw(include_dotfiles follow_symlinks)),
167             (map { $_ => [ sort @{ $self->$_ } ] } qw(exclude_filename exclude_match prune_directory)),
168             };
169              
170             return $config;
171             };
172              
173             sub gather_files {
174 170     170 0 714 my ($self) = @_;
175              
176 170         905 my $exclude_regex = qr/\000/;
177             $exclude_regex = qr/(?:$exclude_regex)|$_/
178 170         448 for @{ $self->exclude_match };
  170         6527  
179              
180 170         5136 my $repo_root = $self->zilla->root;
181 170         5579 my $root = "" . $self->root;
182 170         1520 $root =~ s{^~([\\/])}{ Dist::Zilla::Util->homedir . $1 }e;
  0         0  
183 170 100       817 $root = path($root)->absolute($repo_root)->stringify if path($root)->is_relative;
184              
185 170         22350 my $prune_regex = qr/\000/;
186             $prune_regex = qr/$prune_regex|$_/
187 170 100       455 for ( @{ $self->prune_directory },
  170         6530  
188             $self->include_dotfiles ? () : ( qr/^\.[^.]/ ) );
189              
190             # build up the rules
191 170         2310 my $rule = File::Find::Rule->new();
192 170         8752 $rule->extras({ follow => $self->follow_symlinks });
193              
194 80     80   14227 $rule->exec(sub { $self->log_debug('considering ' . path($_[-1])->relative($repo_root)); 1 })
  80         22970  
195 170 100       7827 if $self->zilla->logger->get_debug;
196              
197             $rule->or(
198 170     685   2972 $rule->new->directory->exec(sub { /$prune_regex/ })->prune->discard,
  685         180865  
199             $rule->new,
200             );
201              
202 170 100       41833 if ($self->follow_symlinks) {
203 1         6 $rule->or(
204             $rule->new->file, # symlinks to files still count as files
205             $rule->new->symlink, # traverse into the linked dir, but screen it out later
206             );
207             } else {
208 169         3836 $rule->file;
209             }
210              
211 170 100   652   11165 $rule->not_exec(sub { /^\.[^.]/ }) unless $self->include_dotfiles; # exec passes basename as $_
  652         126318  
212             $rule->exec(sub {
213 651     651   4199 my $relative = path($_[-1])->relative($root);
214             $relative !~ $exclude_regex &&
215 651 100       183298 all { $relative ne $_ } @{ $self->exclude_filename }
  7         29  
  649         28804  
216 170         17023 });
217              
218 170         2972 FILE: for my $filename ($rule->in($root)) {
219 647 100       26424 next if -d $filename;
220              
221             # _file_from_filename is overloaded in GatherDir::Template
222 646         3407 my $fileobj = $self->_file_from_filename($filename);
223              
224             # GatherDir::Template may rename the file
225 646         2651 $filename = $fileobj->name;
226 646         2693 my $file = path($filename)->relative($root);
227 646 100       183779 $file = path($self->prefix, $file) if $self->prefix;
228              
229 646         3902 $fileobj->name($file->stringify);
230 646         2945 $self->add_file($fileobj);
231             }
232              
233 170         4486 return;
234             }
235              
236             sub _file_from_filename {
237 646     646   1903 my ($self, $filename) = @_;
238              
239 646 50       9439 my @stat = stat $filename or $self->log_fatal("$filename does not exist!");
240              
241 646         27396 return Dist::Zilla::File::OnDisk->new({
242             name => $filename,
243             mode => $stat[2] & 0755, # kill world-writeability
244             });
245             }
246              
247             __PACKAGE__->meta->make_immutable;
248             1;
249              
250             __END__
251              
252             =pod
253              
254             =encoding UTF-8
255              
256             =head1 NAME
257              
258             Dist::Zilla::Plugin::GatherDir - gather all the files in a directory
259              
260             =head1 VERSION
261              
262             version 6.029
263              
264             =head1 DESCRIPTION
265              
266             This is a very, very simple L<FileGatherer|Dist::Zilla::Role::FileGatherer>
267             plugin. It looks in the directory named in the L</root> attribute and adds all
268             the files it finds there. If the root begins with a tilde, the tilde is
269             passed through C<glob()> first.
270              
271             Almost every dist will be built with one GatherDir plugin, since it's the
272             easiest way to get files from disk into your dist. Most users just need:
273              
274             [GatherDir]
275             [PruneCruft]
276              
277             ...and this will pick up all the files from the current directory into the
278             dist. (L<PruneCruft|Dist::Zilla::Plugin::PruneCruft> is needed, here, to drop
279             files that might present as build artifacts, but should not be shipped.) You
280             can use it multiple times, as you can any other plugin, by providing a plugin
281             name. For example, if you want to include external specification files into a
282             subdir of your dist, you might write:
283              
284             [GatherDir]
285             ; this plugin needs no config and gathers most of your files
286              
287             [GatherDir / SpecFiles]
288             ; this plugin gets all the files in the root dir and adds them under ./spec
289             root = ~/projects/my-project/spec
290             prefix = spec
291              
292             =head1 PERL VERSION
293              
294             This module should work on any version of perl still receiving updates from
295             the Perl 5 Porters. This means it should work on any version of perl released
296             in the last two to three years. (That is, if the most recently released
297             version is v5.40, then this module should work on both v5.40 and v5.38.)
298              
299             Although it may work on older versions of perl, no guarantee is made that the
300             minimum required version will not be increased. The version may be increased
301             for any reason, and there is no promise that patches will be accepted to lower
302             the minimum required perl.
303              
304             =head1 ATTRIBUTES
305              
306             =head2 root
307              
308             This is the directory in which to look for files. If not given, it defaults to
309             the dist root -- generally, the place where your F<dist.ini> or other
310             configuration file is located.
311              
312             =head2 prefix
313              
314             This parameter can be set to place the gathered files under a particular
315             directory. See the L<description|DESCRIPTION> above for an example.
316              
317             =head2 include_dotfiles
318              
319             By default, files will not be included if they begin with a dot. This goes
320             both for files and for directories relative to the C<root>.
321              
322             In almost all cases, the default value (false) is correct.
323              
324             =head2 follow_symlinks
325              
326             By default, symlinks pointing to directories will not be followed; set
327             C<< follow_symlinks = 1 >> to traverse these links as if they were normal
328             directories.
329              
330             In all followed directories, files which are symlinks are B<always> gathered,
331             with the link turning into a normal file.
332              
333             =head2 exclude_filename
334              
335             To exclude certain files from being gathered, use the C<exclude_filename>
336             option. The filename is matched exactly, relative to C<root>.
337             This may be used multiple times to specify multiple files to exclude.
338              
339             =head2 exclude_match
340              
341             This is just like C<exclude_filename> but provides a regular expression
342             pattern. Filenames matching the pattern (relative to C<root>) are not
343             gathered. This may be used
344             multiple times to specify multiple patterns to exclude.
345              
346             =head2 prune_directory
347              
348             While traversing, any directory matching the regular expression pattern will
349             not be traversed further. This may be used multiple times to specify multiple
350             directories to skip.
351              
352             =head1 AUTHOR
353              
354             Ricardo SIGNES 😏 <cpan@semiotic.systems>
355              
356             =head1 COPYRIGHT AND LICENSE
357              
358             This software is copyright (c) 2022 by Ricardo SIGNES.
359              
360             This is free software; you can redistribute it and/or modify it under
361             the same terms as the Perl 5 programming language system itself.
362              
363             =cut