Check subroutine-specific requirements on module import

M

Mark Mackey

Hi all.

I've got a simple problem that I can't work out an elegant way to solve.
I have a simple Perl module which is basically a wrapper for some
external binaries. I want the module to check for the existence of those
binaries on import, but only for the binaries which are actually going
to be used by the subroutines that are exported.

For example, I have Thingy.pm:

package Thingy;

require Exporter;
@ISA = qw(Exporter);
# symbols to export on request
@EXPORT_OK = qw(sub1 sub2 sub3);

use strict;

sub sub1 {
print `/bin/ls`;
}

sub sub2 {
print `/bin/true`;
}

sub sub3 {
print `/bin/no_exist`;
}

BEGIN {
my(@required)=qw( /bin/ls /bin/true /bin/no_exist );
my(%required)=( sub1=>"/bin/ls", sub2=>"/bin/true", sub3=>"/bin/no_exist" );
foreach (@required) {
print "Checking $_\n";
die "Can't find required utility $_" if (! -x $_);
}
}
1;

Sub1 will only work if "/bin/ls" is present, sub2 will only work if
"/bin/true" is present, and sub3 will only work if "/bin/no_exist" is
present. With the current structure, all 3 are checked on import, so

use Thingy qw();

dies telling me that "bin/no_exist" doesn't exist :).

What I want is to be able to check the relevant binaries' existence only
for the methods that are imported, so in this case you could do

use Thingy qw(sub1);

and everything would be happy, but

use Thingy qw(sub1 sub2 sub3);

would bail out and inform me that 'sub3' depends on "/bin/no_exist" and
hence it can't continue.

The reason I'd like this check is that this module is used in lots of
programs, each of which uses a different subset of its subroutines. Some
of the programs have very long runtimes, so I don't really want them to
run for hours and then suddenly bail out when sub3() finally gets
called. I suspect the answer involves fiddling around with import()
methods, but I don't completely understand the documentation for
Exporter.pm about this.

Help?
 
U

Uri Guttman

MM> require Exporter;
MM> @ISA = qw(Exporter);

those two lines are better done as:

use base 'Exporter'

MM> # symbols to export on request
MM> @EXPORT_OK = qw(sub1 sub2 sub3);

MM> use strict;

put the strict line before all the others. then use our before
@EXPORT_OK. the use base line will still work correctly too.

MM> What I want is to be able to check the relevant binaries' existence only
MM> for the methods that are imported, so in this case you could do

MM> The reason I'd like this check is that this module is used in lots of
MM> programs, each of which uses a different subset of its subroutines. Some
MM> of the programs have very long runtimes, so I don't really want them to
MM> run for hours and then suddenly bail out when sub3() finally gets
MM> called. I suspect the answer involves fiddling around with import()
MM> methods, but I don't completely understand the documentation for
MM> Exporter.pm about this.

you just need to declare your own import() method. it is called with the
class name and the arguments passed on the use line so you will get
'sub1', etc. do your checks then and die or whatever as needed. if all
the requested features are supported, then you can pass the import
request on to Exporter with: (untested)

sub import {

my( $class, @features ) = @_ ;

# do your testing of the requested features in @features
# and die as desired.

# at this point everything wanted is found

$class->SUPER::import( @features ) ;
}

since you are subclassing Exporter, it will get the same call as if it
was handled directly without your intervening import method.

uri
 
P

Paul Lalli

Mark said:
I've got a simple problem that I can't work out an elegant way to solve.
I have a simple Perl module which is basically a wrapper for some
external binaries. I want the module to check for the existence of those
binaries on import, but only for the binaries which are actually going
to be used by the subroutines that are exported.
I suspect the answer involves fiddling around with import()
methods,

You are correct.
but I don't completely understand the documentation for Exporter.pm about this.

That's okay, because import() doesn't intrinsically have anything to do
with Exporter;

import() is not a built in. It is a method that you define in your
package, that is automatically called when the user calles 'use
Thingy;'. More specifically, if the user calls 'use Thingy qw/ls
true/', then the import method is called with those two parameters.

So, within your package, define your import() method. In it, retrieve
what elements are being requested, via @_, like any other method or
subroutine. Do whatever checks are necessary. And once you're done,
you can then invoke Exporter's magic by following the "Exporting
without using Exporter's import method" section of Exporter's
documentation:

