How to find files with Path::Class::Rule

Path::Class::Rule is what you get when you imagine the love-child of Path::Class and File::Find::Rule. Here is part of the SYNOPSIS:

use Path::Class::Rule;
 
my $rule = Path::Class::Rule->new; # match anything
$rule->file->size(">10k");         # add/chain rules
 
# iterator interface
my $next = $rule->iter( @dirs );
while ( my $file = $next->() ) {   # $file is a Path::Class object
  ...
}

As you can see, it has the same method-chaining of rule helpers that File::Find::Rule does. However, it's not built on top of File::Find, so you get a real, lazy iterator instead of the File::Find::Rule iterator that precomputes the results and hands them to you one at a time. The iterator returns Path::Class objects, which makes it a little easier to do some common file manipulations.

While Path::Class::Rule is very new, it already implements the following kinds of rule helpers:

  • File name rules
  • Directory skipping rules
  • File test rules, e.g. -f, -d, -r, -w, etc.
  • Stat test rules, e.g. results of stat()
  • Search depth rules
  • Perl file rules
  • Version control file skipping rules

It's also easy to extend. You only need to "and" together a callback that gets $_ set to the Path::Class object under examination:

$rule->and( sub { -r -w -x $_ } ); # stacked filetest example

You can write extension classes with new helper methods using the add_helper method. Here's a simple example that matches files named "foo":

package Path::Class::Rule::Foo;
 
use Path::Class::Rule;
 
Path::Class::Rule->add_helper(
  foo => sub {
    my @args = @_; # do this to customize closure with arguments
    return sub {
      my ($item) = shift;
      return if $item->is_dir;
      return $item->basename =~ /^foo$/;
    }
  }
);
 
1;

If this module sounds interesting, give it a try! If you want to help improve it, fork it on Github and let me know what you have in mind.

(If you're a curmudgeon and want to know why I wrote another file-finding module for CPAN, read the "SEE ALSO" section of the documentation for my comparison to other things out there.)

Update I've released 0.004 with some additional SEE ALSO entries based on suggestions below.

This entry was posted in perl programming and tagged , , . Bookmark the permalink. Both comments and trackbacks are currently closed.

6 Comments

  1. Posted September 13, 2011 at 11:20 pm | Permalink

    Its interesting that you post this, because I have written (and submitted) a (possibly superfluous) mechanism for recursive actions on a directory tree, based around your File::chdir. That module is one of the most clever uses of tie and indeed shows the "fun" one can have with Perl. Since I found myself using it and a consistent idiom I packaged it into File::chdir::WalkDir. It benefit is that the sub that gets called per file is always executed with the working directory set to the one that contains the file.

    I had been meaning to mention this little module to you at some point and this seemed like the time. Feel free to ignore, or comment if you have ideas.

    • Posted September 14, 2011 at 6:14 am | Permalink

      To be clear, I only maintain File::chdir; I didn't write it. Your application of it to directory walking is interesting. It reminds me a lot of how File::Find works, but without all the crazy globals. In my case, I actually wanted to avoid chdir, as it's unnecessary for building a list of files, but if you're writing code to do something in a directory that is walked recursively, then it makes more sense.

  2. Posted September 14, 2011 at 1:31 am | Permalink

    What you might add to SEE ALSO: http://search.cpan.org/perldoc?File::Find::Object , http://search.cpan.org/perldoc?File::Find::Object::Rule

    • Posted September 14, 2011 at 6:16 am | Permalink

      Thanks. I'll check them out. I ran out of steam to review modules and focused on the ones that I knew of already.

  3. Posted September 14, 2011 at 9:07 am | Permalink

    The iterator throws an exception when it encounters a directory it can't open


    $ perl -MPath::Class::Rule -E '$r = Path::Class::Rule->new; $r->file->name("foo");$n = $r->iter("."); say $f while $f = $n->()'
    ./foo
    Can't open directory .dbus: Permission denied at /usr/local/lib/perl5/site_perl/5.14.1/Path/Class/Rule.pm line 105

    That's not what we want, is it? If I know the names of the directories I cannot read, I can add a skip_dirs for them, but I don't see how to simply skip any I can't read. Or maybe this covered by the "Error handling callback" not implemented note in the docs?

    • Posted September 14, 2011 at 9:15 am | Permalink

      Generally, I'd say that should be covered by the error handling (not yet implemented), so I should probably do that soon. But here's another way to do it:


      $r = Path::Class::Rule->new->and( sub { -r $_ ? 1 : "0 but true" } );

      That will prune unreadable directories.

© 2009-2014 David Golden All Rights Reserved