CPAN's Greatest Hits - Path::Class

schwern on 2010-01-03T04:10:17

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.


File::Fu

Eric Wilhelm on 2010-01-03T06:19:29

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.

Conditional for removing "." and ".."

Shlomi Fish on 2010-01-03T09:15:35

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.

Re:Conditional for removing "." and ".."

schwern on 2010-01-03T10:47:02

Interesting, I didn't know about no_upwards.

Path::Extended

ishigaki on 2010-01-03T15:15:58

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 forward slash as a path separator.

Re:Path::Extended

schwern on 2010-01-05T00:39:47

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.