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

Discussion in 'Perl Misc' started by jl_post@hotmail.com, Nov 6, 2013.

  1. Guest

    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
     
    , Nov 6, 2013
    #1
    1. Advertising

  2. $Bill Guest

    On 11/6/2013 10:27, wrote:
    > 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";
    }
     
    $Bill, Nov 6, 2013
    #2
    1. Advertising

  3. "" <> writes:
    > 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.
     
    Rainer Weikusat, Nov 6, 2013
    #3
  4. Rainer Weikusat <> writes:
    > "" <> writes:
    >> 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);
     
    Rainer Weikusat, Nov 6, 2013
    #4
  5. On 11/6/2013 10:27 AM, wrote:
    > ...
    > 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;

    --
    Charles DeRykus
     
    Charles DeRykus, Nov 7, 2013
    #5
  6. C.DeRykus Guest

    On Wednesday, November 6, 2013 5:09:26 PM UTC-8, C.DeRykus wrote:
    > On 11/6/2013 10:27 AM, wrote:
    >> ...

    >
    > 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;


    --
    Charles DeRykus
     
    C.DeRykus, Nov 7, 2013
    #6
  7. Rainer Weikusat <> writes:
    > Rainer Weikusat <> writes:
    >> "" <> writes:


    [...]

    >>> 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;


    [...]

    > 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;
     
    Rainer Weikusat, Nov 8, 2013
    #7
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Charles Rush
    Replies:
    1
    Views:
    398
    Scott Ellsworth
    Apr 16, 2004
  2. C Gillespie
    Replies:
    0
    Views:
    302
    C Gillespie
    May 24, 2005
  3. tom c
    Replies:
    5
    Views:
    405
    tom c
    Nov 1, 2006
  4. Replies:
    2
    Views:
    459
  5. Samuel
    Replies:
    6
    Views:
    228
    Gabriel Genellina
    Sep 6, 2007
Loading...

Share This Page