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

I'm working on a system to scan an internal network to get the names associated with every host. Most networks are scannable using NetBios and that will be the primary scan method. However, there could be any number of techniques used to find names for the hosts. Some of these methods are bound to become apparent after the fact.

As a result, I wanted to make the program easily extensible. I was hoping to have some kind of plugin system. Each plugin would define certain standard methods which would be called by the main program. The program would search for the plugins or load the list of plugins from a conf file. It would call the scan() method of each module and then integrate all the results.

It seems to me that some kind of custom import function would be the way to go. Unfortunately, I am no typeglob wizard. What is the right way to do this?

An 8 bit man in a 32 bit world.

Replies are listed 'Best First'.
Re: Similar plugins run serially
by BrowserUk (Patriarch) on Nov 02, 2002 at 01:00 UTC

    Warning!! What follows is just my curiosity in trying to solve the problem. It works as far as I have tested it, and that is exactly as far as you can see from the simple code that follows. Critisisms invited.

    Using this main program

    #! perl -sw use vars qw/@scanners/; use strict; use constant PLUGINS => 'c:/test/plugins/*'; BEGIN { @scanners = glob PLUGINS; for(@scanners) { require $_; warn $@ if $@; my ($module) = m!.*/([^\.]+)\Q.pm\E$!; $_ = eval{ "$module"->new() }; warn "Attempt to load: $module failed\n" if ( !$_ or $@ ); } @scanners = grep{ $_ } @scanners; } # call the 'scan' routine in each module, and # print out the results from the array reference returned. print @{&{$_}}, $/ for @scanners;

    And these two modules

    package method1; sub new{ my $class = shift; return bless \&scan, $class; } sub scan{ my @results = qw/foo bar baz/; #!do scan; return \@results; } 1;

    method2.pm

    package method2; sub new{ my $class = shift; return bless \&scan, $class; } sub scan{ my @results = qw/the quick brown fox/; #!do scan; return \@results; } 1;

    I get these results.

    C:\test>209857 foobarbaz thequickbrownfox C:\test>

    which seems to indicate that the method works. However, whilst I cannot see anything wrong with the mechanism, it's the first time I tried anything like this so I am fully expecting to be told either that there is a much simpler way of doing this, or that there is something fundementally unsound in what I have done.


    Nah! Your thinking of Simon Templar, originally played by Roger Moore and later by Ian Ogilvy
      You're regexing with \.pm$, but you're globbing for *. Doesn't sound like something I'd do. Seems a lot more natural to me do to:
      opendir my $dir, PLUGINS; @scanners = map (-f $_ ? /^(.*)\.pm$/i : ()), readdir $dir; closedir $dir; # .. require "$_.pm";
      That said, it seems like you're trying way too hard. The following is untested and may need some tweaking, but you get the idea.
      { local @INC = PLUGINS; opendir my $dir, PLUGINS; eval qq(@{[ map -f && ? /^(.*)\.pm$/ && "use \Q$1\E;", readdir $di +r ]}); closedir $dir; }

      Update: had forgotten to actually put the plugins path into @INC.

      Makeshifts last the longest.

Re: Similar plugins run serially
by belg4mit (Prior) on Nov 01, 2002 at 23:34 UTC
    If it were me I'd write a class for each manner of scanning. Then your script can use the appropriate classes(you could have this be based upon a config file if you want), which register themselves with a base class. Scanning then simply requires the base class to try each of the registered sub-classes.

    --
    perl -wpe "s/\b;([mnst])/'$1/g"

      This sounds like exactly what I want but I'm not sure on how to do the registration part. Any hints on that?

      An 8 bit man in a 32 bit world.

        The &import for each subclass could call some method of the baseclass eg; &register. register would determine it's caller and add the subclass name to some internal list; over which you later iterate calling scan.

        --
        perl -wpe "s/\b;([mnst])/'$1/g"

Re: Similar plugins run serially
by robartes (Priest) on Nov 02, 2002 at 00:33 UTC
    Make each of your modules a class that inherits from a common parent class. The parent class could then hold an array of function references to the different plugin's scan methods. Each plugin would add a reference to its own scan method in this array in its constructor. Your scan program would than just loop over this array and call each function in turn. To summarize:
    • Build an abstract base class, having an @scan_methods array (or any name you might fancy)
    • Make each of your plugins a class that @ISA('that base class')
    • Have your constructor of the plugin register the callback: push SUPER::@scan_methods, \&scan;, where scan is your plugin's scan method
    • In your scan program do: &$_ for @{$BaseClass->get_next_scan_method}, or whatever way you want to access the scan_methods array.

    CU
    Robartes-

Re: Similar plugins run serially
by BUU (Prior) on Nov 01, 2002 at 23:35 UTC
    This is rather evil, but it should work (note: untested code follows)
    my @funcsToCall=qw/check scan something/; while(<*.pu>) { do $_; map{&{$_}} @funcsToCall; }
    the .pu files would define your methods, in the form of simply
    sub scan{do scan stuff;} sub check{etc;} sub something{yeah}
    And the map would return a list of the data collected by the methods, you would then need some if statements, or possibly a grep to determine. (note2: this is if you need totally different functions in each plugin. If all you're doing is using a different regex or something, just use an array of qr//)