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

Hello, I have been slowly rewriting my collection of scripts and home made gadgets, and got playing with some new stuff. I found that putting specific functions is specific module files (.pm), then using those (use somefile;) helps keep the over scope of the script a bit more readable, and, at times, reduces the overall size of the 'application' (long story short). So to my question, is there a way to call a specific function from each pm file *when desired*. For example:
use strict; use myfirstadin; use mysecondadin; print "hello world"; &runonce; exit; -------------- myfirstadin.pm sub runonce { print "This is file myfirstadin."; } -------------- mysecondadin.pm sub runonce { print "This is file myfirstadin."; } --------------
OK, the coding is crap, I just wanted to express it is the simplest terms I could. Ignoring the standard (complaint) errors, is there any way to accomplish the above? I.E. Load all the 'chosen' .pm files with the use, and call the function names runonce later in the main file, causing it to execute in all files where the given function is found (dummy calling, like calling out someone's name in a room and letting everyone with that name answer).

Replies are listed 'Best First'.
Re: Same function multiples unknown modules
by Zaxo (Archbishop) on Sep 19, 2005 at 13:19 UTC

    The usual way to have several implementations of a function is to place them in a package namespace. If the modules furnish an object constructor, calling the runonce() method will automatically select the correct one.

    To call a non-method runonce() from a package, use the fully-qualified name,

    Package::Name::runonce();
    To call once for each package loaded, you'll need to go through all the loaded packages and try each. Here's an unsophisticated way to do it,
    for (keys %INC) { s!/!::!g; s/\.pm$//; eval { $_::runonce() }; }
    The eval is used to trap fatal errors from packages which don't define runonce().

    After Compline,
    Zaxo

      Thanks. This looks promising. At the moment I have two subdirectories where these .pm files are: Main and Addins. What would need to be changed to use only these two subdirectories. Not the root driectory where the main script is located, or the perl root, only the two subdirectories?

        At the top of the loop, the line,

        next unless m/^Main|Addins/;
        will restrict attention to those two top level namespaces.

        After Compline,
        Zaxo

Re: Same function multiples unknown modules
by Corion (Patriarch) on Sep 19, 2005 at 13:11 UTC

    Depending on how much interaction you want and what plug-ins / add-ins you want and provide, either Module::Pluggable, possibly together with NEXT or a really simple mixin-based approach might work for you. The following code shows how several files can create code in the same namespace:

    package AddIns; use strict; our %functions; our $AUTOLOAD; sub register { my ($class,$key, $sub) = @_; $functions{$key} ||= []; push @{ $functions{$key} }, $sub; }; sub AUTOLOAD { my (@args) = @_; $AUTOLOAD =~ m!.*::([^:]+)$! or die "Strange subroutine '$AUTOLOAD' called."; my $key = $1; die "Unknown slot '$key' called" unless exists $functions{$key}; for my $sub (@{ $functions{$key} }) { eval { $sub->(@args); }; if ($@) { warn "Error while calling slot '$key' : $@"; }; }; }; package AddIn::First; AddIn->register('runonce', \&runonce); sub runonce { print "myfirstaddin" }; package AddIn::Second; AddIn->register('runonce', \&runonce); sub runonce { print "mysecondaddin" }; package main; AddIn->runonce; # All of this is untested though
Re: Same function multiples unknown modules
by Taulmarill (Deacon) on Sep 19, 2005 at 13:10 UTC
    you should use packages to do that. read merlyns great "Learning Perl Objects, References, and Modules".
Re: Same function multiples unknown modules
by Fletch (Bishop) on Sep 19, 2005 at 13:11 UTC

    You could walk the symbol table, but it'd be less dicey (i.e. you can't guarantee 100% that J Random Module's runonce really is your runonce) to have the modules call a registration routine if they want something run whenever (something along the lines of use MyRegistry qw( register ); register( 'Sompackage::runonce' );). You then walk the list that builds calling the subs named (or passed as coderefs).

Re: Same function multiples unknown modules
by ikegami (Patriarch) on Sep 19, 2005 at 14:09 UTC

    You should use do (not use) to load files which don't use package (and they are typically named .pl, not .pm). use (and require) sometimes does nothing, which would be undesireable here.

    Anyway, the following might be the simplest answer:

    use strict; use warnings; use myfirstaddin (); use mysecondaddin (); if (defined $ARGV[0] && $ARGV[0] eq 'second'} { mysecondaddin->import('runonce'); } else { myfirstaddin->import('runonce'); } print "hello world"; runonce(); myfirstaddin::runonce(); mysecondaddin::runonce();
    # myfirstaddin.pm package myfirstaddin; BEGIN { require Exporter; our @ISA = qw( Exporter ); our @EXPORT_OK = qw( runonce ); } # Other "use" statements go here. sub runonce { print "This is file myfirstaddin."; } 1;
    # mysecondaddin.pm package mysecondaddin; BEGIN { require Exporter; our @ISA = qw( Exporter ); our @EXPORT_OK = qw( runonce ); } # Other "use" statements go here. sub runonce { print "This is file mysecondaddin."; } 1;
Re: Same function multiples unknown modules
by tcf03 (Deacon) on Sep 19, 2005 at 13:14 UTC
    wouldnt it be something like
    myfistadin::runonce(); mysecondadin::runonce();
    where myfirstadin would be myfirstadin.pm
    #!/usr/bin/perl -w use strict; package test1; sub runonce { print "hello"; } 1;
    and I beleive you would use require "myfirstadin.pm";

    update
    I misread, my post doesnt exactly answer your question...

    Ted
    --
    "That which we persist in doing becomes easier, not that the task itself has become easier, but that our ability to perform it has improved."
      --Ralph Waldo Emerson
Re: Same function multiples unknown modules
by Delusional (Beadle) on Sep 19, 2005 at 13:49 UTC
    OK, thanks, for the information, however, this, I don't think, is going to work for what I'm asking. Essentually, I wanted to do a 'dummy' call method. Not worrying about packaging etc. The goal is, is to write the 'main' file, then simply add pm files to the directory, and have them called (use), more or less automatically, and then call a specific function that is found in each file, without knowing the pm file name (not knowing the pm file name is the catch to it all). Hence 'like calling out someone's name in a room and letting everyone with that name answer'.

    The goal is to have a single function in each pm file that is called, sorta like an init() or something along those lines. The main file wouldn't be edited in order to use a new pm file, the file would be loaded, and the init() (or what ever) would be started and execution would begin. At this point, the only item in the 'global function' is a 'menu' which the main script should call, and choosing the menu, starts the specific function in the specific pm (which only exists once). The actual showing of the 'menu item' is variable based, so if X eq 1 show me else skip.

    I'm starting to guess this may not be possible, however, Perl is powerfull enough that I beleive it is. I'd prefer doing this with the standard modules, if external modules are required, for compatibility.
      Thanks again Zaxo,
      I ran a test using this, however, the $eval fails on me (in a browser I get Error 500)...

      I use logging in the scripting to catch errors as well as MySQL calls (and anything else I choose to log). The log shows the following when the script is executed:

      Sep 26, 2005 (10:49:52) : ERROR SITUATION: Interface Crash, error message:
      syntax error at Main/Menu.pm line 101, near "$_::runonce(" Compilation failed in require at d:\PROGRA~1\APACHE~1\apache\cgi-bin\intern2.cgi line 31.

      In the file Main/Menu.pm I have your code as:
      sub sub_menus { for (keys %INC) { next unless m/^Main|Addins/; s!/!::!g; s/\.pm$//; #print "<font face=\"Arial\" size=\"2\">$_<br></font>\n"; eval { $_::runonce() }; } }
      The function, runonce (sub runonce), is available in exactly one file. Perhaps I shouldn't use sub, rather someother addressing scheme that I'm unawear of?

      If I comment out the eval line and use the print line, execution runs clean (with the exception of runonce not running).

      I assume the problem lye with the logging I have to catch problems and log them:
      $SIG{__DIE__} = \&error_crash_trapper; $SIG{__WARN__} = \&warn_crash_trapper;
      Is there any other way to achieve this? Essentially I'm looking for simplicity and the use of a 'dummy' routine that doesn't rely on hard coded information which then allows for dynamic implementation.
Re: Same function multiples unknown modules
by chromatic (Archbishop) on Sep 19, 2005 at 19:24 UTC

    This is not the cleanest approach, but it's one (untested) way:

    package main; use lib 'plugins'; main(); sub main { load_plugins(); init_plugins(); } sub load_plugins { for my $plugin (<plugins/*.pm>) { (my $package = $plugin) =~ s/\.pm//; require $plugin; } } my @plugins; sub register_plugin { my ($class, $plugin_subref) = @_ push @plugins, $plugin_subref; } sub init_plugins { $_->() for @plugins; } package ExamplePlugin; sub import { my $caller = caller(); $caller->register_plugin( \&init ); } sub init { # initialization code here } 1;
Re: Same function multiples unknown modules
by Delusional (Beadle) on Sep 20, 2005 at 14:24 UTC
    Thanks everyone for your comments. Regretfully I'm stuck in the same position I previously was. So I have to assume that it isn't possible to accomplish the chosen task as I originally thought. The information from Zaxo proves 'promising', however, a bit 'dangerous' in regards to security. The primary goal was to simply make a single 'program control' script that loads and runs the 'modules' I included in the specific directories, and be able to simply add a new 'module' without needing to update the primary 'program control' file. I'm sure it is possible to accomplish this, but I doubt it can be done in the exact way I originally thought it could.

    If explanation helps someone, who may have already done something along these lines, then here goes:
    program.cgi (Primary control file, written once with just a handful of lines (roughly 100 or so code lines for over all handling and never changed))
    addon1.pm
    addon2.pm
    ...

    In essence, a plug-in model, where specific subs must be in the .pm files and in specific subdirectories when program.cgi executes to be 'included'. Additional addon?.pm files can be later added to add to the over all application, without the requirement to update program.cgi. Most likely I've bitten off more then I can chew with this 'project', and know what I want, but simply do not have the Perl programming understanding to implement it. If, by chance, someone has done something along these lines, I'd love to see how it was done. Study the code, and learn from it. Tried the usual source (Source Forge) but was unable to find anything, perhaps due to incorrect search arguments though.
Re: Same function multiples unknown modules
by Delusional (Beadle) on Sep 28, 2005 at 12:14 UTC
    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?