[couverture de Linux Magazine 98]

Perles de Mongueurs (36)

Article publié dans Linux Magazine 98, octobre 2007.

[+ del.icio.us] [+ Developers Zone] [+ Bookmarks.fr] [Digg this] [+ My Yahoo!]

La perle de ce mois-ci a été rédigée par Christian Jodar (cjodar@c-sait.net), de Sophia.pm.

Détecter la disponibilité de modules à l'exécution

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.

La méthode manuelle

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.

La méthode avec un module CPAN

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.

À vous !

Envoyez vos perles à perles@mongueurs.net, elles seront peut-être publiées dans un prochain numéro de Linux Magazine.

[IE7, par Dean Edwards] [Validation du HTML] [Validation du CSS]