use Carp;
sub import {
my $class = shift;
my @subs_to_export = @_;
my %required_for = (
sub1=>"/bin/ls",
sub2=>"/bin/true",
sub3=>"/bin/no_exist"
);
foreach (@subs_to_export){
print "Checking $_\n";
croak "Can't find required utility for $_"
unless -x $required_for{$_};
}
#we got here, all the required checks passed. Export everything
#the user wanted:
$class->export_to_level(1, $class, @required);
}

Hope this helps.

Paul Lalli
 
M

Mark Mackey

import() is not a built in. It is a method that you define in your
package, that is automatically called when the user calles 'use
Thingy;'. More specifically, if the user calls 'use Thingy qw/ls
true/', then the import method is called with those two parameters.

Ah, that makes sense, and I now understand what the Exporter.pm docs
were wittering about. Where is the fact that the import method is called
on 'use' documented? I'd thought that I'd read all of the relevant bits
of the man pages, but I missed that one.

Thanks!
 
P

Paul Lalli

Mark said:
Ah, that makes sense, and I now understand what the Exporter.pm docs
were wittering about. Where is the fact that the import method is called
on 'use' documented? I'd thought that I'd read all of the relevant bits
of the man pages, but I missed that one.

perldoc -f import
import There is no builtin "import" function. It is just
an ordinary method (subroutine) defined (or
inherited) by modules that wish to export names to
another module. The "use" function calls the
"import" method for the package used.

perldoc -f use
use Module LIST
Imports some semantics into the current package from
the named module, generally by aliasing certain
subroutine or variable names into your package. It
is exactly equivalent to

BEGIN { require Module; import Module LIST; }

except that Module must be a bareword.

Paul Lalli
 
M

Mark Mackey

That is kind of hard to believe. :)

:). I never actually thought of looking at the docs for 'use', but went
and grovelled around in 'man perlmod' directly instead, assuming that
module import semantics might be discussed there. It told me to "(look
in) Exporter for how Perl's standard import/export mechanism works", and
the documentation for Exporter wasn't really comprehensible if you
didn't already know what 'import' was or did. Argh.
 
A

Anno Siegel

Uri Guttman said:
MM> require Exporter;
MM> @ISA = qw(Exporter);

those two lines are better done as:

use base 'Exporter'

MM> # symbols to export on request
MM> @EXPORT_OK = qw(sub1 sub2 sub3);

MM> use strict;

put the strict line before all the others. then use our before
@EXPORT_OK. the use base line will still work correctly too.

MM> What I want is to be able to check the relevant binaries' existence only
MM> for the methods that are imported, so in this case you could do

MM> The reason I'd like this check is that this module is used in lots of
MM> programs, each of which uses a different subset of its subroutines. Some
MM> of the programs have very long runtimes, so I don't really want them to
MM> run for hours and then suddenly bail out when sub3() finally gets
MM> called. I suspect the answer involves fiddling around with import()
MM> methods, but I don't completely understand the documentation for
MM> Exporter.pm about this.

you just need to declare your own import() method. it is called with the
class name and the arguments passed on the use line so you will get
'sub1', etc. do your checks then and die or whatever as needed. if all
the requested features are supported, then you can pass the import
request on to Exporter with: (untested)

sub import {

my( $class, @features ) = @_ ;

# do your testing of the requested features in @features
# and die as desired.

# at this point everything wanted is found

$class->SUPER::import( @features ) ;
}

since you are subclassing Exporter, it will get the same call as if it
was handled directly without your intervening import method.

That isn't quite right. Exporter->import is caller-sensitive (the caller
determines where exported stuff goes), so the method call via SUPER
will import into whatever SUPER resolves to. Better:

goto Exporter->can( 'import') || die "Exporter can't import???";

Make sure @_ is in the state it had originally (or the state you want
it to have). In particular if you are in the habit of saying

my $class = shift;

you must unshift $class before "goto".

Now Uri is going to say, "see, that's why i never shift off arguments".

Anno
 
A

Anno Siegel

Paul Lalli said:
Mark said:
[...]

perldoc -f use
use Module LIST
Imports some semantics into the current package from
the named module, generally by aliasing certain
subroutine or variable names into your package. It
is exactly equivalent to

BEGIN { require Module; import Module LIST; }

Indirect object syntax? I'm not immediately posting a doc patch,
but at some point that should be changed to "Module->import( LIST)".

Anno
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top