Un cours pour débuter en Perl


previous up contents next

Sous-sections


16. Écriture et utilisation de modules

Perl autorise l'écriture de modules qui peuvent être assemblés pour former une application. Cette fonctionnalité a permis le développement de nombreuses contributions, des modules ont été développés pour interfacer TCP, CGI, FTP ... c'est une des raisons du succès de Perl.

16.1 Inclure du code

La forme la plus simple de l'écriture modulaire est l'inclusion de code, autorisée en Perl par la fonction require suivie d'une expression (en général une chaîne de caractère représentant un nom de fichier contenant le code à inclure). Il s'agit d'une fonctionnalité équivalente au #include du langage C, on regroupe en général les inclusions en début de programme :

  

#!/usr/local/bin/perl
require "cgi-lib.pl";   # Inclusion de la bibliothèque
                        # gérant l'interface CGI
  

Les modules Perl écrits ainsi sont souvent un peu anciens et proposent des interfaces rudimentaires :
  

#!/usr/local/bin/perl
#
# Programme principal qui utilise le module monmodule.pl
#
require "monmodule.pl"; 
# on peut simplement appeler la fonction fonc() définie dans
# monmodule.pl et aussi avoir accès à sa variable $I ...
fonc();
print ("I = $I \n");
  

  

#   monmodule.pl
#
#   Petit module accessible simplement, les variables et
#   fonctions sont directement accessibles ...
$I = 2;
sub fonc {
	print (\"appel de fonc() \n\");
}
  

Perl cherche les modules à inclure dans le répertoire courant et dans les répertoires cités dans le tableau @INC. Pour ajouter des répertoires au tableau @INC il convient de modifier la variable d'environnement Perl5LIB ou de modifier @INC avant le require :

  

#!/usr/local/bin/perl
unshift(@INC,"/usr/local/perl/lib/maison");
require "cgi-lib.pl";   # Inclusion de la bibliothèque
                        # gérant l'interface CGI
  

Les fichiers inclus par require sont chargés à l'exécution du programme. Il en résulte une souplesse d'usage liée au fait que l'on peut inclure des fichiers dynamiquement (en fonction du contenu d'une variable). On n'est toutefois pas sûr lorsqu'un programme démarre que tout est prêt pour son bon déroulement (un fichier à inclure qui n'existe pas, qui comporte une erreur de syntaxe ...).

Si l'expression citée par require est numérique, il s'agit du numéro de version de Perl requis pour ce qui suit.

16.2 Les packages

En écrivant classiquement des modules destinés à être inclus par require, le programmeur doit faire attention à la visibilité des variables (cf. section 10). Une première solution est l'usage de my, Perl offre toutefois une autre solution : les packages inclus par use.

Une partie de code isolée dans une déclaration package est destinée à avoir une visibilité externe organisée, le programmeur peut indiquer explicitement quelles sont les variables, les fonctions qui peuvent être simplement utilisées par les consommateurs.

On peut trouver plusieurs déclarations de packages dans un fichier, ce n'est toutefois pas très utilisé, on place généralement un package dans un fichier dont le suffixe est .pm (Perl Module).

  

package essai;     # Le nom du package et du fichier .pm
use Exporter;      # appel au module gérant la visibilité
@ISA=('Exporter'); # hérite d'Exporter (non traité ici,
                   # voir la section sur les objets)
@EXPORT_OK=('Fonc1,'Fonc2','$Var');
# Le tableau @EXPORT_OK est utilisé pour préciser les
# identificateurs qui sont visibles de l'extérieur du
# package.
$Var = "visible";
$I = 10;
sub Fonc1 { ... }
sub Fonc2 { ... }
sub Fonc3 { ... }
sub Fonc4 { ... }
  

Dans l'exemple ci-dessus, seulement deux fonctions (Fonc1 et Fonc2) ainsi qu'une variable ($Var) peuvent être utilisée depuis l'extérieur du package.

Pour utiliser le package essai (placé dans le fichier essai.pm) il convient de procéder comme suit :

  

#!/usr/local/bin/perl
# use est suivi du nom du package  et des variables
# et fonctions à importer
use essai ('Fonc1','Fonc2','$Var');
Fonc1();
Fonc2();
print ("$Var \n");
# Un appel à Fonc3 produirait une erreur
  

Il est possible d'utiliser le tableau @EXPORT à la place de @EXPORT_OK, dans ce cas, même si le programmeur déclare vouloir importer seulement un sous ensemble des identificateurs exportés, il aura une visibilité sur l'ensemble. Il est donc préférable d'utiliser @EXPORT_OK qui évite les conflits potentiels.

Les identificateurs exportés constituent l'interface du package avec l'extérieur, PERL procure toutefois une autre interface avec les packages : le nommage explicite. Il est possible d'accéder à un identificateur (exporté ou non) en le précédant du nom de son package d'appartenance :

  

#!/usr/local/bin/perl
use essai;      # il n'y a pas d'obligation a importer
$I = 20;
essai::Fonc1(); # les identificateurs exportés ou non
essai::Fonc2(); # sont accessibles.
essai::Fonc3();
printf ("%d \n",$essai::I); # affiche 10 (le I de essai.pm)
  

La démarche import/export est évidemment beaucoup plus claire et se généralise.

Les packages peuvent être dotés de constructeurs et de destructeurs rédigés sous la forme de fonctions appelées respectivement BEGIN et END. Les constructeurs sont exécutés dès que possible (à la compilation) et les destructeurs le plus tard possible (lorsque le programme se termine normalement ou pas).

  

package essai;
BEGIN {  # sub n'est pas obligatoire
    print ("debut du package \n");
}
beurk();
END {    # sub n'est pas obligatoire
    print ("fin du package \n");
}
  

Le package essai.pm de l'exemple ci-dessus produit les sorties suivantes :
  

debut du package 
   Undefined subroutine &essai::beurk called 
                        at essai.pm line 7.
   BEGIN failed--compilation aborted at ...
fin du package
  

16.3 Remarques

La différence indiquée ici entre use et require est fictive (elle correspond toutefois à un usage largement répandu), il est en fait parfaitement possible d'inclure un package en utilisant require. Dans ce cas la routine d'import n'est pas appelée mais elle peut l'être à la main, ceci permet d'inclure des packages dynamiquement :
  

if ($a) {           
   require "moda";  # Inclusion de moda.pm ou modb.pm
   moda->import();  # en fonction du contenu d'une variable
}                   
else {              # L'import n'est pas effectué (require)
   require "modb";  # on le fait donc explicitement.
   modb->import();
}
  

16.4 Exercice numéro 15

  

#
#  Petit package d'outils HTML pour montrer comment on exporte
#  des variables et des fonctions.
#  Évidemment les fonctions ci-dessous n'ont pas d'intérêt en
#  dehors du contexte de ce TP.
package html;
use Exporter;
@ISA=('Exporter');
#  Le package exporte une variable et trois fonctions.
@EXPORT=('$html_Version','html_Escape','html_Title',
         'html_Background');
# variable globale au package, elle est donc exportable.
$html_Version = 'html.pm version 1.0';
use strict 'vars';      
# L'emploi de use 'strict rend obligatoire de manipuler
# les variables globales au package en les préfixant par 
# le nom du package : $html::html_Version par exemple ...
sub html_Escape {
#################
# On modifie les caratères accentués les plus usuels ...
  my (@content) = @_;
  my @res;
  foreach (@content) {
    s/é/é/g;
    s/è/È/g;
    s/ç/ç/g; # etc etc etc ....
    push(@res,$_);
  }
  push(@res,"<!-- *** modifié avec $html::html_Version *** -->");
  return(@res);
}
  
  

sub html_Title {
################
  my ($title, @content) = @_;
  my (@res);
#
#  On modifie le titre (sous réserve qu'il existe ...).
#
  foreach (@content) {
    s/<TITLE>.*<\/TITLE>/<TITLE>$title<\/TITLE>/gi;
    push (@res,$_);
  }
  return(@res);
}
sub html_Background {
#####################
  my ($background, @content) = @_;
  my (@res);
#
# On ajoute un attribut bgcolor à la balise BODY 
# si elle existe.
#
  foreach (@content) {
    if (/.*<BODY/i) {
      $_ = $&." bgcolor='$background'".$';
    }
    push (@res,$_);
  }
  return(@res);
}
#  Quand on importe un package en utilisant en utilisant 'use'
#  le contenu du package est passé à 'eval'. La dernière expression
#  évaluée doit rendre vrai car sinon 'eval' échoue ...
return(1);
  

  

#!/usr/bin/perl
#  TP15
#  Utilisation du package html.pm pour modifier
#  un document HTML.
use html;             # Inclusion du package html
use strict 'vars';
my $File;
# S'il n'est pas donné de fichier, on traite index.html
unless (@ARGV) {
  $File = "index.html";
}
else {
  $File = $ARGV[0];
}
unless (open(IN,$File)) {
  print ("Impossible d'ouvrir $File : $! \n");
  exit();
}
my @Content = <IN>;
close(IN);
#  Appel des fonctions du package
@Content = html_Escape(@Content);
@Content = html_Title("nouveau titre",@Content);
@Content = html_Background("#ff0000",@Content);
#  Mise à jour du fichier
unless (open(OUT,">$File.new")) {
  print ("Impossible de réécrire $File.new : $! \n");
  exit();
}
unless (print OUT (@Content)) {
  print ("Impossible de réécrire $File.new : $! \n");
  exit();
}
close(OUT);
unless (rename("$File.new",$File)) {
  print ("Impossible de mettre à jour $File : $! \n");
  exit();
}
else {
  print ("$File modifié à l'aide de $html_Version \n");
}
  
previous up contents next


FD