ETOOBUSY 🚀 minimal blogging for the impatient
Path::Tiny
TL;DR
Path::Tiny is very useful.
When I started learning Perl a bit more seriously in 2005, it was clear that copy-and-paste wouldn’t work. This is true of all languages, of course, but there were so many things that you might copy and paste (e.g. from books) that up to that time I always got my problems solved with it.
Path handling
One of the things that had to change, of course, was path handling. If
you have a directory name in variable $dir
, and you want file
ever.txt
inside its sub-directory what
, it’s easy to do this:
my $filepath = $dir . '/what/ever.txt';
Except, for example, that it’s not really general, e.g. in Windows.
So, I learned that the canonical way to do path handling in Perl was using File::Spec (and, in case, File::Spec::Functions):
use File::Spec::Functions qw< splitpath splitdir catdir catpath >;
my ($volume, $directories) = splitpath($dir, 'this-is-a-directory');
my @dirs = splitdir($directories);
$directories = catdir(@dirs, 'what');
my $filepath = catpath($volume, $directories, 'whatever.txt');
This is very general - works on all systems - but wow it’s hard!
Directories and files take two separate routes (*dir
vs *path
functions) and you have to track them all. Without considering that you
have to carry around a lot of variables for all the different parts.
Path::Tiny makes path handling so much easier:
use Path::Tiny; # imports `path` by default
my $filepath = path($dir)->child('what', 'ever.txt')->stringify;
Much more readable!
Also, the last call to stringify
is most of the times not needed -
whenever a string is needed, a Path::Tiny object transforms itself
into one using that method automatically. Neat!
Of course, other methods are very readable:
my $filepath = path($dir)->child('what', 'ever.txt');
my $basename = $filepath->basename; # this is a string
my $parent_dir = $filepath->parent;
my $dirname = $filepath->parent->stringify; # ...
my $absolute = $filepath->absolute;
my $relative = $filepath->relative('/tmp');
my @all_in_dir = $parent->children;
my $in_same_dir = $filepath->sibling('another.txt');
my $resolved = $filepath->realpath;
The last call is equivalent to Cwd’s realpath
, which does a check
on the filesystem - so make sure the file exists!
Reading and writing
Another thing that you might want to do is reading and writing files.
Many times you want to iterate line by line. Well, in this case use the good ol’ system 😄:
use autodie;
open my $fh, '<', $filepath;
while (<$fh>) { ... }
close $fh;
If, on the other hand, you want to get the whole file, you might be tempted to use File::Slurp, right? Path::Tiny has you covered:
my $whole_file = $filepath->slurp;
Make sure to take a look at slurp_raw
and slurp_utf8
!
On the other hand, what if you want to save data in a file? Again… Path::Tiny has you covered:
$filepath->spew($contents_to_be_saved);
Again… look also at spew_raw
and spew_utf8
, as well as append
,
append_raw
, and append_utf8
!
Temporary stuff
Sometimes you need a temporary file. Sometimes a temporary directory. Those sometimes, you can use File::Temp. Or…
my $tempfile = Path::Tiny->tempfile;
my $tempdir = Path::Tiny->tempdir;
They have options… but you get the idea. What’s interesting is that these options have been put to sensible defaults, like e.g. getting rid of temporary files when they are not needed any more. Whatever!
Going on
We’ve come about halfway through the module… there are other functions, like for creating directories, getting rid of stuff, manipulating permissions, copying stuff… most things you want to do with files.
If I had to find the one thing I would change is function copy
. This
is how you call it:
$foo->copy($bar);
Wait… is $foo
copied to $bar
, or is the other way around? I think
that this would be much better:
$foo->copy_to($bar);
Enough nitpicking anyway… go read the documentation!