Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dynamically load modules and execute methods in perl

I'm following up on this question about perl web services. I've managed to get modules loading and executing from a main program. Each of the modules is something like this:

#!/usr/bin/perl
package NiMbox::perlet::skeleton;

use strict;
use warnings;

require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(%DEFINITION main secondary);

our %DEFINITION;
$DEFINITION{'main'} = {
    summary => 'skeleton main',
    description => 'long skeleton main description',
    args => { 'box' => {}, 'other' => {} }
};
$DEFINITION{'secondary'} = {
    summary => 'skeleton secondary',
    description => 'long skeleton secondary description'
};

sub main {
    print "main...\n";
}

sub secondary {
    print "secondary...\n"
}

1; 

And invocation of these modules can then be done like this:

use NiMbox::perlet::skeleton;

my %DEFINITION = %NiMbox::perlet::skeleton::DEFINITION;
foreach my $s (keys %DEFINITION) {
    print "calling sub '$s'\n";
    NiMbox::perlet::skeleton->$s();
}

How would I get rid of the direct invocation of NiMbox::perlet:skeleton in a way in which I could do something that looks like this (which does not work but illustrates what I need to do):

my $perlet = 'skeleton';

use NiMbox::perlet::$perlet;

my %DEFINITION = %NiMbox::perlet::$perlet::DEFINITION;
foreach my $s (keys %DEFINITION) {
    print "calling sub '$s'\n";
    NiMbox::perlet::$perlet->$s();
}

Since I'm very close I would rather see what is missing in this example rather than use another library. Any ideas?

like image 668
Ricardo Marimon Avatar asked May 09 '26 14:05

Ricardo Marimon


2 Answers

If you want to make the class name dynamic, you can do something like this:

my $class = 'NiMbox::perlet::' . $perlet;
my $class_file = $class;
$class_file =~ s{::}{/};
$class_file .= '.pm';

require $class_file;
$class->import;

(Or even better, use Module::Load as @Schwern suggests.

Getting the %DEFINITION class is a bit tricky since it would involve symbolic references. A better way would be to provide a class method that returns it, e.g.

package NiMbox::perlet::skeleton;
...
sub definition { 
    my %definition;
    $definition{main} = { summary => 'skeleton main', ... };
    return %definition;
}

Then you could do something like:

my %DEFINITION = $class->definition;
foreach my $s( keys %DEFINITION ) { 
    print "calling sub '$s'\n";
    $class->$s;
}
like image 189
friedo Avatar answered May 11 '26 15:05

friedo


I believe what you're looking for is Exporter or its many follow on modules. I see you're already using it in your module, but you're not using it to get %DEFINITION. You'd do that like so:

use NiMbox::perlet::skeleton qw(%DEFINITION);

foreach my $s (keys %DEFINITION) {
    print "calling sub '$s'\n";
    NiMbox::perlet::skeleton->$s();
}

That aliases %NiMbox::perlet::skeleton::DEFINITION to %DEFINITION and saves a bunch of typing.

To be able to use a variable definition of %DEFINITION you could use "symbolic references" to refer to the variable by name... but those are fraught with peril. Also, exporting global variables means you can only have one at a time in a given namespace. We can do better.

What I would suggest is instead changing the %DEFINITION hash into the definition() class method which returns a reference to %DEFINITION. You could return a hash, but the reference avoids wasting time copying.

package NiMbox::perlet::skeleton;

use strict;
use warnings;

my %DEFINITION = ...;

sub definition {
    return \%DEFINITION;
}

Now you can call that method and get the hash ref.

use NiMbox::perlet::skeleton;

my $definition = NiMbox::perlet::skeleton->definition;

foreach my $s (keys %$definition) {
    print "calling sub '$s'\n";
    NiMbox::perlet::skeleton->$s();
}

Doing it dynamically, the only trick is to load the class. You can eval "require $class" or die $@ but that has security implications. UNIVERSAL::require or Module::Load can handle that better for you.

use Module::Load;

my $class = 'NiMbox::perlet::skeleton';
load $class;

my $definition = $class->definition;

foreach my $s (keys %$definition) {
    print "calling sub '$s'\n";
    $class->$s();
}
like image 44
Schwern Avatar answered May 11 '26 16:05

Schwern



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!