How can I pick a module depending if I have it or not?

J

jl_post

Dear Perl community,

Recently I had to debug a Perl script in a Windows environment that was meant for a Unix environment. The script used the module Device::SerialPort, and since I didn't have it on my Windows installation of Strawberry Perl, the compiler check "perl -c script.pl" was giving me errors.

No problem, I thought; I'll just use the "cpan Device::SerialPort" to install it, and that'll be the end of my problem. Well, I wasn't able to install the Device::SerialPort module through Strawberry Perl, so I looked around CPAN and the internets to see what I could do.

I discovered a very similar module named Win32::SerialPort. It is so similar to Device::SerialPort that many of the method names are the same. And I was able to install Win32::SerialPort onto my Strawberry Perl setup. After that I was able to change:

use Device::SerialPort;
my $port = Device::SerialPort->new($portName)
or die "Can't establish connection with $portName.\n";

to:

use Win32::SerialPort;
my $port = Win32::SerialPort->new($portName)
or die "Can't establish connection with $portName.\n";

and the rest of the code (that involved the SerialPort, at least) compiled just fine.

However, once I finished debugging the script I had to remember to change the "Win32::SerialPort" module to "Device::SerialPort" before I sent the script back for use on a Unix platform. Eventually an idea came to me thatI could let the script determine which module to use (depending on whetherthat module existed). In other words, if Device::SerialPort exists, use that one, but if not, use Win32::SerialPort. (And if neither exist, exit/die with an informative error message.)

So my question is: How can I use a module if it is installed, or another if it is not installed?

I want to be able to do something like this:

my $port = do
{
if (use Device::SerialPort)
{
Device::SerialPort->new($portName)
or die "Can't establish connection with $portName.\n";
}
elsif (use Win32::SerialPort)
{
Win32::SerialPort->new($portName)
or die "Can't establish connection with $portName.\n";
}
else
{
die "This script requires the Device::SerialPort or Win32::SerialPort module.\n";
}
};


Of course this code won't compile, so I was wondering if someone knows how to get a Perl script to do what I want -- that is, to detect which modules (out of several) exist, and to properly load them as if I used them withnormal "use" syntax.

Thanks in advance,

-- Jean-Luc
 
$

$Bill

Dear Perl community,

Recently I had to debug a Perl script in a Windows environment that was meant for a Unix environment. The script used the module Device::SerialPort, and since I didn't have it on my Windows installation of Strawberry Perl, the compiler check "perl -c script.pl" was giving me errors.

No problem, I thought; I'll just use the "cpan Device::SerialPort" to install it, and that'll be the end of my problem. Well, I wasn't able to install the Device::SerialPort module through Strawberry Perl, so I looked around CPAN and the internets to see what I could do.

I discovered a very similar module named Win32::SerialPort. It is so similar to Device::SerialPort that many of the method names are the same. And I was able to install Win32::SerialPort onto my Strawberry Perl setup. After that I was able to change:

use Device::SerialPort;
my $port = Device::SerialPort->new($portName)
or die "Can't establish connection with $portName.\n";

to:

use Win32::SerialPort;
my $port = Win32::SerialPort->new($portName)
or die "Can't establish connection with $portName.\n";

and the rest of the code (that involved the SerialPort, at least) compiled just fine.

However, once I finished debugging the script I had to remember to change the "Win32::SerialPort" module to "Device::SerialPort" before I sent the script back for use on a Unix platform. Eventually an idea came to me that I could let the script determine which module to use (depending on whether that module existed). In other words, if Device::SerialPort exists, use that one, but if not, use Win32::SerialPort. (And if neither exist, exit/die with an informative error message.)

So my question is: How can I use a module if it is installed, or another if it is not installed?

I want to be able to do something like this:

my $port = do
{
if (use Device::SerialPort)
{
Device::SerialPort->new($portName)
or die "Can't establish connection with $portName.\n";
}
elsif (use Win32::SerialPort)
{
Win32::SerialPort->new($portName)
or die "Can't establish connection with $portName.\n";
}
else
{
die "This script requires the Device::SerialPort or Win32::SerialPort module.\n";
}
};


