2

I have been toying with the modulino perl pattern and would like to load one without knowing the package name and only the file in which it is. I'm looking for something that could be used like this:

eval {
  my $file = "some-file-name-in-the-hierarchy-unrelated-to-package-name";
  my $module = something($file);
  $module->sub1();
};

The something here is the key. No require or use since I would need the module name afterwards. Do I have to read the $file and parse the package name out of it to then use it? There might a better way I'm sure. There always is in perl.

Paul Sweatte
  • 24,148
  • 7
  • 127
  • 265
Ricardo Marimon
  • 10,339
  • 9
  • 52
  • 59
  • How do you know there's a `sub1` if you don't even know the name of the module you're trying to use? – cjm Mar 24 '12 at 04:58
  • 1
    Why are you trying to do it this way? Are you just curious or do you have a good reason for it? – brian d foy Mar 24 '12 at 07:34
  • The reason is that I'm using modulinos as scripts and modules. The actual name of the action is represented by the file name and not the package name. – Ricardo Marimon Mar 24 '12 at 13:40
  • Well, in my concept of modulinos, the file exists as a module file and you call it like a program (maybe through a symlink). You're treating a program as a module, which is the harder way to do it. – brian d foy Mar 24 '12 at 17:16

3 Answers3

5

Which of the files do you control? If you are creating the program as a modulino and then trying to load that file, just make the last statement in the file return the package name. require expects you to return a true value and most modules use 1;. However, you can use any true value you like. This will be the return value of require:

# i_dont_know_whats_inside
package Buster;

__PACKAGE__;

Loading it is easy:

my $package = require 'i_dont_know_whats_inside';

print "Package is $package\n";

$package->new( ... );

If you can't control the file you're loading, it's not that hard to find out what's inside:

my @packages = load_file( 'i_dont_know_whats_inside' );

print "Packages are @packages\n";

sub load_file {
    my $file = shift;
    require Module::Extract::Namespaces;
    my @packages = Module::Extract::Namespaces->from_file( $file );
    my $rc = require $file;
    return @packages;
    }
brian d foy
  • 129,424
  • 31
  • 207
  • 592
  • Tried both approaches and they work like a charm. Going for the first one since it is simpler and no less standard than returning the `1;`. – Ricardo Marimon Mar 24 '12 at 14:01
  • Brian I'm getting the error `Useless use of a constant in void context` at the line with the statement `__PACKAGE__;`. Any ideas on how to get rid of this warning. – Ricardo Marimon Mar 24 '12 at 17:11
  • Turn off warnings when you get that far, or you can make an assignment. The result of an assignment is the value you assigned. :) – brian d foy Mar 24 '12 at 17:15
  • Thanks brian. The assignment takes it away. I'm a bit worried that this approach is becoming obscure... – Ricardo Marimon Mar 24 '12 at 17:36
  • 2
    `perl -E'say require "Mod.pm"; say require "Mod.pm";'` return `Mod` then `1`. – ikegami Mar 27 '12 at 00:54
  • @rmarimon, you should not get that warning unless you treat the module as a script. – ikegami Mar 27 '12 at 00:55
0

Not knowing the package means information must be returned by the loading mechanism, so that means we need to use do.

Using do means we can't use the symbol table, which means anonymous subs.

Create a file that looks like the following:

sub foo { ... }
sub bar { ... }

{
   foo => \&foo,
   bar => \&bar,
};

or like

{
   foo => sub { ... },
   bar => sub { ... },
};

Then use

my $table = do '/abs/path/to/file.pl' or die $@;
$table{foo}->();

or

my $table = do 'rel/path/to/file.pl' or die $@;  # Relative to entries in @INC
$table{foo}->();

You can call do more than once, but avoid it, as it compiles and runs the file every time. Cache the value it returns instead.

UGLY! And all because you don't want any relation between the file name and the package therein. If you go about trying to circumvent Perl's requirements, expect ugliness. It doesn't even help you any to have this requirement, so get rid of it.

What should do instead is create a file that looks like the following:

package Some::Package;

sub foo { shift; ... }
sub bar { shift; ... }

1;

The calling code would be:

my $file= 'Some/Package.pm';
my $pkg = $file;
$file =~ s{\.pm\z}{};
$file =~ s{/}{::}g;

require $file;
$pkg->foo();

See also Module::PluginFinder.

ikegami
  • 367,544
  • 15
  • 269
  • 518
0

Literals (and variables) in require interpretes exactly as a filename.

For example:

$var = "TEST::test";
require $var;

output:

Can't locate TEST::test in @INC (@INC contains: ...)
             ^^^^^^^^^^ -- not TEST/test.pm!
Vladimir
  • 1,532
  • 1
  • 13
  • 13