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.
This document describes Module::prefer
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.
$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.
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.
In reply to RFC: Module::Prefer by radiantmatrix
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |