Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

schwern (1528)

schwern
  (email not shown publicly)
http://schwern.net/
AOL IM: MichaelSchwern (Add Buddy, Send Message)
Jabber: schwern@gmail.com

Schwern can destroy CPAN at his whim.

Journal of schwern (1528)

Saturday January 02, 2010
11:10 PM

CPAN's Greatest Hits - Path::Class

[ #40069 ]

Once upon a time, file and directory manipulation was considered very convenient. Compared to languages like C and Java, which consider I/O as some sort of distasteful act that should best be done behind at least 9 layers of abstraction, its positively enlightened. But once you use something like Ruby's File and Dir objects Perl starts to look a touch out of date.

In Perl, reading a file or directory is a three step process. Safe path manipulation requires the brilliant but cumbersome File::Spec. Even something like deleting a file requires special code to be safe. Want to reliably delete or create a directory? That's another module, File::Path. Copying a file? File::Copy. And so on.

The root of the problem is that Perl represents paths as just strings. And while that's enough to uniquely identify them, its not nearly enough to actually do anything with them. You want an object, files and directories which know how to do it all. Path::Class provides.

Created by Ken Williams, Path::Class is it. Short, convenient constructors, string overloading and providing just about everything you'd want to do with a path. Since its just sugar on top of all the pre-existing and well-built File modules, its extremely robust.

Here is why it is awesome.

Slurp a file.

# Perl
open my $fh, "<", $file;
my $content = do { local $/; <$fh> };
close $fh;
 
# Path::Class
my $contents = file($file)->slurp;

Iterate over every file in a directory.

# Perl
opendir my $dh, $dir;
for my $thing (grep { $_ ne '.' or $_ ne '..' } readdir $dh) {
    ...
}
closedir $dh;
 
# Path::Class
for my $thing (dir($dir)->children) {
    ...
}

Change the subdir and file on a path (ie. from /some/path/foo/bar.txt to /some/path/baz/biff.txt)

# Perl
my($vol, $dir, $file) = File::Spec->splitpath($path);
my @dirs = File::Spec->splitdir($dir);
pop @dirs;
my $newpath = File::Spec->catpath($vol, @dirs, $newdir, $newfile);
 
# Path::Class
my $newpath = file($file)->parent->parent->subdir($newdir)->file($newfile);

That last example starts to demonstrate what happens once Path::Class objects become ubiquitous in your code. Rather than instantiating them when needed, they're just there and can be chained together for rapid manipulation. Since they're string overloaded there's no reason not to use them.

I neglected error handling in the examples above. Path::Class was written back when library functions calling die() was considered impolite. Before pjf hammered home (cleaved with a Bat'leth?) the point that exceptions are awesome. So you still have to do all the "or die ..." junk with Path::Class, you don't even get the convenience of autodie. Fortunately I hope to do something about that.

The next time you find yourself writing "use File::Spec" give Path::Class a shot.

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • I could never remember whether it was Path::Class or Class::Path for some reason. Plus there's that "just throw an error" thing and some weird business about manipulating filenames for a foreign operating system which seemed a oddly pervasive in the source. Thus: File::Fu.

  • Hi Schwern!

    Your conditional reads: $_ ne '.' or $_ ne '..' but it is wrong, and should be either $_ ne '.' and $_ ne '..' or alterantively ! ($_ eq '.' or $_ eq '..'). Furthermore, a better, more idiomatic, way would be to use File::Spec->no_upwards.

  • Path::Class may be a nightmare while testing modules under Win32, as it uses a native path separator, which is good sometimes but breaks tests if the author of the tests just compares a Path::Class object stringification with a path separated by forward slashes like this:

    my $file = file('foo/bar');
    like $file => qr|foo/bar|; # not ok for Win32 as $file becomes "foo\bar" there.

    I'd rather recommend Path::Extended (or Path::Extended::Class if you prefer), which has almost the same API but always uses a forw

    • I think assuming everything on Windows will take Unix style paths is eventually going to bite you in the ass, and fall flat on its face on VMS, but I see why you'd want to normalize paths. It does make life simpler.

      Path::Extended contains some great ideas, things like grep(), save() and copy_to(). It would be nice if Path::Extended was a subclass of Path::Class, but I see there's some issues with that [cpan.org].