Delusional has asked for the wisdom of the Perl Monks concerning the following question:

OK I'm totally lost at this point. I tried scaling back, and using browser passed variabls to call functions. Regretfully I can't even get that far because I'm simply not understanding why the following fails to work:
for $plugin (<Main/*.pm>) { $plugin =~ s!/!::!g; $plugin =~ s/\.pm$//; use $plugin; }
What in the world am I doing wrong here? When this executes I get this error in the Apache error.log (and Error 500 in the Browser):

...syntax error at .... near "use $plugin"

Changing the code to:
for $plugin (<Main/*.pm>) { $plugin =~ s!/!::!g; $plugin =~ s/\.pm$//; $plugin = "use $plugin\;"; $plugin; }
Results in no error message in the browser, however, Errors in the Apache errors.log about missing and/or unknown function calls (Undefined subroutine), which would be in the included (use'd) .pm files...

I also attempted to use require and eval, which resulted Undefined subroutine messeges appearing in the Apache error.log. How can you simply use a complete directory of .pm files, without packageing anything or calling specific subs in the includes (blind including of files in location X), ignoring all potential hazards of doing something like this, just use filename; in a for loop and continue processing, and have the functions in the use'd files available outside the specific loop that use'd them?

Replies are listed 'Best First'.
Re: Loading all files in a dir with use via for loop
by ikegami (Patriarch) on Sep 28, 2005 at 14:52 UTC

    Use require (which expects a namespace as a bareword or a path as an expression, and happens at run-time), not use (which expects a namespace as a bareword, and happens at compile time):

    foreach $plugin (<Main/*.pm>) { require $plugin; }

    Of course, I'm assuming the module really is a module (i.e. has a package). If it doesn't, you should be using do (and shouldn't use .pm).

    I'm also assuming you're not trying to import any symbols, since use imports symbols, while require doesn't. I consider importing symbols from a dynamically loaded modules to be dangerous and unnecessary.

Re: Loading all files in a dir with use via for loop
by radiantmatrix (Parson) on Sep 28, 2005 at 15:19 UTC

    From the Perldoc for the function use:

    Imports some semantics into the current package from the named module, generally by aliasing certain subroutine or variable names into your package. It is exactly equivalent to
    BEGIN { require Module; import Module LIST; }
    except that Module must be a bareword.
    ((emphasis mine))

    So you see, the 'use' takes only a bareword: you can't use a variable.

    The second doesn't actually execute anything, so the modules named in $plugin are never loaded.

    What you want is to eval the use statements:

    for $plugin (<Main/*.pm>) { $plugin =~ s!/!::!g; $plugin =~ s/\.pm$//; $plugin = "use $plugin\;"; ## $plugin; eval $plugin; }

    As written, that may be risky -- read the documentation on eval (linked above) and understand the risk before implementing.

    <-radiant.matrix->
    Larry Wall is Yoda: there is no try{} (ok, except in Perl6; way to ruin a joke, Larry! ;P)
    The Code that can be seen is not the true Code
    "In any sufficiently large group of people, most are idiots" - Kaa's Law
      Thanks allot radiant.matrix, exactly what I was looking for! I was using eval differently (and obviously wrong for this situation ( eval { $plugin; }; and eval { "$plugin;" };)), which should explain why it was failing(?)....

      Now that I can actually run something, things can, again, move along.

      I have one final question (which is brought up due to the code loading the files needed (dynamically)). When using a variable to call a function, is there anyway to varify that function exists before calling it (since the function would have been 'loaded' at the time its called)?

      For example:
      ....load pm files here.... $PAGE = "test_function_a"; eval { &$PAGE; }; $@ and die( &nopageerror() ); $PAGE = "test_function_b"; eval { &$PAGE; }; $@ and die( &nopageerror() ); exit; sub test_function_a { print "in test_function_a"; } sub nopageerror { print "The function $PAGE is unknown"; }

      This is just an example, and not accurate. In fact, nopageerror doesn't even execute, rather the script just terminates, no error when ran in the browser, however, I can produce an additional log (out side of Apache), that then states "Undefined subroutine &main::test_function_b". Again, the above is just an example to explain, with as little overhead/code as possible to better show the area I'm stuck in. Yes the sub function test_function_b is not present here, to intentionally cause an error situation.

        Your question is a little confusing, but I will do my best. It seems like you are trying to see if certain functions have been exported into your main script.

        First step, read up on packages. Your script should have the package name 'main' (you might even want to get in the habit of specifying package main;). Once you realize this, you can use the standard method for checking to see if a package has a particular method/subroutine available.

        All packages are descendants of UNIVERSAL, so you can use its can method, which looks like this:

        PackageName->can('sub_name');

        So, if you're in the main package:

        $PAGE = "test_function_a"; eval "$PAGE" if main->can($PAGE);

        However, this is probably not what you want to do. Executing function names stored in variables can be rather dangerous, and you should understand the risks before doing so. I don't know what problem you're trying to solve, but perhaps a solution like a rudimetary dispatch table is better suited:

        my %dispatch = ( 'test_function_a' => \&test_function_a, 'test_function_b' => \&test_function_b, ); foreach my $func ( qw'test_function_a test_function_b' ) { die "Package main doesn't seem able to '$func'" unless main->can($f +unc); $dispatch{$func}->(); }

        This avoids some of the issues with eval, and might even be faster.

        <-radiant.matrix->
        Larry Wall is Yoda: there is no try{} (ok, except in Perl6; way to ruin a joke, Larry! ;P)
        The Code that can be seen is not the true Code
        "In any sufficiently large group of people, most are idiots" - Kaa's Law
Re: Loading all files in a dir with use via for loop
by scollyer (Sexton) on Sep 28, 2005 at 15:04 UTC
    You are trying to load a module at runtime, so your solution will depend upon 'require', as 'use' is evaluated before the rest of your code.

    So, in your first code segment, the 'use $plugin' is evaluated before $plugin contains any data, and is doomed to fail.

    Your second code segment merely places some text (which happens to be 'use something_or_other') into a variable, then evaluates the variable in a void context (which in this case means that the value of the variable is returned and disappears, unused, immediately).

    Try replacing the 'use' in the first segment with

    eval "require $plugin";
    - then as long as $plugin contains something that looks like a module name, you should load and evaluate the code in that module at run-time. The eval is required as you have generated the code to be executed on the fly, and perl has not yet had a chance to generate the necessary opcodes that it must run; when you give the string containing "require $plugin" to eval, it assumes the string contains Perl code, and compiles and runs it.

    You say that you've tried 'eval' and 'require' but without seeing the code, it's hard to say what went wrong before.

    Steve Collyer

      Oops, the dangers of cut-n-paste and typing without thinking ...

      You don't need the eval, in fact. Ignore that, as require works fine on a variable. The code I pasted was taken from code that needs to catch an attempt to load a non-existent module (and should be wrapped in an if statement)

      If you know the modules are present, just use the bare 'require $plugin'.

      Steve Collyer