Of course this code won't compile, so I was wondering if someone knows how to get a Perl script to do what I want -- that is, to detect which modules (out of several) exist, and to properly load them as if I used them with normal "use" syntax.

Try using eval - something like:

eval "use Device::SerialPort";
if ($@) {
print "Can't find Device::SerialPort\n";
eval "use Win32::SerialPort";
if ($@) {
die "Can't find Win32::SerialPort - quitting";
} else {
print "Found Win32::SerialPort - using it\n";
}
} else {
print "Found Device::SerialPort - using it\n";
}
 
R

Rainer Weikusat

Recently I had to debug a Perl script in a Windows environment that
was meant for a Unix environment. The script used the module
Device::SerialPort, and since I didn't have it on my Windows
installation of Strawberry Perl, the compiler check "perl -c
script.pl" was giving me errors.
[...]

I discovered a very similar module named Win32::SerialPort. It is
so similar to Device::SerialPort that many of the method names are
the same.
[...]

However, once I finished debugging the script I had to remember to
change the "Win32::SerialPort" module to "Device::SerialPort"
before I sent the script back for use on a Unix platform.
[...]

So my question is: How can I use a module if it is installed, or
another if it is not installed?

Someone else must have implemented this already but you could use this
small module:

-------------
package Delegate;

sub import
{
my ($candidates, $fname);

shift;
$candidates = shift;

for (@$candidates) {
$fname = $_;
$fname =~ s/::/\//g;
$fname .= '.pm';

eval {
require $fname;
};

$@ || do {
unshift(@_, $_);
$_ .= "::import";
goto &$_;
};
}

die("No way man ... \n");
}

1;
---------------

Example usage

---------------
use Delegate (['B::Mad', 'ModA'], 'for_whats_it_worth');

print(for_whats_it_worth(), "\n");
---------------

The first argument is a reference to an anonymous array containing a
list of candiate module names, the others are an ordinary import list.

NB: This doesn't duplicate the documented functionality of use ... /
require Bareword completely as it doesn't try loading a file with .pmc
at the end.
 
R

Rainer Weikusat

Rainer Weikusat said:
Recently I had to debug a Perl script in a Windows environment that
was meant for a Unix environment. The script used the module
Device::SerialPort, and since I didn't have it on my Windows
installation of Strawberry Perl, the compiler check "perl -c
script.pl" was giving me errors.
[...]

I discovered a very similar module named Win32::SerialPort. It is
so similar to Device::SerialPort that many of the method names are
the same.
[...]

However, once I finished debugging the script I had to remember to
change the "Win32::SerialPort" module to "Device::SerialPort"
before I sent the script back for use on a Unix platform.
[...]

So my question is: How can I use a module if it is installed, or
another if it is not installed?

Someone else must have implemented this already but you could use this
small module:

-------------
package Delegate;

sub import
{
my ($candidates, $fname);

shift;
$candidates = shift;

for (@$candidates) {
$fname = $_;
$fname =~ s/::/\//g;
$fname .= '.pm';

eval {
require $fname;
};

$@ || do {
unshift(@_, $_);
$_ .= "::import";
goto &$_;
};
}

die("No way man ... \n");
}

1;
---------------

While this is a neat idea, it won't work with modules which don't define
or inherit an import method, won't work with OO-modules at all and is
restricted to a single set of delegations per program. Something more
suitable (for the problem situation which was about OO-modules) could
look like this:

-------------
sub require_one
{
my $fname;

for (@_) {
$fname = $_;
$fname =~ s/::/\//g;
$fname .= '.pm';

eval {
require $fname;
};

return $_ unless $@;
}

die("need any of ".join(', ', @_)."\n");
}
-------------

This expects a list of module names as arguments and will return the
name of the first one which could be loaded. This name can then used to
call class methods like this

$serial_class = require_one('Win32::SerialPort', 'Device::SerialPort');
$port = $serial_class->new($portName);
 
C

Charles DeRykus

...
So my question is: How can I use a module if it is installed, or another if it is not installed?

