Blame lib/File/Find/Object/Rule.pm

Packit 26bf30
#       $Id: /mirror/lab/perl/File-Find-Rule/lib/File/Find/Rule.pm 2102 2006-06-01T15:39:03.942922Z richardc  $
Packit 26bf30
Packit 26bf30
package File::Find::Object::Rule;
Packit 26bf30
Packit 26bf30
use strict;
Packit 26bf30
use warnings;
Packit 26bf30
Packit 26bf30
use 5.008;
Packit 26bf30
Packit 26bf30
use vars qw/$VERSION $AUTOLOAD/;
Packit 26bf30
use File::Spec;
Packit 26bf30
use Text::Glob 'glob_to_regex';
Packit 26bf30
use Number::Compare;
Packit 26bf30
use Carp qw/croak/;
Packit 26bf30
use File::Find::Object; # we're only wrapping for now
Packit 26bf30
use File::Basename;
Packit 26bf30
use Cwd;           # 5.00503s File::Find goes screwy with max_depth == 0
Packit 26bf30
Packit 26bf30
$VERSION = '0.0306';
Packit 26bf30
Packit 26bf30
use Class::XSAccessor
Packit 26bf30
    accessors => {
Packit 26bf30
        "extras" => "extras",
Packit 26bf30
        "finder" => "finder",
Packit 26bf30
        "_match_cb" => "_match_cb",
Packit 26bf30
        "rules" => "rules",
Packit 26bf30
        "_relative" => "_relative",
Packit 26bf30
        "_subs" => "_subs",
Packit 26bf30
        "_maxdepth" => "_maxdepth",
Packit 26bf30
        "_mindepth" => "_mindepth",
Packit 26bf30
    }
Packit 26bf30
    ;
