2009-08-27

When "-e" Just Doesn't Cut It Anymore

In Perl5 if you want to check that a file exists, you can use the syntax if (-e $filename). This is ridiculously handy, but it doesn't extend to modules. Modules are seldom single files, and I was never able to piece together an easy way to run something like this:

if (is_installed( "Module::Name" )) { use Module::Name; }
else { use Other::Module; }

Some of the Perl I write is run on multiple heterogeneous platforms: a Windows box here, a BSD box there, some Linux boxes somewhere far, far away. I can write a Perl script to perform some action the easy way or the hard way, but what I really want is a script that is intelligent enough to figure out if it has the tools necessary on the local host to do it the easy way, and optionally fail over and do it the hard way if there's no other choice. It's self-evident that you can do a quick-and-dirty check from the commandline on any single host:

$ perl -MModule -e "print $Module::VERSION;"

If the module isn't installed, you'll get an error message. But this isn't always a command that you, the coder, can personally run on every system that will house your script. Futhermore, there's no way to factor this kind of check into your software and keep on truckin' if you find that the module doesn't exist. There has to be a way to programatically do a similar check, and tailor the execution of your application accordingly.

The key to today's discovery was that the use keyword was syntactically equivalent to a sequential require and import. This is documented in perlmod:

use Module;

is shorthand for

BEGIN { require Module; import Module; }

I have had more luck doing error-checking on the require instruction than on the use: require can return a value. use doesn't. So it's pretty easy to wrap the require in an eval block and react accordingly.

#!/usr/bin/env perl

use strict;
use warnings;

use Math::BigInt;

# Not sure if Math::BigFloat is installed? Better check.
# use Math::BigFloat;

sub main {

  my $mod_check = undef;

  eval { $mod_check = require Math::BigFloat; };
  if (@!) { die("eval error: $!\n"); }
  if (!defined($mod_check)) { die("module not installed"); }

  import Math::BigFloat;
  my $mbf = Math::BigFloat->new(1.2);
  print $mbf, "\n"; # print the number "1.2"
}

main();
exit();
die("NOT REACHED");

Up next? Evaluating if I should be switching from die to croak in all my scripts.

No comments: