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