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