Cargando un Módulo Remoto

El siguiente programa rload.pl carga el módulo Email::Valid desde CPAN y lo usa para comprobar la validez de una dirección de email:

lhp@nereida:~/Lperl/src/testing$ perldoc -l Email::Valid
No documentation found for "Email::Valid".
lhp@nereida:~/Lperl/src/testing$ rload.pl 'casiano@ull.es'
yes
lhp@nereida:~/Lperl/src/testing$ rload.pl 'tutu.es'
no

Este es el contenido del programa:

lhp@nereida:~/Lperl/src/testing$ cat -n rload.pl
     1  #!/usr/bin/perl -w
     2  #uses the web to retrieve Email::Valid, and does an example call
     3
     4  use strict;
     5  use LWP::Simple;
     6
     7  scalar eval get('http://search.cpan.org/src/RJBS/Email-Valid-0.179/lib/Email/Valid.pm');
     8
     9  print +(Email::Valid->address( -address => shift,
    10                                  -mxcheck => 1 ) ? "yes\n" : "no\n");

Esta estrategia puede mejorarse construyendo un módulo que modifique la búsqueda en @INC para que los módulos sean descargados desde un site remoto:

pp2@nereida:~/src/perl/Module-Remote-LWP/examples$ cat ut2.pl
#!/usr/bin/perl -w -I../lib/
use strict;
use Module::Remote::LWP root => 'http://orion.pcg.ull.es/~casiano/cpan';
use Tintin::Trivial;

Tintin::Trivial::hello();
Cuando este programa se ejecuta accede al módulo Tintinn::Trivial pese a que no esta disponible en la máquina local:

pp2@nereida:~/src/perl/Module-Remote-LWP/examples$ ut2.pl
Hello from Tintin::Trivial

La estrategia se basa en una propiedad de require. Sigue el extracto (obtenido mediante el comando $ PERLDOC_PAGER=cat perldoc -oLaTeX -f require) de la documentación de require relevante a este asunto:

You can also insert hooks into the import facility, by putting directly Perl code into the @INC array. There are three forms of hooks: subroutine references, array references and blessed objects.

Subroutine references are the simplest case. When the inclusion system walks through @INC and encounters a subroutine, this subroutine gets called with two parameters, the first being a reference to itself, and the second the name of the file to be included (e.g. "Foo/Bar.pm"). The subroutine should return undef or a filehandle, from which the file to include will be read. If undef is returned, require will look at the remaining elements of @INC.

If the hook is an array reference, its first element must be a subroutine reference. This subroutine is called as above, but the first parameter is the array reference. This enables to pass indirectly some arguments to the subroutine.

In other words, you can write:

    push @INC, \&my_sub;
    sub my_sub {
        my ($coderef, $filename) = @_;  # $coderef is \&my_sub
        ...
    }

or:

    push @INC, [ \&my_sub, $x, $y, ... ];
    sub my_sub {
        my ($arrayref, $filename) = @_;
        # Retrieve $x, $y, ...
        my @parameters = @$arrayref[1..$#$arrayref];
        ...
    }

If the hook is an object, it must provide an INC method that will be called as above, the first parameter being the object itself. (Note that you must fully qualify the sub's name, as it is always forced into package main.) Here is a typical code layout:

    # In Foo.pm
    package Foo;
    sub new { ... }
    sub Foo::INC {
        my ($self, $filename) = @_;
        ...
    }
    # In the main program
    push @INC, new Foo(...);

Note that these hooks are also permitted to set the %INC entry corresponding to the files they have loaded. See %INC in perlvar.

El módulo que sigue muestra como implantar el proceso de carga remota:

pp2@nereida:~/src/perl/Module-Remote-LWP/lib/Module/Remote$ cat -n LWP.pm
 1  package Module::Remote::LWP;
 2  use strict;
 3  use warnings;
 4
 5  our $VERSION = '0.02';
 6
 7  use LWP::Simple;
 8
 9  my $URL;
10
11  sub import {
12    my $module = shift;
13    my %arg = @_;
14
15    die "Provide a root URL" unless defined $arg{root};
16    $URL = $arg{root};
17
18  }
19
20  sub new {
21    my $this = shift;
22    my $class = ref($this) || $this;
23    my %arg = @_;
24
25    my $self = bless {}, $class;
26
27    return $self;
28  }
29
30  sub Module::Remote::LWP::INC {
31    my ($self, $filename) = @_;
32
33    if ($filename =~ m{^[\w/\\]+\.pm$}) {
34       my $module = get("$URL/$_[1]") or return undef;
35       open my $fh, '<', \$module or return undef;
36       return $fh;
37    }
38
39    return undef;
40  }
41
42  BEGIN {
43    push (@INC, Module::Remote::LWP->new());
44  }
45
46  1;

Casiano Rodríguez León
2012-02-29