File Coverage

blib/lib/File/Find/Rule.pm
Criterion Covered Total %
statement 196 297 65.9
branch 34 42 80.9
condition 4 6 66.6
subroutine 37 39 94.8
pod 13 14 92.8
total 284 398 71.3


line stmt bran cond sub pod time code
1             # $Id$
2              
3             package File::Find::Rule;
4 1     1   21764 use strict;
  1         2  
  1         26  
5 1     1   5 use File::Spec;
  1         1  
  1         21  
6 1     1   808 use Text::Glob 'glob_to_regex';
  1         814  
  1         61  
7 1     1   698 use Number::Compare;
  1         421  
  1         28  
8 1     1   6 use Carp qw/croak/;
  1         2  
  1         40  
9 1     1   5 use File::Find (); # we're only wrapping for now
  1         3  
  1         54  
10              
11             our $VERSION = '0.34';
12              
13             # we'd just inherit from Exporter, but I want the colon
14             sub import {
15 4     4   595 my $pkg = shift;
16 4         10 my $to = caller;
17 4         7 for my $sym ( qw( find rule ) ) {
18 1     1   5 no strict 'refs';
  1         7  
  1         607  
19 8         11 *{"$to\::$sym"} = \&{$sym};
  8         38  
  8         21  
20             }
21 4         47 for (grep /^:/, @_) {
22 2         9 my ($extension) = /^:(.*)/;
23 2         135 eval "require File::Find::Rule::$extension";
24 2 100       207 croak "couldn't bootstrap File::Find::Rule::$extension: $@" if $@;
25             }
26             }
27              
28             =head1 NAME
29              
30             File::Find::Rule - Alternative interface to File::Find
31              
32             =head1 SYNOPSIS
33              
34             use File::Find::Rule;
35             # find all the subdirectories of a given directory
36             my @subdirs = File::Find::Rule->directory->in( $directory );
37              
38             # find all the .pm files in @INC
39             my @files = File::Find::Rule->file()
40             ->name( '*.pm' )
41             ->in( @INC );
42              
43             # as above, but without method chaining
44             my $rule = File::Find::Rule->new;
45             $rule->file;
46             $rule->name( '*.pm' );
47             my @files = $rule->in( @INC );
48              
49             =head1 DESCRIPTION
50              
51             File::Find::Rule is a friendlier interface to File::Find. It allows
52             you to build rules which specify the desired files and directories.
53              
54             =cut
55              
56             # the procedural shim
57              
58             *rule = \&find;
59             sub find {
60 29     29 0 1047 my $object = __PACKAGE__->new();
61 29         57 my $not = 0;
62              
63 29         84 while (@_) {
64 77         143 my $method = shift;
65 77         106 my @args;
66              
67 77 100       236 if ($method =~ s/^\!//) {
68             # jinkies, we're really negating this
69 1         4 unshift @_, $method;
70 1         2 $not = 1;
71 1         4 next;
72             }
73 76 100       281 unless (defined prototype $method) {
74 55         82 my $args = shift;
75 55 100       196 @args = ref $args eq 'ARRAY' ? @$args : $args;
76             }
77 76 100       178 if ($not) {
78 1         2 $not = 0;
79 1         3 @args = $object->new->$method(@args);
80 1         3 $method = "not";
81             }
82              
83 76         622 my @return = $object->$method(@args);
84 76 100       531 return @return if $method eq 'in';
85             }
86 13         43 $object;
87             }
88              
89              
90             =head1 METHODS
91              
92             =over
93              
94             =item C
95              
96             A constructor. You need not invoke C manually unless you wish
97             to, as each of the rule-making methods will auto-create a suitable
98             object if called as class methods.
99              
100             =cut
101              
102             sub new {
103 58     58 1 483 my $referent = shift;
104 58   66     267 my $class = ref $referent || $referent;
105 58         411 bless {
106             rules => [],
107             subs => {},
108             iterator => [],
109             extras => {},
110             maxdepth => undef,
111             mindepth => undef,
112             }, $class;
113             }
114              
115             sub _force_object {
116 162     162   244 my $object = shift;
117 162 100       443 $object = $object->new()
118             unless ref $object;
119 162         795 $object;
120             }
121              
122             =back
123              
124             =head2 Matching Rules
125              
126             =over
127              
128             =item C
129              
130             Specifies names that should match. May be globs or regular
131             expressions.
132              
133             $set->name( '*.mp3', '*.ogg' ); # mp3s or oggs
134             $set->name( qr/\.(mp3|ogg)$/ ); # the same as a regex
135             $set->name( 'foo.bar' ); # just things named foo.bar
136              
137             =cut
138              
139             sub _flatten {
140 22     22   36 my @flat;
141 22         74 while (@_) {
142 25         39 my $item = shift;
143 25 100       110 ref $item eq 'ARRAY' ? push @_, @{ $item } : push @flat, $item;
  1         5  
144             }
145 22         58 return @flat;
146             }
147              
148             sub name {
149 22     22 1 504 my $self = _force_object shift;
150 22 100       61 my @names = map { ref $_ eq "Regexp" ? $_ : glob_to_regex $_ } _flatten( @_ );
  24         352  
151              
152 22         83 push @{ $self->{rules} }, {
153             rule => 'name',
154 22         1190 code => join( ' || ', map { "m{$_}" } @names ),
  24         182  
155             args => \@_,
156             };
157              
158 22         121 $self;
159             }
160              
161             =item -X tests
162              
163             Synonyms are provided for each of the -X tests. See L for
164             details. None of these methods take arguments.
165              
166             Test | Method Test | Method
167             ------|------------- ------|----------------
168             -r | readable -R | r_readable
169             -w | writeable -W | r_writeable
170             -w | writable -W | r_writable
171             -x | executable -X | r_executable
172             -o | owned -O | r_owned
173             | |
174             -e | exists -f | file
175             -z | empty -d | directory
176             -s | nonempty -l | symlink
177             | -p | fifo
178             -u | setuid -S | socket
179             -g | setgid -b | block
180             -k | sticky -c | character
181             | -t | tty
182             -M | modified |
183             -A | accessed -T | ascii
184             -C | changed -B | binary
185              
186             Though some tests are fairly meaningless as binary flags (C,
187             C, C), they have been included for completeness.
188              
189             # find nonempty files
190             $rule->file,
191             ->nonempty;
192              
193             =cut
194              
195 1     1   5 use vars qw( %X_tests );
  1         2  
  1         153  
196             %X_tests = (
197             -r => readable => -R => r_readable =>
198             -w => writeable => -W => r_writeable =>
199             -w => writable => -W => r_writable =>
200             -x => executable => -X => r_executable =>
201             -o => owned => -O => r_owned =>
202              
203             -e => exists => -f => file =>
204             -z => empty => -d => directory =>
205             -s => nonempty => -l => symlink =>
206             => -p => fifo =>
207             -u => setuid => -S => socket =>
208             -g => setgid => -b => block =>
209             -k => sticky => -c => character =>
210             => -t => tty =>
211             -M => modified =>
212             -A => accessed => -T => ascii =>
213             -C => changed => -B => binary =>
214             );
215              
216             for my $test (keys %X_tests) {
217             my $sub = eval 'sub () {
218             my $self = _force_object shift;
219             push @{ $self->{rules} }, {
220             code => "' . $test . ' \$_",
221 0     0   0 rule => "'.$X_tests{$test}.'",
  0         0  
  0         0  
  0         0  
  13         52  
  13         29  
  13         74  
  13         80  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  3         13  
  3         8  
  3         16  
  3         19  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
222             };
223             $self;
224             } ';
225 1     1   5 no strict 'refs';
  1         1  
  1         49  
226             *{ $X_tests{$test} } = $sub;
227             }
228              
229              
230             =item stat tests
231              
232             The following C based methods are provided: C, C,
233             C, C, C, C, C, C, C,
234             C, C, C, and C. See L
235             for details.
236              
237             Each of these can take a number of targets, which will follow
238             L semantics.
239              
240             $rule->size( 7 ); # exactly 7
241             $rule->size( ">7Ki" ); # larger than 7 * 1024 * 1024 bytes
242             $rule->size( ">=7" )
243             ->size( "<=90" ); # between 7 and 90, inclusive
244             $rule->size( 7, 9, 42 ); # 7, 9 or 42
245              
246             =cut
247              
248 1     1   4 use vars qw( @stat_tests );
  1         2  
  1         189  