Packit 26bf30
Packit 26bf30
# we'd just inherit from Exporter, but I want the colon
Packit 26bf30
sub import {
Packit 26bf30
    my $pkg = shift;
Packit 26bf30
    my $to  = caller;
Packit 26bf30
    for my $sym ( qw( find rule ) ) {
Packit 26bf30
        no strict 'refs';
Packit 26bf30
        *{"$to\::$sym"} = \&{$sym};
Packit 26bf30
    }
Packit 26bf30
    for (grep /^:/, @_) {
Packit 26bf30
        my ($extension) = /^:(.*)/;
Packit 26bf30
        eval "require File::Find::Object::Rule::$extension";
Packit 26bf30
        croak "couldn't bootstrap File::Find::Object::Rule::$extension: $@" if $@;
Packit 26bf30
    }
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
=encoding utf8
Packit 26bf30
Packit 26bf30
=head1 NAME
Packit 26bf30
Packit 26bf30
File::Find::Object::Rule - Alternative interface to File::Find::Object
Packit 26bf30
Packit 26bf30
=head1 SYNOPSIS
Packit 26bf30
Packit 26bf30
  use File::Find::Object::Rule;
Packit 26bf30
  # find all the subdirectories of a given directory
Packit 26bf30
  my @subdirs = File::Find::Object::Rule->directory->in( $directory );
Packit 26bf30
Packit 26bf30
  # find all the .pm files in @INC
Packit 26bf30
  my @files = File::Find::Object::Rule->file()
Packit 26bf30
                              ->name( '*.pm' )
Packit 26bf30
                              ->in( @INC );
Packit 26bf30
Packit 26bf30
  # as above, but without method chaining
Packit 26bf30
  my $rule =  File::Find::Object::Rule->new;
Packit 26bf30
  $rule->file;
Packit 26bf30
  $rule->name( '*.pm' );
Packit 26bf30
  my @files = $rule->in( @INC );
Packit 26bf30
Packit 26bf30
=head1 DESCRIPTION
Packit 26bf30
Packit 26bf30
File::Find::Object::Rule is a friendlier interface to L<File::Find::Object> .
Packit 26bf30
It allows you to build rules which specify the desired files and directories.
Packit 26bf30
Packit 26bf30
B<WARNING> : This module is a fork of version 0.30 of L<File::Find::Rule>
Packit 26bf30
(which has been unmaintained for several years as of February, 2009), and may
Packit 26bf30
still have some bugs due to its reliance on File::Find'isms. As such it is
Packit 26bf30
considered Alpha software. Please report any problems with
Packit 26bf30
L<File::Find::Object::Rule> to its RT CPAN Queue.
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
# the procedural shim
Packit 26bf30
Packit 26bf30
*rule = \&fin;;
Packit 26bf30
sub find {
Packit 26bf30
    my $object = __PACKAGE__->new();
Packit 26bf30
    my $not = 0;
Packit 26bf30
Packit 26bf30
    while (@_) {
Packit 26bf30
        my $method = shift;
Packit 26bf30
        my @args;
Packit 26bf30
Packit 26bf30
        if ($method =~ s/^\!//) {
Packit 26bf30
            # jinkies, we're really negating this
Packit 26bf30
            unshift @_, $method;
Packit 26bf30
            $not = 1;
Packit 26bf30
            next;
Packit 26bf30
        }
Packit 26bf30
        unless (defined prototype $method) {
Packit 26bf30
            my $args = shift;
Packit 26bf30
            @args = ref $args eq 'ARRAY' ? @$args : $args;
Packit 26bf30
        }
Packit 26bf30
        if ($not) {
Packit 26bf30
            $not = 0;
Packit 26bf30
            @args = ref($object)->new->$method(@args);
Packit 26bf30
            $method = "not";
Packit 26bf30
        }
Packit 26bf30
Packit 26bf30
        my @return = $object->$method(@args);
Packit 26bf30
        return @return if $method eq 'in';
Packit 26bf30
    }
Packit 26bf30
    $object;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
Packit 26bf30
=head1 METHODS
Packit 26bf30
Packit 26bf30
=over
Packit 26bf30
Packit 26bf30
=item C<new>
Packit 26bf30
Packit 26bf30
A constructor.  You need not invoke C<new> manually unless you wish
Packit 26bf30
to, as each of the rule-making methods will auto-create a suitable
Packit 26bf30
object if called as class methods.
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
sub new {
Packit 26bf30
    # We need this to maintain compatibility with File-Find-Object.
Packit 26bf30
    # However, Randal Schwartz recommends against this practice in general:
Packit 26bf30
    # http://www.stonehenge.com/merlyn/UnixReview/col52.html
Packit 26bf30
    my $referent = shift;
Packit 26bf30
    my $class = ref $referent || $referent;
Packit 26bf30
Packit 26bf30
    return
Packit 26bf30
    bless {
Packit 26bf30
        rules    => [],  # [0]
Packit 26bf30
        _subs     => [],  # [1]
Packit 26bf30
        iterator => [],
Packit 26bf30
        extras   => {},
Packit 26bf30
        _maxdepth => undef,
Packit 26bf30
        _mindepth => undef,
Packit 26bf30
        _relative => 0,
Packit 26bf30
    }, $class;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
sub _force_object {
Packit 26bf30
    my $object = shift;
Packit 26bf30
    if (! ref($object))
Packit 26bf30
    {
Packit 26bf30
        $object = $object->new();
Packit 26bf30
    }
Packit 26bf30
    return $object;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
=back
Packit 26bf30
Packit 26bf30
=head2 finder
Packit 26bf30
Packit 26bf30
The L<File::Find::Object> finder instance itself.
Packit 26bf30
Packit 26bf30
=head2 my @rules = @{$ffor->rules()};
Packit 26bf30
Packit 26bf30
The rules to match against. For internal use only.
Packit 26bf30
Packit 26bf30
=head2 Matching Rules
Packit 26bf30
Packit 26bf30
=over
Packit 26bf30
Packit 26bf30
=item C<name( @patterns )>
Packit 26bf30
Packit 26bf30
Specifies names that should match.  May be globs or regular
Packit 26bf30
expressions.
Packit 26bf30
Packit 26bf30
 $set->name( '*.mp3', '*.ogg' ); # mp3s or oggs
Packit 26bf30
 $set->name( qr/\.(mp3|ogg)$/ ); # the same as a regex
Packit 26bf30
 $set->name( 'foo.bar' );        # just things named foo.bar
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
sub _flatten {
Packit 26bf30
    my @flat;
Packit 26bf30
    while (@_) {
Packit 26bf30
        my $item = shift;
Packit 26bf30
        ref $item eq 'ARRAY' ? push @_, @{ $item } : push @flat, $item;
Packit 26bf30
    }
Packit 26bf30
    return @flat;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
sub _add_rule {
Packit 26bf30
    my $self = shift;
Packit 26bf30
    my $new_rule = shift;
Packit 26bf30
Packit 26bf30
    push @{$self->rules()}, $new_rule;
Packit 26bf30
Packit 26bf30
    return;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
sub name {
Packit 26bf30
    my $self = _force_object shift;
Packit 26bf30
    my @names = map { ref $_ eq "Regexp" ? $_ : glob_to_regex $_ } _flatten( @_ );
Packit 26bf30
Packit 26bf30
    $self->_add_rule(
Packit 26bf30
        {
Packit 26bf30
            rule => 'name',
Packit 26bf30
            code => join( ' || ', map { "m($_)" } @names ),
Packit 26bf30
            args => \@_,
Packit 26bf30
        }
Packit 26bf30
    );
Packit 26bf30
Packit 26bf30
    $self;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
=item -X tests
Packit 26bf30
Packit 26bf30
Synonyms are provided for each of the -X tests. See L<perlfunc/-X> for
Packit 26bf30
details.  None of these methods take arguments.
Packit 26bf30
Packit 26bf30
  Test | Method               Test |  Method
Packit 26bf30
 ------|-------------        ------|----------------
Packit 26bf30
   -r  |  readable             -R  |  r_readable
Packit 26bf30
   -w  |  writeable            -W  |  r_writeable
Packit 26bf30
   -w  |  writable             -W  |  r_writable
Packit 26bf30
   -x  |  executable           -X  |  r_executable
Packit 26bf30
   -o  |  owned                -O  |  r_owned
Packit 26bf30
       |                           |
Packit 26bf30
   -e  |  exists               -f  |  file
Packit 26bf30
   -z  |  empty                -d  |  directory
Packit 26bf30
   -s  |  nonempty             -l  |  symlink
Packit 26bf30
       |                       -p  |  fifo
Packit 26bf30
   -u  |  setuid               -S  |  socket
Packit 26bf30
   -g  |  setgid               -b  |  block
Packit 26bf30
   -k  |  sticky               -c  |  character
Packit 26bf30
       |                       -t  |  tty
Packit 26bf30
   -M  |  modified                 |
Packit 26bf30
   -A  |  accessed             -T  |  ascii
Packit 26bf30
   -C  |  changed              -B  |  binary
Packit 26bf30
Packit 26bf30
Though some tests are fairly meaningless as binary flags (C<modified>,
Packit 26bf30
C<accessed>, C<changed>), they have been included for completeness.
Packit 26bf30
Packit 26bf30
 # find nonempty files
Packit 26bf30
 $rule->file,
Packit 26bf30
      ->nonempty;
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
use vars qw( %X_tests );
Packit 26bf30
%X_tests = (
Packit 26bf30
    -r  =>  readable           =>  -R  =>  r_readable      =>
Packit 26bf30
    -w  =>  writeable          =>  -W  =>  r_writeable     =>
Packit 26bf30
    -w  =>  writable           =>  -W  =>  r_writable      =>
Packit 26bf30
    -x  =>  executable         =>  -X  =>  r_executable    =>
Packit 26bf30
    -o  =>  owned              =>  -O  =>  r_owned         =>
Packit 26bf30
Packit 26bf30
    -e  =>  exists             =>  -f  =>  file            =>
Packit 26bf30
    -z  =>  empty              =>  -d  =>  directory       =>
Packit 26bf30
    -s  =>  nonempty           =>  -l  =>  symlink         =>
Packit 26bf30
                               =>  -p  =>  fifo            =>
Packit 26bf30
    -u  =>  setuid             =>  -S  =>  socket          =>
Packit 26bf30
    -g  =>  setgid             =>  -b  =>  block           =>
Packit 26bf30
    -k  =>  sticky             =>  -c  =>  character       =>
Packit 26bf30
                               =>  -t  =>  tty             =>
Packit 26bf30
    -M  =>  modified                                       =>
Packit 26bf30
    -A  =>  accessed           =>  -T  =>  ascii           =>
Packit 26bf30
    -C  =>  changed            =>  -B  =>  binary          =>
Packit 26bf30
   );
Packit 26bf30
Packit 26bf30
for my $test (keys %X_tests) {
Packit 26bf30
    my $sub = eval 'sub () {
Packit 26bf30
        my $self = _force_object shift;
Packit 26bf30
        $self->_add_rule({
Packit 26bf30
            code => "' . $test . ' \$path",
Packit 26bf30
            rule => "'.$X_tests{$test}.'",
Packit 26bf30
        });
Packit 26bf30
        $self;
Packit 26bf30
    } ';
Packit 26bf30
    no strict 'refs';
Packit 26bf30
    *{ $X_tests{$test} } = $sub;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
Packit 26bf30
=item stat tests
Packit 26bf30
Packit 26bf30
The following C<stat> based methods are provided: C<dev>, C<ino>,
Packit 26bf30
C<mode>, C<nlink>, C<uid>, C<gid>, C<rdev>, C<size>, C<atime>,
Packit 26bf30
C<mtime>, C<ctime>, C<blksize>, and C<blocks>.  See L<perlfunc/stat>
Packit 26bf30
for details.
Packit 26bf30
Packit 26bf30
Each of these can take a number of targets, which will follow
Packit 26bf30
L<Number::Compare> semantics.
Packit 26bf30
Packit 26bf30
 $rule->size( 7 );         # exactly 7
Packit 26bf30
 $rule->size( ">7Ki" );    # larger than 7 * 1024 * 1024 bytes
Packit 26bf30
 $rule->size( ">=7" )
Packit 26bf30
      ->size( "<=90" );    # between 7 and 90, inclusive
Packit 26bf30
 $rule->size( 7, 9, 42 );  # 7, 9 or 42
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
use vars qw( @stat_tests );
Packit 26bf30
@stat_tests = qw( dev ino mode nlink uid gid rdev
Packit 26bf30
                  size atime mtime ctime blksize blocks );
Packit 26bf30
{
Packit 26bf30
    my $i = 0;
Packit 26bf30
    for my $test (@stat_tests) {
Packit 26bf30
        my $index = $i++; # to close over
Packit 26bf30
        my $sub = sub {
Packit 26bf30
            my $self = _force_object shift;
Packit 26bf30
Packit 26bf30
            my @tests = map { Number::Compare->parse_to_perl($_) } @_;
Packit 26bf30
Packit 26bf30
            $self->_add_rule({
Packit 26bf30
                rule => $test,
Packit 26bf30
                args => \@_,
Packit 26bf30
                code => 'do { my $val = (stat $path)['.$index.'] || 0;'.
Packit 26bf30
                  join ('||', map { "(\$val $_)" } @tests ).' }',
Packit 26bf30
            });
Packit 26bf30
            $self;
Packit 26bf30
        };
Packit 26bf30
        no strict 'refs';
Packit 26bf30
        *$test = $sub;
Packit 26bf30
    }
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
=item C<any( @rules )>
Packit 26bf30
Packit 26bf30
=item C<or( @rules )>
Packit 26bf30
Packit 26bf30
Allows shortcircuiting boolean evaluation as an alternative to the
Packit 26bf30
default and-like nature of combined rules.  C<any> and C<or> are
Packit 26bf30
interchangeable.
Packit 26bf30
Packit 26bf30
 # find avis, movs, things over 200M and empty files
Packit 26bf30
 $rule->any( File::Find::Object::Rule->name( '*.avi', '*.mov' ),
Packit 26bf30
             File::Find::Object::Rule->size( '>200M' ),
Packit 26bf30
             File::Find::Object::Rule->file->empty,
Packit 26bf30
           );
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
sub any {
Packit 26bf30
    my $self = _force_object shift;
Packit 26bf30
    my @rulesets = @_;
Packit 26bf30
Packit 26bf30
    $self->_add_rule({
Packit 26bf30
        rule => 'any',
Packit 26bf30
        code => '(' . join( ' || ', map {
Packit 26bf30
            "( " . $_->_compile($self->_subs()) . " )"
Packit 26bf30
        } @rulesets ) . ")",
Packit 26bf30
        args => \@rulesets,
Packit 26bf30
    });
Packit 26bf30
    $self;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
*or = \&any;
Packit 26bf30
Packit 26bf30
=item C<none( @rules )>
Packit 26bf30
Packit 26bf30
=item C<not( @rules )>
Packit 26bf30
Packit 26bf30
Negates a rule.  (The inverse of C<any>.)  C<none> and C<not> are
Packit 26bf30
interchangeable.
Packit 26bf30
Packit 26bf30
  # files that aren't 8.3 safe
Packit 26bf30
  $rule->file
Packit 26bf30
       ->not( $rule->new->name( qr/^[^.]{1,8}(\.[^.]{0,3})?$/ ) );
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
sub not {
Packit 26bf30
    my $self = _force_object shift;
Packit 26bf30
    my @rulesets = @_;
Packit 26bf30
Packit 26bf30
    $self->_add_rule({
Packit 26bf30
        rule => 'not',
Packit 26bf30
        args => \@rulesets,
Packit 26bf30
        code => '(' . join ( ' && ', map {
Packit 26bf30
            "!(". $_->_compile($self->_subs()) . ")"
Packit 26bf30
        } @_ ) . ")",
Packit 26bf30
    });
Packit 26bf30
    $self;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
*none = \¬
Packit 26bf30
Packit 26bf30
=item C<prune>
Packit 26bf30
Packit 26bf30
Traverse no further.  This rule always matches.
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
sub prune () {
Packit 26bf30
    my $self = _force_object shift;
Packit 26bf30
Packit 26bf30
    $self->_add_rule(
Packit 26bf30
        {
Packit 26bf30
            rule => 'prune',
Packit 26bf30
            code => 'do { $self->finder->prune(); 1 }'
Packit 26bf30
        },
Packit 26bf30
    );
Packit 26bf30
Packit 26bf30
    return $self;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
=item C<discard>
Packit 26bf30
Packit 26bf30
Don't keep this file.  This rule always matches.
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
sub discard () {
Packit 26bf30
    my $self = _force_object shift;
Packit 26bf30
Packit 26bf30
    $self->_add_rule({
Packit 26bf30
        rule => 'discard',
Packit 26bf30
        code => '$discarded = 1',
Packit 26bf30
    });
Packit 26bf30
Packit 26bf30
    return $self;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
=item C<exec( \&subroutine( $shortname, $path, $fullname ) )>
Packit 26bf30
Packit 26bf30
Allows user-defined rules.  Your subroutine will be invoked with parameters of
Packit 26bf30
the name, the path you're in, and the full relative filename.
Packit 26bf30
In addition, C<$_> is set to the current short name, but its use is
Packit 26bf30
discouraged since as opposed to File::Find::Rule, File::Find::Object::Rule
Packit 26bf30
does not cd to the containing directory.
Packit 26bf30
Packit 26bf30
Return a true value if your rule matched.
Packit 26bf30
Packit 26bf30
 # get things with long names
Packit 26bf30
 $rules->exec( sub { length > 20 } );
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
sub exec {
Packit 26bf30
    my $self = _force_object shift;
Packit 26bf30
    my $code = shift;
Packit 26bf30
Packit 26bf30
    $self->_add_rule(
Packit 26bf30
        {
Packit 26bf30
            rule => 'exec',
Packit 26bf30
            code => $code,
Packit 26bf30
        }
Packit 26bf30
    );
Packit 26bf30
Packit 26bf30
    return $self;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
=item ->grep( @specifiers );
Packit 26bf30
Packit 26bf30
Opens a file and tests it each line at a time.
Packit 26bf30
Packit 26bf30
For each line it evaluates each of the specifiers, stopping at the
Packit 26bf30
first successful match.  A specifier may be a regular expression or a
Packit 26bf30
subroutine.  The subroutine will be invoked with the same parameters
Packit 26bf30
as an ->exec subroutine.
Packit 26bf30
Packit 26bf30
It is possible to provide a set of negative specifiers by enclosing
Packit 26bf30
them in anonymous arrays.  Should a negative specifier match the
Packit 26bf30
iteration is aborted and the clause is failed.  For example:
Packit 26bf30
Packit 26bf30
 $rule->grep( qr/^#!.*\bperl/, [ sub { 1 } ] );
Packit 26bf30
Packit 26bf30
Is a passing clause if the first line of a file looks like a perl
Packit 26bf30
shebang line.
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
sub grep {
Packit 26bf30
    my $self = _force_object shift;
Packit 26bf30
    my @pattern = map {
Packit 26bf30
        ref $_
Packit 26bf30
          ? ref $_ eq 'ARRAY'
Packit 26bf30
            ? map { [ ( ref $_ ? $_ : qr/$_/ ) => 0 ] } @$_
Packit 26bf30
            : [ $_ => 1 ]
Packit 26bf30
          : [ qr/$_/ => 1 ]
Packit 26bf30
      } @_;
Packit 26bf30
Packit 26bf30
    $self->exec( sub {
Packit 26bf30
        local *FILE;
Packit 26bf30
        open FILE, $self->finder->item() or return;
Packit 26bf30
        local ($_, $.);
Packit 26bf30
        while (<FILE>) {
Packit 26bf30
            for my $p (@pattern) {
Packit 26bf30
                my ($rule, $ret) = @$p;
Packit 26bf30
                return $ret
Packit 26bf30
                  if ref $rule eq 'Regexp'
Packit 26bf30
                    ? /$rule/
Packit 26bf30
                      : $rule->(@_);
Packit 26bf30
            }
Packit 26bf30
        }
Packit 26bf30
        return;
Packit 26bf30
    } );
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
=item C<maxdepth( $level )>
Packit 26bf30
Packit 26bf30
Descend at most C<$level> (a non-negative integer) levels of directories
Packit 26bf30
below the starting point.
Packit 26bf30
Packit 26bf30
May be invoked many times per rule, but only the most recent value is
Packit 26bf30
used.
Packit 26bf30
Packit 26bf30
=item C<mindepth( $level )>
Packit 26bf30
Packit 26bf30
Do not apply any tests at levels less than C<$level> (a non-negative
Packit 26bf30
integer).
Packit 26bf30
Packit 26bf30
=item C<extras( \%extras )>
Packit 26bf30
Packit 26bf30
Specifies extra values to pass through to C<File::File::find> as part
Packit 26bf30
of the options hash.
Packit 26bf30
Packit 26bf30
For example this allows you to specify following of symlinks like so:
Packit 26bf30
Packit 26bf30
 my $rule = File::Find::Object::Rule->extras({ follow => 1 });
Packit 26bf30
Packit 26bf30
May be invoked many times per rule, but only the most recent value is
Packit 26bf30
used.
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
sub maxdepth {
Packit 26bf30
    my $self = _force_object shift;
Packit 26bf30
    $self->_maxdepth(shift);
Packit 26bf30
    return $self;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
sub mindepth {
Packit 26bf30
    my $self = _force_object shift;
Packit 26bf30
    $self->_mindepth(shift);
Packit 26bf30
    return $self;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
=item C<relative>
Packit 26bf30
Packit 26bf30
Trim the leading portion of any path found
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
sub relative () {
Packit 26bf30
    my $self = _force_object shift;
Packit 26bf30
    $self->_relative(1);
Packit 26bf30
Packit 26bf30
    return $self;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
=item C<not_*>
Packit 26bf30
Packit 26bf30
Negated version of the rule.  An effective shortand related to ! in
Packit 26bf30
the procedural interface.
Packit 26bf30
Packit 26bf30
 $foo->not_name('*.pl');
Packit 26bf30
Packit 26bf30
 $foo->not( $foo->new->name('*.pl' ) );
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
sub DESTROY {}
Packit 26bf30
sub AUTOLOAD {
Packit 26bf30
    $AUTOLOAD =~ /::not_([^:]*)$/
Packit 26bf30
      or croak "Can't locate method $AUTOLOAD";
Packit 26bf30
    my $method = $1;
Packit 26bf30
Packit 26bf30
    my $sub = sub {
Packit 26bf30
        my $self = _force_object shift;
Packit 26bf30
        $self->not( $self->new->$method(@_) );
Packit 26bf30
    };
Packit 26bf30
    {
Packit 26bf30
        no strict 'refs';
Packit 26bf30
        *$AUTOLOAD = $sub;
Packit 26bf30
    }
Packit 26bf30
    &$sub;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
=back
Packit 26bf30
Packit 26bf30
=head2 Query Methods
Packit 26bf30
Packit 26bf30
=over
Packit 26bf30
Packit 26bf30
=item C<in( @directories )>
Packit 26bf30
Packit 26bf30
Evaluates the rule, returns a list of paths to matching files and
Packit 26bf30
directories.
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
Packit 26bf30
sub _call_find {
Packit 26bf30
    my $self = shift;
Packit 26bf30
    my $paths = shift;
Packit 26bf30
Packit 26bf30
    my $finder = File::Find::Object->new( $self->extras(), @$paths);
Packit 26bf30
Packit 26bf30
    $self->finder($finder);
Packit 26bf30
Packit 26bf30
    return;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
sub _compile {
Packit 26bf30
    my $self = shift;
Packit 26bf30
    my $subs = shift;
Packit 26bf30
Packit 26bf30
    return '1' unless @{ $self->rules() };
Packit 26bf30
Packit 26bf30
    my $code = join " && ", map {
Packit 26bf30
        if (ref $_->{code}) {
Packit 26bf30
            push @$subs, $_->{code};
Packit 26bf30
            "\$subs->[$#{$subs}]->(\@args) # $_->{rule}\n";
Packit 26bf30
        }
Packit 26bf30
        else {
Packit 26bf30
            "( $_->{code} ) # $_->{rule}\n";
Packit 26bf30
        }
Packit 26bf30
    } @{ $self->rules() };
Packit 26bf30
Packit 26bf30
    return $code;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
sub in {
Packit 26bf30
    my $self = _force_object shift;
Packit 26bf30
    my @paths = @_;
Packit 26bf30
Packit 26bf30
    $self->start(@paths);
Packit 26bf30
Packit 26bf30
    my @results;
Packit 26bf30
Packit 26bf30
    while (defined(my $match = $self->match()))
Packit 26bf30
    {
Packit 26bf30
        push @results, $match;
Packit 26bf30
    }
Packit 26bf30
Packit 26bf30
    return @results;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
=item C<start( @directories )>
Packit 26bf30
Packit 26bf30
Starts a find across the specified directories.  Matching items may
Packit 26bf30
then be queried using L</match>.  This allows you to use a rule as an
Packit 26bf30
iterator.
Packit 26bf30
Packit 26bf30
 my $rule = File::Find::Object::Rule->file->name("*.jpeg")->start( "/web" );
Packit 26bf30
 while ( my $image = $rule->match ) {
Packit 26bf30
     ...
Packit 26bf30
 }
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
Packit 26bf30
sub start {
Packit 26bf30
    my $self = _force_object shift;
Packit 26bf30
    my @paths = @_;
Packit 26bf30
Packit 26bf30
    my $fragment = $self->_compile($self->_subs());
Packit 26bf30
Packit 26bf30
    my $subs = $self->_subs();
Packit 26bf30
Packit 26bf30
    warn "relative mode handed multiple paths - that's a bit silly\n"
Packit 26bf30
      if $self->_relative() && @paths > 1;
Packit 26bf30
Packit 26bf30
    my $code = 'sub {
Packit 26bf30
        my $path_obj = shift;
Packit 26bf30
        my $path = shift;
Packit 26bf30
Packit 26bf30
        if (!defined($path_obj))
Packit 26bf30
        {
Packit 26bf30
            return;
Packit 26bf30
        }
Packit 26bf30
Packit 26bf30
        $path =~ s#^(?:\./+)+##;
Packit 26bf30
        my $path_dir = dirname($path);
Packit 26bf30
        my $path_base = fileparse($path);
Packit 26bf30
        my @args = ($path_base, $path_dir, $path);
Packit 26bf30
        local $_ = $path_base;
Packit 26bf30
        my $maxdepth = $self->_maxdepth;
Packit 26bf30
        my $mindepth = $self->_mindepth;
Packit 26bf30
Packit 26bf30
        my $comps = $path_obj->full_components();
Packit 26bf30
Packit 26bf30
        my $depth = scalar(@$comps);
Packit 26bf30
Packit 26bf30
        defined $maxdepth && $depth >= $maxdepth
Packit 26bf30
           and $self->finder->prune();
Packit 26bf30
Packit 26bf30
        defined $mindepth && $depth < $mindepth
Packit 26bf30
           and return;
Packit 26bf30
Packit 26bf30
        #print "Testing \'$_\'\n";
Packit 26bf30
Packit 26bf30
        my $discarded;
Packit 26bf30
        return unless ' . $fragment . ';
Packit 26bf30
        return if $discarded;
Packit 26bf30
        return $path;
Packit 26bf30
    }';
Packit 26bf30
Packit 26bf30
    #use Data::Dumper;
Packit 26bf30
    #print Dumper \@subs;
Packit 26bf30
    #warn "Compiled sub: '$code'\n";
Packit 26bf30
Packit 26bf30
    my $callback = eval "$code" or die "compile error '$code' $@";
Packit 26bf30
Packit 26bf30
    $self->_match_cb($callback);
Packit 26bf30
    $self->_call_find(\@paths);
Packit 26bf30
Packit 26bf30
    return $self;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
Packit 26bf30
=item C<match>
Packit 26bf30
Packit 26bf30
Returns the next file which matches, false if there are no more.
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
sub match {
Packit 26bf30
    my $self = _force_object shift;
Packit 26bf30
Packit 26bf30
    my $finder = $self->finder();
Packit 26bf30
Packit 26bf30
    my $match_cb = $self->_match_cb();
Packit 26bf30
    my $preproc_cb = $self->extras()->{'preprocess'};
Packit 26bf30
Packit 26bf30
    while(defined(my $next_obj = $finder->next_obj()))
Packit 26bf30
    {
Packit 26bf30
        if (defined($preproc_cb) && $next_obj->is_dir())
Packit 26bf30
        {
Packit 26bf30
            $finder->set_traverse_to(
Packit 26bf30
                $preproc_cb->(
Packit 26bf30
                        $self,
Packit 26bf30
                        [ @{$finder->get_current_node_files_list()} ]
Packit 26bf30
                )
Packit 26bf30
            );
Packit 26bf30
        }
Packit 26bf30
Packit 26bf30
        if (defined(my $path = $match_cb->($next_obj, $next_obj->path())))
Packit 26bf30
        {
Packit 26bf30
            if ($self->_relative)
Packit 26bf30
            {
Packit 26bf30
                my $comps = $next_obj->full_components();
Packit 26bf30
                if (@$comps)
Packit 26bf30
                {
Packit 26bf30
                    return
Packit 26bf30
                        ($next_obj->is_dir()
Packit 26bf30
                        ? File::Spec->catdir(@$comps)
Packit 26bf30
                        : File::Spec->catfile(@$comps)
Packit 26bf30
                        )
Packit 26bf30
                    ;
Packit 26bf30
                }
Packit 26bf30
            }
Packit 26bf30
            else
Packit 26bf30
            {
Packit 26bf30
                return $path;
Packit 26bf30
            }
Packit 26bf30
        }
Packit 26bf30
Packit 26bf30
    }
Packit 26bf30
Packit 26bf30
    return;
Packit 26bf30
}
Packit 26bf30
Packit 26bf30
1;
Packit 26bf30
Packit 26bf30
__END__
Packit 26bf30
Packit 26bf30
=back
Packit 26bf30
Packit 26bf30
=head2 Extensions
Packit 26bf30
Packit 26bf30
Extension modules are available from CPAN in the File::Find::Object::Rule
Packit 26bf30
namespace.  In order to use these extensions either use them directly:
Packit 26bf30
Packit 26bf30
 use File::Find::Object::Rule::ImageSize;
Packit 26bf30
 use File::Find::Object::Rule::MMagic;
Packit 26bf30
Packit 26bf30
 # now your rules can use the clauses supplied by the ImageSize and
Packit 26bf30
 # MMagic extension
Packit 26bf30
Packit 26bf30
or, specify that File::Find::Object::Rule should load them for you:
Packit 26bf30
Packit 26bf30
 use File::Find::Object::Rule qw( :ImageSize :MMagic );
Packit 26bf30
Packit 26bf30
For notes on implementing your own extensions, consult
Packit 26bf30
L<File::Find::Object::Rule::Extending>
Packit 26bf30
Packit 26bf30
=head2 Further examples
Packit 26bf30
Packit 26bf30
=over
Packit 26bf30
Packit 26bf30
=item Finding perl scripts
Packit 26bf30
Packit 26bf30
 my $finder = File::Find::Object::Rule->or
Packit 26bf30
  (
Packit 26bf30
   File::Find::Object::Rule->name( '*.pl' ),
Packit 26bf30
   File::Find::Object::Rule->exec(
Packit 26bf30
                          sub {
Packit 26bf30
                              if (open my $fh, $_) {
Packit 26bf30
                                  my $shebang = <$fh>;
Packit 26bf30
                                  close $fh;
Packit 26bf30
                                  return $shebang =~ /^#!.*\bperl/;
Packit 26bf30
                              }
Packit 26bf30
                              return 0;
Packit 26bf30
                          } ),
Packit 26bf30
  );
Packit 26bf30
Packit 26bf30
Based upon this message http://use.perl.org/comments.pl?sid=7052&cid=10842
Packit 26bf30
Packit 26bf30
=item ignore CVS directories
Packit 26bf30
Packit 26bf30
 my $rule = File::Find::Object::Rule->new;
Packit 26bf30
 $rule->or($rule->new
Packit 26bf30
                ->directory
Packit 26bf30
                ->name('CVS')
Packit 26bf30
                ->prune
Packit 26bf30
                ->discard,
Packit 26bf30
           $rule->new);
Packit 26bf30
Packit 26bf30
Note here the use of a null rule.  Null rules match anything they see,
Packit 26bf30
so the effect is to match (and discard) directories called 'CVS' or to
Packit 26bf30
match anything.
Packit 26bf30
Packit 26bf30
=back
Packit 26bf30
Packit 26bf30
=head1 TWO FOR THE PRICE OF ONE
Packit 26bf30
Packit 26bf30
File::Find::Object::Rule also gives you a procedural interface.  This is
Packit 26bf30
documented in L<File::Find::Object::Rule::Procedural>
Packit 26bf30
Packit 26bf30
=head1 EXPORTS
Packit 26bf30
Packit 26bf30
=head2 find
Packit 26bf30
Packit 26bf30
=head2 rule
Packit 26bf30
Packit 26bf30
=head1 Tests
Packit 26bf30
Packit 26bf30
=head2 accessed
Packit 26bf30
Packit 26bf30
Corresponds to C<-A>.
Packit 26bf30
Packit 26bf30
=head2 ascii
Packit 26bf30
Packit 26bf30
Corresponds to C<-T>.
Packit 26bf30
Packit 26bf30
=head2 atime
Packit 26bf30
Packit 26bf30
See "stat tests".
Packit 26bf30
Packit 26bf30
=head2 binary
Packit 26bf30
Packit 26bf30
Corresponds to C<-b>.
Packit 26bf30
Packit 26bf30
=head2 blksize
Packit 26bf30
Packit 26bf30
See "stat tests".
Packit 26bf30
Packit 26bf30
=head2 block
Packit 26bf30
Packit 26bf30
Corresponds to C<-b>.
Packit 26bf30
Packit 26bf30
=head2 blocks
Packit 26bf30
Packit 26bf30
See "stat tests".
Packit 26bf30
Packit 26bf30
=head2 changed
Packit 26bf30
Packit 26bf30
Corresponds to C<-C>.
Packit 26bf30
Packit 26bf30
=head2 character
Packit 26bf30
Packit 26bf30
Corresponds to C<-c>.
Packit 26bf30
Packit 26bf30
=head2 ctime
Packit 26bf30
Packit 26bf30
See "stat tests".
Packit 26bf30
Packit 26bf30
=head2 dev
Packit 26bf30
Packit 26bf30
See "stat tests".
Packit 26bf30
Packit 26bf30
=head2 directory
Packit 26bf30
Packit 26bf30
Corresponds to C<-d>.
Packit 26bf30
Packit 26bf30
=head2 empty
Packit 26bf30
Packit 26bf30
Corresponds to C<-z>.
Packit 26bf30
Packit 26bf30
=head2 executable
Packit 26bf30
Packit 26bf30
Corresponds to C<-x>.
Packit 26bf30
Packit 26bf30
=head2 exists
Packit 26bf30
Packit 26bf30
Corresponds to C<-e>.
Packit 26bf30
Packit 26bf30
=head2 fifo
Packit 26bf30
Packit 26bf30
Corresponds to C<-p>.
Packit 26bf30
Packit 26bf30
=head2 file
Packit 26bf30
Packit 26bf30
Corresponds to C<-f>.
Packit 26bf30
Packit 26bf30
=head2 gid
Packit 26bf30
Packit 26bf30
See "stat tests".
Packit 26bf30
Packit 26bf30
=head2 ino
Packit 26bf30
Packit 26bf30
See "stat tests".
Packit 26bf30
Packit 26bf30
=head2 mode
Packit 26bf30
Packit 26bf30
See "stat tests".
Packit 26bf30
Packit 26bf30
=head2 modified
Packit 26bf30
Packit 26bf30
Corresponds to C<-M>.
Packit 26bf30
Packit 26bf30
=head2 mtime
Packit 26bf30
Packit 26bf30
See "stat tests".
Packit 26bf30
Packit 26bf30
=head2 nlink
Packit 26bf30
Packit 26bf30
See "stat tests".
Packit 26bf30
Packit 26bf30
=head2 r_executable
Packit 26bf30
Packit 26bf30
Corresponds to C<-X>.
Packit 26bf30
Packit 26bf30
=head2 r_owned
Packit 26bf30
Packit 26bf30
Corresponds to C<-O>.
Packit 26bf30
Packit 26bf30
=head2 nonempty
Packit 26bf30
Packit 26bf30
A predicate that determines if the file is empty. Uses C<-s>.
Packit 26bf30
Packit 26bf30
=head2 owned
Packit 26bf30
Packit 26bf30
Corresponds to C<-o>.
Packit 26bf30
Packit 26bf30
=head2 r_readable
Packit 26bf30
Packit 26bf30
Corresponds to C<-R>.
Packit 26bf30
Packit 26bf30
=head2 r_writeable
Packit 26bf30
Packit 26bf30
=head2 r_writable
Packit 26bf30
Packit 26bf30
Corresponds to C<-W>.
Packit 26bf30
Packit 26bf30
=head2 rdev
Packit 26bf30
Packit 26bf30
See "stat tests".
Packit 26bf30
Packit 26bf30
=head2 readable
Packit 26bf30
Packit 26bf30
Corresponds to C<-r>.
Packit 26bf30
Packit 26bf30
=head2 setgid
Packit 26bf30
Packit 26bf30
Corresponds to C<-g>.
Packit 26bf30
Packit 26bf30
=head2 setuid
Packit 26bf30
Packit 26bf30
Corresponds to C<-u>.
Packit 26bf30
Packit 26bf30
=head2 size
Packit 26bf30
Packit 26bf30
See stat tests.
Packit 26bf30
Packit 26bf30
=head2 socket
Packit 26bf30
Packit 26bf30
Corresponds to C<-S>.
Packit 26bf30
Packit 26bf30
=head2 sticky
Packit 26bf30
Packit 26bf30
Corresponds to C<-k>.
Packit 26bf30
Packit 26bf30
=head2 symlink
Packit 26bf30
Packit 26bf30
Corresponds to C<-l>.
Packit 26bf30
Packit 26bf30
=head2 uid
Packit 26bf30
Packit 26bf30
See "stat tests".
Packit 26bf30
Packit 26bf30
=head2 tty
Packit 26bf30
Packit 26bf30
Corresponds to C<-t>.
Packit 26bf30
Packit 26bf30
=head2 writable()
Packit 26bf30
Packit 26bf30
Corresponds to C<-w>.
Packit 26bf30
Packit 26bf30
=head1 BUGS
Packit 26bf30
Packit 26bf30
The code relies on qr// compiled regexes, therefore this module
Packit 26bf30
requires perl version 5.005_03 or newer.
Packit 26bf30
Packit 26bf30
Currently it isn't possible to remove a clause from a rule object.  If
Packit 26bf30
this becomes a significant issue it will be addressed.
Packit 26bf30
Packit 26bf30
=head1 AUTHOR
Packit 26bf30
Packit 26bf30
Richard Clamp <richardc@unixbeard.net> with input gained from this
Packit 26bf30
use.perl discussion: http://use.perl.org/~richardc/journal/6467
Packit 26bf30
Packit 26bf30
Additional proofreading and input provided by Kake, Greg McCarroll,
Packit 26bf30
and Andy Lester andy@petdance.com.
Packit 26bf30
Packit 26bf30
Ported to use L<File::Find::Object> as File::Find::Object::Rule by
Packit 26bf30
Shlomi Fish.
Packit 26bf30
Packit 26bf30
=head1 COPYRIGHT
Packit 26bf30
Packit 26bf30
Copyright (C) 2002, 2003, 2004, 2006 Richard Clamp.  All Rights Reserved.
Packit 26bf30
Packit 26bf30
This module is free software; you can redistribute it and/or modify it
Packit 26bf30
under the same terms as Perl itself.
Packit 26bf30
Packit 26bf30
=head1 SEE ALSO
Packit 26bf30
Packit 26bf30
L<File::Find::Object>, L<Text::Glob>, L<Number::Compare>, find(1)
Packit 26bf30
Packit 26bf30
If you want to know about the procedural interface, see
Packit 26bf30
L<File::Find::Object::Rule::Procedural>, and if you have an idea for a neat
Packit 26bf30
extension, see  L<File::Find::Object::Rule::Extending> .
Packit 26bf30
Packit 26bf30
L<Path::Class::Rule> ’s SEE ALSO contains a review of many directory traversal
Packit 26bf30
modules on CPAN, including L<File::Find::Object::Rule> and L<File::Find::Rule>
Packit 26bf30
(on which this module is based).
Packit 26bf30
Packit 26bf30
=head1 KNOWN BUGS
Packit 26bf30
Packit 26bf30
The tests don't run successfully when directly inside an old Subversion
Packit 26bf30
checkout, due to the presence of C<.svn> directories. C<./Build disttest> or
Packit 26bf30
C<./Build distruntest> run fine.
Packit 26bf30
Packit 26bf30
=cut
Packit 26bf30
Packit 26bf30
=begin Developers
Packit 26bf30
Packit 26bf30
Implementation notes:
Packit 26bf30
Packit 26bf30
[0] Currently we use an array of anonymous subs, and call those
Packit 26bf30
repeatedly from match.  It'll probably be way more effecient to
Packit 26bf30
instead eval-string compile a dedicated matching sub, and call that to
Packit 26bf30
avoid the repeated sub dispatch.
Packit 26bf30
Packit 26bf30
[1] Though [0] isn't as true as it once was, I'm not sure that the
Packit 26bf30
subs stack is exposed in quite the right way.  Maybe it'd be better as
Packit 26bf30
a private global hash.  Something like $subs{$self} = []; and in
Packit 26bf30
C<DESTROY>, delete $subs{$self}.
Packit 26bf30
Packit 26bf30
That'd make compiling subrules really much easier (no need to pass
Packit 26bf30
@subs in for context), and things that work via a mix of callbacks and
Packit 26bf30
code fragments are possible (you'd probably want this for the stat
Packit 26bf30
tests).
Packit 26bf30
Packit 26bf30
Need to check this currently working version in before I play with
Packit 26bf30
that though.
Packit 26bf30
Packit 26bf30
[*] There's probably a win to be made with the current model in making
Packit 26bf30
stat calls use C<_>.  For
Packit 26bf30
Packit 26bf30
  find( file => size => "> 20M" => size => "< 400M" );
Packit 26bf30
Packit 26bf30
up to 3 stats will happen for each candidate.  Adding a priming _
Packit 26bf30
would be a bit blind if the first operation was C< name => 'foo' >,
Packit 26bf30
since that can be tested by a single regex.  Simply checking what the
Packit 26bf30
next type of operation doesn't work since any arbritary exec sub may
Packit 26bf30
or may not stat.  Potentially worse, they could stat something else
Packit 26bf30
like so:
Packit 26bf30
Packit 26bf30
  # extract from the worlds stupidest make(1)
Packit 26bf30
  find( exec => sub { my $f = $_; $f =~ s/\.c$/.o/ && !-e $f } );
Packit 26bf30
Packit 26bf30
Maybe the best way is to treat C<_> as invalid after calling an exec,
Packit 26bf30
and doc that C<_> will only be meaningful after stat and -X tests if
Packit 26bf30
they're wanted in exec blocks.
Packit 26bf30
Packit 26bf30
=end Developers
Packit 26bf30
Packit 26bf30
=cut