File Coverage

blib/lib/File/Finder.pm
Criterion Covered Total %
statement 94 95 98.9
branch 54 66 81.8
condition n/a
subroutine 20 20 100.0
pod 5 5 100.0
total 173 186 93.0


line stmt bran cond sub pod time code
1             package File::Finder;
2              
3 4     4   49804 use 5.006;
  4         14  
  4         166  
4 4     4   25 use strict;
  4         6  
  4         135  
5 4     4   31 use warnings;
  4         7  
  4         142  
6              
7 4     4   25 use base qw(Exporter);
  4         7  
  4         523  
8              
9             ## no exports
10              
11             our $VERSION = '0.53';
12              
13 4     4   50 use Carp qw(croak);
  4         8  
  4         638  
14              
15             ## public methods:
16              
17             sub new {
18 29     29 1 11064 my $class = shift;
19 29         203 bless {
20             options => {},
21             steps => [],
22             }, $class;
23             }
24              
25             sub as_wanted {
26 44     44 1 50 my $self = shift;
27 44     43   190 return sub { $self->_run };
  43         92  
28             }
29              
30             use overload
31 4         32 '&{}' => 'as_wanted',
32             # '""' => sub { overload::StrVal(shift) },
33 4     4   3519 ;
  4         2389  
34              
35             sub as_options {
36 30     30 1 47 my $self = shift;
37 30     1650   48 return { %{$self->{options}}, wanted => sub { $self->_run } };
  30         1711  
  1650         3565  
38             }
39              
40             sub in {
41 25     25 1 52 my $self = _force_object(shift);
42              
43             ## this must return count in a scalar context
44 25     873   120 $self->collect(sub { $File::Find::name }, @_);
  873         2454  
45             }
46              
47             sub collect {
48 25     25 1 45 my $self = _force_object(shift);
49 25         36 my $code = shift;
50              
51 25         36 my @result;
52 25     873   154 my $self_store = $self->eval( sub { push @result, $code->() } );
  873         1367  
53              
54 25         119 require File::Find;
55 25         94 File::Find::find($self_store->as_options, @_);
56              
57             ## this must return count in a scalar context
58 25         704 return @result;
59             }
60              
61             ## private methods
62              
63             sub _force_object {
64 666     666   703 my $self_or_class = shift;
65 666 100       1423 ref $self_or_class ? $self_or_class : $self_or_class->new;
66             }
67              
68             sub _clone {
69 308     308   470 my $self = _force_object(shift);
70 308         1029 bless {
71 308         1452 options => {%{$self->{options}}},
72 308         390 steps => [@{$self->{steps}}],
73             }, ref $self;
74             }
75              
76             ## we set this to ensure that _ is correct for all tests
77             $File::Find::dont_use_nlink = 1;
78             ## otherwise, we have to lstat/stat($_) inside _run
79             ## thanks, tye!
80              
81             sub _run {
82 1693     1693   1842 my $self = shift;
83              
84 1693         1776 my @stat;
85 1693 100       18919 @stat = stat if defined $_;
86              
87 1693         3006 my @state = (1);
88             ## $state[-1]:
89             ## if 2: we're in a true state, but we've just seen a NOT
90             ## if 1: we're in a true state
91             ## if 0: we're in a false state
92             ## if -1: we're in a "skipping" state (true OR ...[here]...)
93              
94 1693         1976 for my $step(@{$self->{steps}}) {
  1693         3982  
95              
96             ## verify underscore handle is good:
97 3816 100       7031 if (@stat) {
98 3564         8638 my @cache_stat = stat _;
99 3564 100       36728 stat unless "@stat" eq "@cache_stat";
100             }
101              
102 3816 100       7434 if (ref $step) { # coderef
    100          
    100          
    100          
    100          
    50          
103 3414 100       7622 if ($state[-1] >= 1) { # true state
104 2573 100       15018 if ($self->$step) { # coderef ran returning true
105 1735 100       6385 if ($state[-1] == 2) {
106 111         229 $state[-1] = 0;
107             }
108             } else {
109 838         1781 $state[-1]--; # 2 => 1, 1 => 0
110             }
111             }
112             } elsif ($step eq "or") {
113             # -1 => -1, 0 => 1, 1 => -1, 2 is error
114 63 50       114 croak "not before or?" if $state[-1] > 1;
115 63 100       130 if ($state[-1] == 0) {
    100          
116 12         18 $state[-1] = 1;
117             } elsif ($state[-1] == 1) {
118 31         43 $state[-1] = -1;
119             }
120             } elsif ($step eq "left") {
121             ## start subrule
122             ## -1 => -1, 0 => -1, 1 => 1, 2 => 1
123 27 100       61 push @state, ($state[-1] >= 1) ? 1 : -1;
124             } elsif ($step eq "right") {
125             ## end subrule
126 27 50       48 croak "right without left" unless @state > 1;
127 27 50       48 croak "not before right" if $state[-1] > 1;
128 27         36 my $result = pop @state;
129 27 100       64 if ($state[-1] >= 1) {
130 11 100       17 if ($result) { # 1 or -1, so counts as true
131 9 100       25 if ($state[-1] == 2) {
132 2         5 $state[-1] = 0;
133             }
134             } else {
135 2         5 $state[-1]--; # 2 => 1, 1 => 0
136             }
137             }
138             } elsif ($step eq "comma") {
139 12 50       25 croak "not before comma" if $state[-1] > 1;
140 12 100       26 if (@state < 2) { # not in parens
141 3         5 $state[-1] = 1; # reset to true
142             } else { # in parens, reset as if start of parens
143 9 100       22 $state[-1] = (($state[-2] >= 1) ? 1 : -1);
144             }
145             } elsif ($step eq "not") {
146             # -1 => -1, 0 => 0, 1 => 2, 2 => 1
147 273 50       501 if ($state[-1] >= 1) {
148 273 100       538 $state[-1] = $state[-1] > 1 ? 1 : 2;
149             }
150             } else {
151 0         0 die "internal error at $step";
152             }
153             }
154 1693 50       3818 croak "left without right" unless @state == 1;
155 1693 50       3223 croak "trailing not" if $state[-1] > 1;
156 1693         67799 return $state[-1] != 0; # true and skipping are both true
157             }
158              
159             sub AUTOLOAD {
160 308     308   140295 my $self = _force_object(shift);
161              
162 308         1451 my ($method) = our $AUTOLOAD =~ /(?:.*::)?(.*)/;
163 308 50       713 return if $method eq "DESTROY";
164              
165 308         581 my $clone = $self->_clone;
166              
167             ## bring in the steps
168 308         716 my $steps_class = $clone->_steps_class;
169 308 50       834 $steps_class =~ /[^\w:]/
170             and die "bad value for \$steps_class: $steps_class";
171 308 50       14230 eval "require $steps_class"; die $@ if $@;
  308         915  
172              
173 308 50       1782 my $sub_method = $steps_class->can($method)
174             or croak "Cannot add step $method";
175              
176 308         347 push @{$clone->{steps}}, $sub_method->($clone, @_);
  308         1161  
177 308         2247 $clone;
178             }
179              
180 308     308   452 sub _steps_class { "File::Finder::Steps" }
181              
182             1;
183             __END__
184              
185             =head1 NAME
186              
187             File::Finder - nice wrapper for File::Find ala find(1)
188              
189             =head1 SYNOPSIS
190              
191             use File::Finder;
192             ## simulate "-type f"
193             my $all_files = File::Finder->type('f');
194              
195             ## any rule can be extended:
196             my $all_files_printer = $all_files->print;
197              
198             ## traditional use: generating "wanted" subroutines:
199             use File::Find;
200             find($all_files_printer, @starting_points);
201              
202             ## or, we can gather up the results immediately:
203             my @results = $all_files->in(@starting_points);
204              
205             ## -depth and -follow are noted, but need a bit of help for find:
206             my $deep_dirs = File::Finder->depth->type('d')->ls->exec('rmdir','{}');
207             find($deep_dirs->as_options, @places);
208              
209             =head1 DESCRIPTION
210              
211             C<File::Find> is great, but constructing the C<wanted> routine can
212             sometimes be a pain. This module provides a C<wanted>-writer, using
213             syntax that is directly mappable to the I<find> command's syntax.
214              
215             Also, I find myself (heh) frequently just wanting the list of names
216             that match. With C<File::Find>, I have to write a little accumulator,
217             and then access that from a closure. But with C<File::Finder>, I can
218             turn the problem inside out.
219              
220             A C<File::Finder> object contains a hash of C<File::Find> options, and
221             a series of steps that mimic I<find>'s predicates. Initially, a
222             C<File::Finder> object has no steps. Each step method clones the
223             previous object's options and steps, and then adds the new step,
224             returning the new object. In this manner, an object can be grown,
225             step by step, by chaining method calls. Furthermore, a partial
226             sequence can be created and held, and used as the head of many
227             different sequences.
228              
229             For example, a step sequence that finds only files looks like:
230              
231             my $files = File::Finder->type('f');
232              
233             Here, C<type> is acting as a class method and thus a constructor. An
234             instance of C<File::Finder> is returned, containing the one step to
235             verify that only files are selected. We could use this immediately
236             as a C<File::Find::find> wanted routine, although it'd be uninteresting:
237              
238             use File::Find;
239             find($files, "/tmp");
240              
241             Calling a step method on an existing object adds the step, returning
242             the new object:
243              
244             my $files_print = $files->print;
245              
246             And now if we use this with C<find>, we get a nice display:
247              
248             find($files_print, "/tmp");
249              
250             Of course, we didn't really need that second object: we could
251             have generated it on the fly:
252              
253             find($files->print, "/tmp");
254              
255             C<File::Find> supports options to modify behavior, such as depth-first
256             searching. The C<depth> step flags this in the options as well:
257              
258             my $files_depth_print = $files->depth->print;
259              
260             However, the C<File::Finder> object needs to be told explictly to
261             generate an options hash for C<File::Find::find> to pass this
262             information along:
263              
264             find($files_depth_print->as_options, "/tmp");
265              
266             A C<File::Finder> object, like the I<find> command, supports AND, OR,
267             NOT, and parenthesized sub-expressions. AND binds tighter than OR,
268             and is also implied everywhere that it makes sense. Like I<find>, the
269             predicates are computed in a "short-circuit" fashion, so that a false
270             to the left of the (implied) AND keeps the right side from being
271             evaluated, including entire parenthesized subexpressions. Similarly,
272             if the left side of an OR is false, the right side is evaluated, and
273             if the left side of the OR is true, the right side is skipped. Nested
274             parens are handled properly. Parens are indicated with the rather
275             ugly C<left> and C<right> methods:
276              
277             my $big_or_old_files = $files->left->size("+50")->or->atime("+30")->right;
278              
279             The parens here correspond directly to the parens in:
280              
281             find somewhere -type f '(' -size +50 -o -atime +30 ')'
282              
283             and are needed so that the OR and the implied ANDs have the right
284             nesting.
285              
286             Besides passing the constructed C<File::Finder> object to
287             C<File::Finder::find> directly as a C<wanted> routine or an options
288             hash, you can also call C<find> implictly, with C<in>. C<in> provides
289             a list of starting points, and returns all filenames that match the
290             criteria.
291              
292             For example, a list of all names in /tmp can be generated simply with:
293              
294             my @names = File::Finder->in("/tmp");
295              
296             For more flexibility, use C<collect> to execute an arbitrary block
297             in a list context, concatenating all the results (similar to C<map>):
298              
299             my %sizes = File::Finder
300             ->collect(sub { $File::Find::name => -s _ }, "/tmp");
301              
302             That's all I can think of for now. The rest is in the detailed
303             reference below.
304              
305             =head2 META METHODS
306              
307             All of these methods can be used as class or instance methods, except
308             C<new>, which is usually not needed and is class only.
309              
310             =over
311              
312             =item new
313              
314             Not strictly needed, because any instance method called on a class
315             will create a new object anyway.
316              
317             =item as_wanted
318              
319             Returns a subroutine suitable for passing to C<File::Find::find> or
320             C<File::Find::finddepth> as the I<wanted> routine. If the object is
321             used in a place that wants a coderef, this happens automatically
322             through overloading.
323              
324             =item as_options
325              
326             Returns a hashref suitable for passing to C<File::Find::find> or
327             C<File::Find::finddepth> as the I<options> hash. This is necessary if
328             you want the meta-information to carry forward properly.
329              
330             =item in(@starting_points)
331              
332             Calls C<< File::Find::find($self->as_options, @starting_points) >>,
333             gathering the results, and returns the results as a list. At the
334             moment, it also returns the count of those items in a scalar context.
335             If that's useful, I'll maintain that.
336              
337             =item collect($coderef, @starting_points)
338              
339             Calls C<$coderef> in a list context for each of the matching items,
340             gathering and concatenating the results, and returning the results as
341             a list.
342              
343             my $f = File::Finder->type('f');
344             my %sizes = $f->collect(sub { $File::Find::name, -s _ }, "/tmp");
345              
346             In fact, C<in> is implemented by calling C<collect> with a coderef
347             of just C<sub { $File::Find::name }>.
348              
349             =back
350              
351             =head2 STEPS
352              
353             See L<File::Finder::Steps>.
354              
355             =head2 SPEED
356              
357             All the steps can have a compile-time and run-time component. As
358             much work is done during compile-time as possible. Runtime consists
359             of a simple linear pass executing a series of closures representing
360             the individual steps (not method calls). It is hoped that this will
361             produce a speed that is within a factor of 2 or 3 of a handcrafted
362             monolithic C<wanted> routine.
363              
364             =head1 SEE ALSO
365              
366             L<File::Finder::Steps>, L<File::Find>, L<find2perl>, L<File::Find::Rule>
367              
368             =head1 BUGS
369              
370             Please report bugs to C<bug-File-Finder@rt.cpan.org>.
371              
372             =head1 AUTHOR
373              
374             Randal L. Schwartz, E<lt>merlyn@stonehenge.comE<gt>, with a tip
375             of the hat to Richard Clamp for C<File::Find::Rule>.
376              
377             =head1 COPYRIGHT AND LICENSE
378              
379             Copyright (C) 2003,2004 by Randal L. Schwartz,
380             Stonehenge Consulting Services, Inc.
381              
382             This library is free software; you can redistribute it and/or modify
383             it under the same terms as Perl itself, either Perl version 5.8.2 or,
384             at your option, any later version of Perl 5 you may have available.
385              
386             =cut