I often find myself needing to do things like:

BEGIN { eval "require CGI::Simple; CGI::Simple->import;" if ($@) { eval "require CGI; CGI->import"; die ($@) if $@; $main::q = CGI->new; } else { $main::q = CGI::Simple->new; } }

In other words, I want to load a preferred module, but if I can't, fall back on another. having no succinct term for this, I was unable to find a CPAN module to make this easier. So, I wrote something, and I'm curious for feedback from my fellow (usually much smarter...) monks.


NAME

This document describes Module::prefer


SYNOPSIS

Allows a package to effectively use preferred modules, while falling back on less-preferred modules when needed.

use Module::prefer 'prefer'; # use CGI::Simple if possible, CGI otherwise my $CGI = prefer( [ 'CGI::Simple' => [1], 'CGI' => [1], ], );

See the section on prefer for the full explanation of the parameter spec.

Note that nothing is exported by default, so one must explicitly ask for prefer to be exported if this is desired.


SUBROUTINES

prefer
$chosen = prefer( \@modspec, @default_args );

The prefer subroutine accepts a reference to a modspec array, and a list of default arguments to pass to the imported module's import method (if any). That is, the same list that would be passed after the use statement.

If none of the modules can be required, prefer will die, dumping the errors given by each module, in order.

Once a module is sucessfully required, it will be imported, if possible, and the name of the chosen module will be returned.


DETAILS

modspec

The modspec is a reference to a ``hash-like'' array, in the following form:

[ 'Module::Name' => [ $use_default_args, @special_args ], ... ]

Modules should occur in order of preference, from most to least preferred. The element immediately following the module name may either be undef or an ARRAYref containing at least one value.

If it is undef, this modules import function will be called without any parameters (if this module is selected for import).

The first element in that ARRAYref will be a true/false value that determines whether or not the default args list will be applied if this module is selected for import. The remaining arguments, if any, will always be passed to this modules import (if this module is chosen).

For example, assume Module::A must manually import foo, but Module::B imports this automatically; also, both modules need to be told to import bar. Two approaches are possible:

# 'bar' in default, special args for Module::A $chosen = prefer ( [ 'Module::A' => [1,'foo'], # use Module::A ('bar','foo'); 'Module::B' => [1], # use Module::B ('bar'); ], 'bar', );
# 'foo' and 'bar' in default, Module::B does it's own thing $chosen = prefer ( [ 'Module::A' => [1], # use Module::A ('bar','foo'); 'Module::B' => [0,'bar'], # use Module::B ('bar') - ignore defau +lts! ], 'foo', 'bar', );

And the code itself:

package Module::Prefer; use strict; use warnings; use base 'Exporter'; our @EXPORT_OK = qw'prefer'; sub prefer { # prefer ([ module_name => [ $use_defaults, @param ], ... ], @defaul +t_args); my $modspec = shift; my $caller = caller; my $module; my @err; if (@$modspec % 2 != 0) { die "need a hash-like ARRAYref in first ar +gument" } for (my $i = 0; $i <= @$modspec/2; $i+=2) { eval 'require '.$modspec->[$i].';'; if ($@) { push @err, [ $modspec->[$i], $@ ]; next; } $module = $i; last; } if (defined $module && $$modspec[$module]->can('import')) { # Required one of the modules! my $call = "package $caller; ".$$modspec[$module]."->import"; my $param = $modspec->[$module+1]; # mod_name => undef same as mod_name => [0] unless (defined $param && @$param) { $param = [0] } my $use_defaults = shift @$param; if ($use_defaults) { # we should use defaults, if there are any if (@_ || @$param) { # only add things if we either have some defaults, or some spe +cials $call .= '('.(@_ ? "\@_," : '').(@$param ? "\@\$param" : ''); $call =~ s/,$//; $call .= ')'; } } elsif (@$param) { # only use the specials, if any exist $call .= "(\@\$param)"; } $call .= ';'; eval "$call"; die ($@) if $@; } else { # couldn't require any module; die "Failed to require any of the modules:\n\t" .join("\n\t",map { sprintf('%20s => %s', @$_) } @err) ."\n"; } return $modspec->[$module]; #return name of module used } 1; __END__ =head1 NAME This document describes Module::Prefer =head1 SYNOPSIS Allows a package to effectively C<use> preferred modules, while fallin +g back on less-preferred modules when needed. use Module::Prefer 'prefer'; # use CGI::Simple if possible, CGI otherwise my $CGI = prefer( [ 'CGI::Simple' => [1], 'CGI' => [1], ], ); See the section on C<prefer> for the full explanation of the parameter + spec. Note that nothing is exported by default, so one must explicitly ask f +or C<prefer> to be exported if this is desired. =head1 SUBROUTINES =over 8 =item C<prefer> $chosen = prefer( \@modspec, @default_args ); The C<prefer> subroutine accepts a reference to a L</"modspec"> array, + and a list of default arguments to pass to the imported module's C<import> method (if any). That is, the same list that would be passed after th +e C<use> statement. If none of the modules can be C<require>d, C<prefer> will die, dumping the errors given by each module, in order. Once a module is sucessfully required, it will be C<import>ed, if poss +ible, and the name of the chosen module will be returned. =back =head1 DETAILS =head2 modspec The modspec is a reference to a "hash-like" array, in the following fo +rm: [ 'Module::Name' => [ $use_default_args, @special_args ], ... ] Modules should occur in order of preference, from most to least prefer +red. The element immediately following the module name may either be C<unde +f> or an ARRAYref containing at least one value. If it is C<undef>, this modules C<import> function will be called with +out any parameters (if this module is selected for import). The first element in that ARRAYref will be a true/false value that det +ermines whether or not the default args list will be applied if this module is + selected for C<import>. The remaining arguments, if any, will always be passed + to this modules C<import> (if this module is chosen). For example, assume B<Module::A> must manually import C<foo>, but B<Mo +dule::B> imports this automatically; also, both modules need to be told to impo +rt C<bar>. Two approaches are possible: # 'bar' in default, special args for Module::A $chosen = prefer ( [ 'Module::A' => [1,'foo'], # use Module::A ('bar','foo'); 'Module::B' => [1], # use Module::B ('bar'); ], 'bar', ); # 'foo' and 'bar' in default, Module::B does it's own thing $chosen = prefer ( [ 'Module::A' => [1], # use Module::A ('bar','foo'); 'Module::B' => [0,'bar'], # use Module::B ('bar') - ignore defau +lts! ], 'foo', 'bar', ); =cut

The interface is a little clunky, but I haven't figured out any better (several worse ones, but we won't talk about those ;D). I'd love to hear suggestions on interface and comments on usefulness, including whether I'm doing anything stupid like reinventing a perfectly good wheel.

<radiant.matrix>
A collection of thoughts and links from the minds of geeks
The Code that can be seen is not the true Code
I haven't found a problem yet that can't be solved by a well-placed trebuchet

In reply to RFC: Module::Prefer by radiantmatrix

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.