Article publié dans Linux Magazine 98, octobre 2007.
La perle de ce mois-ci a été rédigée par Christian Jodar
(cjodar@c-sait.net
), de Sophia.pm.
Pour utiliser des modules externes en Perl il y a le classique
use
. Le problème est que si le module n'existe pas,
le programme ne sera même pas compilé. Il y a pourtant des cas où le
module n'est nécessaire que pour une fonctionnalité optionnelle du
programme. Celui-ci fonctionnera alors dans un mode dégradé mais il
faut pouvoir détecter que l'on est dans ce cas pour effectuer les
opérations nécessaires (comme griser un élément dans une interface
graphique).
Il faut donc pendant l'exécution détecter si un module est présent ou non pour savoir qu'il faut désactiver certaines parties (ou faire n'importe quel autre traitement). Cela peut principalement être fait de deux manières.
Quand on parle de déplacer un problème de la phase de compilation à
celle d'exécution, on pense normalement à eval
. Ce mot-clé peut
être utilisé de deux manières, en le faisant suivre par un bloc ou par
une chaîne. La version avec bloc ne permet pas de contourner ce qui
nous intéresse ici car son contenu est compilé en même temps que tout
le reste. On va donc utiliser la version avec une chaîne qui
contiendra un bout de code Perl qui ne sera compilé qu'au moment de
l'exécution.
Si un problème survient pendant la compilation de ce qui est passé à
eval
, la variable $@
contiendra une description de ce
problème. Dans le cas contraire, elle sera vide. On peut alors
facilement tester la présence d'un module de cette manière :
eval "use Module::A::Tester"; if ($@) { # Code à exécuter si le module est absent } else { # Ici tout va bien }
Dans le else
on peut aussi tester le numéro de version en regardant
la variable $Module::A::Tester::VERSION
.
On peut également utiliser un bloc eval
avec require
plutôt que
use
de cette manière :
eval { require Module::A::Tester; import Module::A::Tester; };
Cela marchera comme précédemment si ce n'est que le nom du module ne
peut être déterminé à l'exécution car le comportement de require est
d'alors considérer cela comme un nom de fichier et non pas de
module. Il faudrait alors utiliser le module Module::Util
avec sa
fonction module_path
pour convertir le nom du module.
On peut mettre tout ceci dans une fonction qui prendra en entrée un
nom de module et un numéro de version facultatif. Elle retournera une
valeur vraie si le module est disponible, une valeur fausse sinon. Un
petit exemple suit la définition de la fonction. Il affichera Module
Présent
ou Module Absent
selon les cas.
sub checkModule { my ($module, $version) = @_; eval "use $module $version"; return 0 if $@; return 1; } print 'Module ' . (checkModule('HTML::Parser', 3.55) ? 'Présent' : 'Absent') ."\n";
Son fonctionnement est assez simple. On essaye d'abord de charger le
module avec eval
. Si on a une erreur, pas besoin d'aller plus loin
et on retourne 0. Si on a passé cette phase, le module est bien
disponible. On va alors immédiatement retourner que tout va bien si le
numéro de version n'est pas spécifié. Sinon on teste qu'il est bien
plus grand que la valeur en entrée.
Cette méthode n'est utile que si on utilise réellement le module ensuite et non pas seulement pour tester sa présence. En effet les éventuels blocs BEGIN présents seront exécutés.
Il existe un module CPAN qui permet de faire à peu près la même
chose. Comme la plupart des modules déjà existants, il a été plus
testé et gère plus de cas que la simple fonction précédente. Ce module
est UNIVERSAL::require
. Son utilisation crée une fonction require
qui s'utilise dans un style objet. Pour reprendre l'exemple
précédent :
use UNIVERSAL::require; my $module = 'HTML::Parser'; print 'Module ' . (($module->require(3.55)) ? 'Présent' : 'Absent') ."\n";
Le message d'erreur de chargement, si require
a renvoyé une valeur
fausse, est présent dans $@
ou dans $UNIVERSAL::require::ERROR
.
Par rapport à un use
, ceci n'aura pas appelé la fonction import()
du module. Il faudra donc éventuellement l'appeler ensuite
explicitement, si require
s'est bien passé, comme ceci :
$module->use;
Un module équivalent s'appelle Module::Load::Conditional
avec la
fonction can_load()
. On lui passe en paramètre une référence vers
un hash qui contient la liste des modules à tester comme clés et les
numéros de versions pour les valeurs, ou undef
pour ne pas la
vérifier.
L'exemple précédent devient alors :
use Module::Load::Conditional qw(can_load); my $modulesATester = { 'HTML::Parser' => 3.55 }; print 'Module ' . (can_load(modules => $modulesATester) ? 'Présent' : 'Absent') ."\n";
On aurait pu indiquer plusieurs modules dans $modulesATester
. La
fonction renvoie une valeur vraie seulement si tous les modules ont pu
être chargés avec succès.
Envoyez vos perles à perles@mongueurs.net
, elles seront peut-être
publiées dans un prochain numéro de Linux Magazine.
Copyright © Les Mongueurs de Perl, 2001-2011
pour le site.
Les auteurs conservent le copyright de leurs articles.