249             @stat_tests = qw( dev ino mode nlink uid gid rdev
250             size atime mtime ctime blksize blocks );
251             {
252             my $i = 0;
253             for my $test (@stat_tests) {
254             my $index = $i++; # to close over
255             my $sub = sub {
256 7     7   20 my $self = _force_object shift;
257              
258 7         18 my @tests = map { Number::Compare->parse_to_perl($_) } @_;
  7         49  
259              
260 7         34 push @{ $self->{rules} }, {
261             rule => $test,
262             args => \@_,
263             code => 'do { my $val = (stat $_)['.$index.'] || 0;'.
264 7         201 join ('||', map { "(\$val $_)" } @tests ).' }',
  7         45  
265             };
266 7         64 $self;
267             };
268 1     1   4 no strict 'refs';
  1         2  
  1         846  
269             *$test = $sub;
270             }
271             }
272              
273             =item C
274              
275             =item C
276              
277             Allows shortcircuiting boolean evaluation as an alternative to the
278             default and-like nature of combined rules. C and C are
279             interchangeable.
280              
281             # find avis, movs, things over 200M and empty files
282             $rule->any( File::Find::Rule->name( '*.avi', '*.mov' ),
283             File::Find::Rule->size( '>200M' ),
284             File::Find::Rule->file->empty,
285             );
286              
287             =cut
288              
289             sub any {
290 10     10 1 21 my $self = _force_object shift;
291             # compile all the subrules to code fragments
292 10         16 push @{ $self->{rules} }, {
  10         43  
293             rule => "any",
294             code => '(' . join( ' || ', map '( ' . $_->_compile . ' )', @_ ). ')',
295             args => \@_,
296             };
297              
298             # merge all the subs hashes of the kids into ourself
299 10         27 %{ $self->{subs} } = map { %{ $_->{subs} } } $self, @_;
  10         24  
  28         30  
  28         99  
300 10         32 $self;
301             }
302              
303             *or = \&any;
304              
305             =item C
306              
307             =item C
308              
309             Negates a rule. (The inverse of C.) C and C are
310             interchangeable.
311              
312             # files that aren't 8.3 safe
313             $rule->file
314             ->not( $rule->new->name( qr/^[^.]{1,8}(\.[^.]{0,3})?$/ ) );
315              
316             =cut
317              
318             sub not {
319 3     3 1 8 my $self = _force_object shift;
320              
321 3         12 push @{ $self->{rules} }, {
322             rule => 'not',
323             args => \@_,
324 3         8 code => '(' . join ( ' && ', map { "!(". $_->_compile . ")" } @_ ) . ")",
  3         12  
325             };
326              
327             # merge all the subs hashes into us
328 3         9 %{ $self->{subs} } = map { %{ $_->{subs} } } $self, @_;
  3         11  
  6         9  
  6         21  
329 3         22 $self;
330             }
331              
332             *none = \¬
333              
334             =item C
335              
336             Traverse no further. This rule always matches.
337              
338             =cut
339              
340             sub prune () {
341 4     4 1 14 my $self = _force_object shift;
342              
343 4         7 push @{ $self->{rules} },
  4         24  
344             {
345             rule => 'prune',
346             code => '$File::Find::prune = 1'
347             };
348 4         13 $self;
349             }
350              
351             =item C
352              
353             Don't keep this file. This rule always matches.
354              
355             =cut
356              
357             sub discard () {
358 6     6 1 13 my $self = _force_object shift;
359              
360 6         10 push @{ $self->{rules} }, {
  6         24  
361             rule => 'discard',
362             code => '$discarded = 1',
363             };
364 6         17 $self;
365             }
366              
367             =item C
368              
369             Allows user-defined rules. Your subroutine will be invoked with C<$_>
370             set to the current short name, and with parameters of the name, the
371             path you're in, and the full relative filename.
372              
373             Return a true value if your rule matched.
374              
375             # get things with long names
376             $rules->exec( sub { length > 20 } );
377              
378             =cut
379              
380             sub exec {
381 14     14 1 37 my $self = _force_object shift;
382 14         26 my $code = shift;
383              
384 14         20 push @{ $self->{rules} }, {
  14         57  
385             rule => 'exec',
386             code => $code,
387             };
388 14         74 $self;
389             }
390              
391             =item C
392              
393             Opens a file and tests it each line at a time.
394              
395             For each line it evaluates each of the specifiers, stopping at the
396             first successful match. A specifier may be a regular expression or a
397             subroutine. The subroutine will be invoked with the same parameters
398             as an ->exec subroutine.
399              
400             It is possible to provide a set of negative specifiers by enclosing
401             them in anonymous arrays. Should a negative specifier match the
402             iteration is aborted and the clause is failed. For example:
403              
404             $rule->grep( qr/^#!.*\bperl/, [ sub { 1 } ] );
405              
406             Is a passing clause if the first line of a file looks like a perl
407             shebang line.
408              
409             =cut
410              
411             sub grep {
412 1     1 1 4 my $self = _force_object shift;
413             my @pattern = map {
414 1         3 ref $_
415             ? ref $_ eq 'ARRAY'
416 2 50       12 ? map { [ ( ref $_ ? $_ : qr/$_/ ) => 0 ] } @$_
  1 100       6  
    50          
417             : [ $_ => 1 ]
418             : [ qr/$_/ => 1 ]
419             } @_;
420              
421             $self->exec( sub {
422 3     3   9 local *FILE;
423 3 50       78 open FILE, $_ or return;
424 3         11 local ($_, $.);
425 3         49 while () {
426 3         6 for my $p (@pattern) {
427 5         11 my ($rule, $ret) = @$p;
428 5 50       175 return $ret
    100          
429             if ref $rule eq 'Regexp'
430             ? /$rule/
431             : $rule->(@_);
432             }
433             }
434 0         0 return;
435 1         7 } );
436             }
437              
438             =item C
439              
440             Descend at most C<$level> (a non-negative integer) levels of directories
441             below the starting point.
442              
443             May be invoked many times per rule, but only the most recent value is
444             used.
445              
446             =item C
447              
448             Do not apply any tests at levels less than C<$level> (a non-negative
449             integer).
450              
451             =item C
452              
453             Specifies extra values to pass through to C as part
454             of the options hash.
455              
456             For example this allows you to specify following of symlinks like so:
457              
458             my $rule = File::Find::Rule->extras({ follow => 1 });
459              
460             May be invoked many times per rule, but only the most recent value is
461             used.
462              
463             =cut
464              
465             for my $setter (qw( maxdepth mindepth extras )) {
466             my $sub = sub {
467 23     23   52 my $self = _force_object shift;
468 23         58 $self->{$setter} = shift;
469 23         72 $self;
470             };
471 1     1   6 no strict 'refs';
  1         2  
  1         245  
472             *$setter = $sub;
473             }
474              
475              
476             =item C
477              
478             Trim the leading portion of any path found
479              
480             =cut
481              
482             sub relative () {
483 1     1 1 7 my $self = _force_object shift;
484 1         5 $self->{relative} = 1;
485 1         7 $self;
486             }
487              
488             =item C
489              
490             Normalize paths found using Ccanonpath>. This will return paths
491             with a file-seperator that is native to your OS (as determined by L),
492             instead of the default C.
493              
494             For example, this will return C on Unix-ish OSes
495             and C on Win32.
496              
497             =cut
498              
499             sub canonpath () {
500 1     1 1 6 my $self = _force_object shift;
501 1         5 $self->{canonpath} = 1;
502 1         7 $self;
503             }
504              
505             =item C
506              
507             Negated version of the rule. An effective shortand related to ! in
508             the procedural interface.
509              
510             $foo->not_name('*.pl');
511              
512             $foo->not( $foo->new->name('*.pl' ) );
513              
514             =cut
515              
516       0     sub DESTROY {}
517             sub AUTOLOAD {
518 1     1   3 our $AUTOLOAD;
519 1 50       10 $AUTOLOAD =~ /::not_([^:]*)$/
520             or croak "Can't locate method $AUTOLOAD";
521 1         5 my $method = $1;
522              
523             my $sub = sub {
524 1     1   3 my $self = _force_object shift;
525 1         5 $self->not( $self->new->$method(@_) );
526 1         6 };
527             {
528 1     1   5 no strict 'refs';
  1         1  
  1         599  
  1         3  
529 1         6 *$AUTOLOAD = $sub;
530             }
531 1         4 &$sub;
532             }
533              
534             =back
535              
536             =head2 Query Methods
537              
538             =over
539              
540             =item C
541              
542             Evaluates the rule, returns a list of paths to matching files and
543             directories.
544              
545             =cut
546              
547             sub in {
548 41     41 1 284 my $self = _force_object shift;
549              
550 41         61 my @found;
551 41         109 my $fragment = $self->_compile;
552 41         73 my %subs = %{ $self->{subs} };
  41         139  
553              
554             warn "relative mode handed multiple paths - that's a bit silly\n"
555 41 50 66     143 if $self->{relative} && @_ > 1;
556              
557 41         52 my $topdir;
558 41         154 my $code = 'sub {
559             (my $path = $File::Find::name) =~ s#^(?:\./+)+##;
560             my @args = ($_, $File::Find::dir, $path);
561             my $maxdepth = $self->{maxdepth};
562             my $mindepth = $self->{mindepth};
563             my $relative = $self->{relative};
564             my $canonpath = $self->{canonpath};
565              
566             # figure out the relative path and depth
567             my $relpath = $File::Find::name;
568             $relpath =~ s{^\Q$topdir\E/?}{};
569             my $depth = scalar File::Spec->splitdir($relpath);
570             #print "name: \'$File::Find::name\' ";
571             #print "relpath: \'$relpath\' depth: $depth relative: $relative\n";
572              
573             defined $maxdepth && $depth >= $maxdepth
574             and $File::Find::prune = 1;
575              
576             defined $mindepth && $depth < $mindepth
577             and return;
578              
579             #print "Testing \'$_\'\n";
580              
581             my $discarded;
582             return unless ' . $fragment . ';
583             return if $discarded;
584             if ($relative) {
585             if ($relpath ne "") {
586             push @found, $canonpath ? File::Spec->canonpath($relpath) : $relpath;
587             }
588             }
589             else {
590             push @found, $canonpath ? File::Spec->canonpath($path) : $path;
591             }
592             }';
593              
594             #use Data::Dumper;
595             #print Dumper \%subs;
596             #warn "Compiled sub: '$code'\n";
597              
598 41 50       15263 my $sub = eval "$code" or die "compile error '$code' $@";
599 41         126 for my $path (@_) {
600             # $topdir is used for relative and maxdepth
601 41         67 $topdir = $path;
602             # slice off the trailing slash if there is one (the
603             # maxdepth/mindepth code is fussy)
604 41 50       264 $topdir =~ s{/?$}{}
605             unless $topdir eq '/';
606 41         75 $self->_call_find( { %{ $self->{extras} }, wanted => $sub }, $path );
  41         226  
607             }
608              
609 41         2095 return @found;
610             }
611              
612             sub _call_find {
613 41     41   74 my $self = shift;
614 41         4137 File::Find::find( @_ );
615             }
616              
617             sub _compile {
618 62     62   104 my $self = shift;
619              
620 62 100       77 return '1' unless @{ $self->{rules} };
  62         215  
621             my $code = join " && ", map {
622 87 100       1723 if (ref $_->{code}) {
623 14         40 my $key = "$_->{code}";
624 14         41 $self->{subs}{$key} = $_->{code};
625 14         64 "\$subs{'$key'}->(\@args) # $_->{rule}\n";
626             }
627             else {
628 73         308 "( $_->{code} ) # $_->{rule}\n";
629             }
630 56         92 } @{ $self->{rules} };
  56         126  
631              
632             #warn $code;
633 56         218 return $code;
634             }
635              
636             =item C
637              
638             Starts a find across the specified directories. Matching items may
639             then be queried using L. This allows you to use a rule as an
640             iterator.
641              
642             my $rule = File::Find::Rule->file->name("*.jpeg")->start( "/web" );
643             while ( defined ( my $image = $rule->match ) ) {
644             ...
645             }
646              
647             =cut
648              
649             sub start {
650 1     1 1 4 my $self = _force_object shift;
651              
652 1         4 $self->{iterator} = [ $self->in( @_ ) ];
653 1         4 $self;
654             }
655              
656             =item C
657              
658             Returns the next file which matches, false if there are no more.
659              
660             =cut
661              
662             sub match {
663 11     11 1 63 my $self = _force_object shift;
664              
665 11         16 return shift @{ $self->{iterator} };
  11         25  
666             }
667              
668             1;
669              
670             __END__