I want to be able to do something like this:

my $port = do
{
if (use Device::SerialPort)
{
Device::SerialPort->new($portName)
or die "Can't establish connection with $portName.\n";
}
elsif (use Win32::SerialPort)
{
Win32::SerialPort->new($portName)
or die "Can't establish connection with $portName.\n";
}
else
{
die "This script requires the Device::SerialPort or Win32::SerialPort module.\n";
}
};


Maybe a combination of 'use if' and Module::Load::Conditional ...

use Module::Load::Condition qw/check_install/;

our( $m1, $m2 );
use if $m1=check_install(module=>Device::SerialPort),Device::SerialPort;
use if $m2=check_install(module=>Win32::SerialPort), Win32::SerialPort;

die "This script requires..." unless $m1 or $m2;
 
C

C.DeRykus

Maybe a combination of 'use if' and Module::Load::Conditional ...

use Module::Load::Condition qw/check_install/;

our( $m1, $m2 );
use if $m1=check_install(module=>Device::SerialPort),Device::SerialPort;

use if $m2=check_install(module=>Win32::SerialPort), Win32::SerialPort;

die "This script requires..." unless $m1 or $m2;

And to reduce the "yuck" look:

use Module::Load::Condition qw/check_install/;

my $mod1 = check_install(module=>..., ...);
my $mod2 = check_install(module=>..., ...);
die "This script requires..." unless $mod1 or $mod2;

use if $mod1, Device::SerialPort;
use if $mod2, Win32::SerialPort;
 
R

Rainer Weikusat

Rainer Weikusat said:
Rainer Weikusat said:
"(e-mail address removed)" <[email protected]> writes:
[...]
Someone else must have implemented this already but you could use this
small module:
[...]

While this is a neat idea, it won't work with modules which don't define
or inherit an import method, won't work with OO-modules at all and is
restricted to a single set of delegations per program.

The last assertion is wrong because while a module will only be loaded
once, it's import method will be called for as many times as there are
use statements naming it. It is also surprisingly easy to make 'OO
modules' work, courtesy of the fact that Perl-OO doesn't work by special
magic but reuses existing language facilities.

This deals with 'bareword modules' only but it can do 'implicit imports'
as this really doesn't require any work minus using the import-mechanism
correctly. The code is open to abuse in many different ways but "in line
with the usual UNIX(*) tradition, it gives you enough rope to hang
yourself" (as opposed to hanging you proactively to make sure you don't
do something someone considers to be stupid for some reason).

package Delegate;

#
# Delegate something to the first module from a set of
# candidate modules which can be loaded.
#
# Usage
# -----
#
# use Delegate (['candidate0', 'candidiate1', ...], 'importsub0', 'importsub1', ...);
#
# Load the first candidate module which can be loaded and import the
# specified subroutines from it.
#
# use Delegate ({ Winner => ['candidate0', 'candidiate1', ...]}, 'importsub0', 'importsub1', ...);
#
# Same as above but additionally, create a package whose name is the
# single key of the anonymous hash passed as first argument
# functioning as class derived from the selected candidate module.
#

sub require_one
{
eval "require $_;" and return $_
for @_;

return;
}

sub import
{
my ($arg0, $candidates, $delegate, $x);

shift;

$arg0 = shift;
if (ref($arg0) eq 'HASH') {
$delegate = (keys(%$arg0))[0];
$candidates = $arg0->{$delegate};
} else {
$candidates = $arg0;
}

$x = require_one(@$candidates);
die("Need one of ".join(',', @$candidates)) unless $x;

eval "package $delegate; our \@ISA = qw($x); 1;"
if $delegate;

if ($x->can('import')) {
unshift(@_, $x);
$x .= '::import';
goto &$x;
}
}

# Because properties of existing things are discovered and not
# invented, this code is in the public domain. Whoever discovers
# a bug in it is entitled to keep it.
1;
 

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

Staff online

Members online

Forum statistics

Threads
473,763
Messages
2,569,560
Members
45,035
Latest member
HoTaKeDai

Latest Threads

Top