dynamically loading optional Perl modules

Recently I tweaked some code of mine that dynamically loads optional Perl modules.  Two words should stick out.  dynamically and optional.  So why would you want to do this?  Well, you may have a program with acceptable default behaviour that can be made better if certain Perl modules exist, and so you want to determine at run-time if they exist, and if so, load them and if loaded successfully, then use them.

On Unix/Linux systems, I rarely use the ls command.  I use a command called lc that comes from the University of Waterloo, more than 30 years ago, that does a better job in my opinion of visually separating out directory structure between files and directories.
An example of the output of the lc command, without any options, is:

Directories:
RCS

Files:
fax-template-personal.g       fax-template-work.g

One of the first things I do when I set up a new system is to install this command since I need a ls-like command to look in directories.  The original lc command from UW is written in C, and on a new system there may not yet be a C compiler, or it may not build properly on some variants of Unix.  And I’m always in a hurry when I’m setting up a new system to be able to navigate around the file-system.  So long ago I rewrote lc in Perl.  Actually, I first did this to have it available on Windows systems after I installed the cygwin package.  If the Term::ReadKey module existed, then the program would check to see if it was a interactive terminal, and if so, get the window width and set the output width accordingly, instead of a default 80 characters wide. This worked fine for years, because the module IO::Interactive, also required, was already installed by default, but recently I find it is not.  So, the program needed modification.  So I decided to do a better job of loading these optional modules which may not exist.

You can’t just use the Perl ‘use‘ pragma, since that loads the module at compile time, and if it does not exist, your program will fail.  But a ‘use’ statement is really just doing a:

BEGIN { require Module; Module->import( LIST ); }

It is the BEGIN that causes us grief.  So, we’ll just do a ‘require’ which happens at run-time, if the module exists. So we have to check for its existence.  And we do that by checking each of the paths that modules can be found in and we do that by checking the @INC array. We need to convert the double-colons to path separators.  Let’s be system agnostic and do it properly using the File::Spec->catfile() method.
We’ll create a function that takes a array of desired modules we want loaded, and returns a value indicating if we succeeded or not. We’ll then call the function like:

my @things_we_need = (
    "Term::ReadKey",
    "IO::Interactive",
) ;
my $got_things_we_need = load_modules( \@things_we_need ) ;

And then at the appropriate place in our program we’ll check if we were successful or not:

# check to see if STDOUT attached to terminal.
# If so, and we loaded our optional modules ok, then
# go get the terminal width size and use it

my $term_width = 80 ;
if ( $got_things_we_need ) {
    my @term_size = () ;
    my $fd = *STDOUT ;
    if ( IO::Interactive::is_interactive( $fd )) {
        @term_size = Term::ReadKey::GetTerminalSize $fd ;
    }
    if ( @term_size != 0 ) {
        $term_width = $term_size[0] ;
    } 
}

Our function load_modules() that uses this is:

# load the modules we need
# Args:
#   1:  reference to array of modules (this::that::blah)
# Returns:
#   1:  success
#   0:  was not able to load some of the modules

sub load_modules {
    my $modules_ref = shift ;

    my $modules_we_found = 0 ;       # counter
    my $modules_we_need  = @{$modules_ref} ;

    foreach my $thing ( @${modules_ref} ) {
        my @parts = split( /::/, $thing ) ;
        $parts[-1] .= ".pm" ;  # tack the .pm on the end

        my $found = 0 ;
        foreach my $path ( @INC ) {
            my $f = File::Spec->catfile( $path, @parts ) ;
            # check if the module exists
            if ( -f $f ) {
                require $f ;
                $found++ ;
                $modules_we_found++ ;
                last ;
            }
        }
        return(0) if ( $found == 0 ) ;
    }
    if ( $modules_we_need == $modules_we_found ) {
        return(1) ;     # success
    } else {
        return(0) ;     #failure
    }
}

In the future, if there are other optional modules needed by the program, they can just be added to the array @things_we_need.

The function is part of the lc Perl source code, which is available at  Github

About

RJ is a freelance consultant living in Toronto specializing in software development and systems administration on Unix/Linux systems.

Posted in Perl, Programming

Leave a Reply

%d bloggers like this: