[couverture de Linux Magazine 94]

Perles de Mongueurs (32)

Article publié dans Linux Magazine 94, mai 2007.

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

La perle de ce mois-ci a été rédigée par Philippe "BooK" Bruhat (book@mongueurs.net), de Lyon.pm et Paris.pm.

Préparation artisanale d'un mailing

Mon épouse est enseignante, et elle a récemment dit à ses élèves qu'elle allait, suite à une absence, leur envoyer par mail leurs notes au dernier devoir, individuellement.

Les notes sont disponibles dans un fichier gnumeric, avec les noms des élèves. Elle veut cependant se laisser la possibilité de rajouter éventuellement un message personnalisé. Sans envoyer automatiquement des emails en masse à partir d'un fichier, je voulais lui faciliter la vie autant que possible.

L'objectif est donc de prendre la liste des notes et de produire un message pour chaque élève de la classe dans sa boîte de « brouillons » (sous pine, c'est le fichier ~/mail/postponed-msgs).

Template Toolkit pour produire les messages

Qui dit message formaté, dit utilisation d'un modèle. Nous n'allons pas nous encombrer de détails, aussi vais-je me servir de l'outil utilisé pour tous les sites des Mongueurs et qui est de plus déjà installé sur ma machine : Template Toolkit.

Le mail sera défini dans un modèle, les e-mails, noms et notes des élèves le seront dans un fichier (obtenu à partir de son tableur). Nous allons donc produire un message au format mbox qu'il suffira ensuite d'ajouter à la fin de ~/mail/postponed-msgs.

Le plus simple est évidemment d'écrire un brouillon de message avec pine et de le sauver pour une édition ultérieure (postpone). Voici le résultat brut tel que sauvegardé par pine :

    From prof@localhost.localdomain Sun Jan 14 23:37:48 2007 +0100
    Newsgroups:
    Date: Sun, 14 Jan 2007 23:37:48 +0100 (CET)
    From: Votre prof <prof@grande-ecole.fr>
    X-X-Sender: prof@localhost.localdomain
    To: "Eleve 71" <Eleve71@grande-ecole.fr>
    Reply-To: prof@grande-ecole.fr
    Subject: note de l'interro 2
    Fcc: sent-mail
    Message-ID: <Pine.LNX.4.64.0701192333580.7140@localhost.localdomain>
    X-Cursor-Pos: : 129
    X-Our-ReplyTo: Full
    MIME-Version: 1.0
    Content-Type: MULTIPART/MIXED; BOUNDARY="8323329-2067678150-1169246090=:7187"
    Status: O
    X-Status:
    X-Keywords:
    X-UID: 1

    --8323329-2067678150-1169246090=:7187
    Content-Type: TEXT/PLAIN; CHARSET=iso-8859-1; format=flowed
    Content-Transfer-Encoding: QUOTED-PRINTABLE
    
    Bonsoir,
    
    voici ta note de l'interro 2: 12
    
    La moyenne du groupe est de 9,3. Les notes vont de 3,5 =E0 18.
    
    A bient=F4t,

    Votre prof

    --8323329-2067678150-1169246090=:7187

Bref, c'est l'horreur : il y a plein d'en-têtes dont on se moque (et certains sont de plus supposés être des identifiants uniques, nous devrions éviter de créer des doublons), le contenu du message est en multipart-MIME encodé en quoted-printable...

Heureusement, pine n'est pas si bête et sait travailler à partir d'une version minimale d'un message copié dans un fichier au format mbox. Le texte suivant suffit amplement :

    From prof@localhost.localdomain Sun Jan 14 23:37:48 2007 +0100
    From: prof <prof@grande-ecole.fr>
    Reply-To: prof@grande-ecole.fr>
    Subject: note de l'interro 2
    To: "Eleve 71" <Eleve71@grande-ecole.fr>
    Fcc: sent-mail

    Bonsoir,

    voici ta note de l'interro 2: 12

    La moyenne du groupe est de 9,3. Les notes vont de 3,5 à 18.

    A bientôt,

    Votre prof

Quand on sauvegarde le message après vérification dans pine, on constate que pine l'a sauvé sous une forme similaire à notre premier exemple. Parfait.

Pour utiliser notre outil de template, il suffit de remplacer deux lignes dans notre message épuré :

On a remplacé les informations cruciales par les variables du modèle : [% prenom %], [% nom %], [% email %] et [% note %]. C'est avec [% %] que l'on peut insérer des directives Template Toolkit dans n'importe quel document^Wmodèle.

Regexp::Assemble pour trouver les adresses

Il reste cependant un petit problème pratique... Trouver l'email de chaque élève. En effet, si la plupart des adresses sont de la forme Prenom.Nom@grande-ecole.fr, ce n'est pas le cas pour toutes les adresses. L'administration a parfois dû ajouter les seconds prénoms pour gérer des problèmes d'homonymie.

Heureusement, Estelle dispose des adresses email de tous ses élèves. Il reste donc à établir la correspondance entre le nom d'un élève dans le fichier de notes et son email dans la liste d'adresses.

En fait, tout le travail a déjà été fait par David Landgren, l'auteur de Regexp::Assemble (que nous avons déjà vu dans la Perle 29)

Regexp::Assemble peut produire des regexp "suivies" (tracked), c'est à dire qu'il est capable de retrouver laquelle des regexp ayant servi à la construire a déclenché la correspondance.

    my $re = Regexp::Assemble->new()->track(1);

Il suffit ensuite d'ajouter les expressions avec add().

Dans les quelques classes dont elle a la charge, il n'y a pas deux élèves qui portent le même nom. Nous allons donc utiliser les noms de famille des élèves comme clés d'un hachage dont les valeurs sont les adresses correspondantes, sans nous préoccuper des problèmes d'homonymie.

Cette technique de table de correspondance basée sur des expressions régulières est extrêmement puissante, et d'une grande simplicité à mettre en œuvre grâce au travail de David.

Au final, le script est assez court :

    #!/usr/bin/perl
    use strict;
    use warnings;
    use Template;
    use Regexp::Assemble;

    # utilisation: prepare_emails.pl notes.csv emails.txt modele.tt

    # noms de fichiers
    my ( $notes, $emails, $modele ) = @ARGV;
    my $fh;

    # récupération de la liste des emails
    # pour construire une table de distribution
    open $fh, $emails or die "Impossible d'ouvrir $emails: $!";
    my %emails = map {
        chomp;            # supprime le saut de ligne final
        /\.([^.]*)\@/;    # trouve le nom dans l'adresse email
        ( my $re = $1 ) =~ s/-/\\W/g;    # remplace les - par \W
        ( $re => $_ )                    # produit la table de distribution
    } <$fh>;
    close $fh;

    # invoque la puissante magie de Regexp::Assemble
    my $re
        = Regexp::Assemble->new( flags => 'i' )->track(1)->add( keys %emails );

    # création de l'objet Template
    my $tt = Template->new();

    # ouverture du fichier de notes
    open $fh, $notes or die "Impossible d'ouvrir $notes: $!";

    # boucle de lecture des données élèves
    while (<$fh>) {
        next if /^\s*(?:#|$)/;    # ignore commentaires et lignes blanches
        chomp;                    # supprime le saut de ligne final

        my %vars;
        @vars{qw( nom prenom note )} = split /,/;    # CSV
        if ( $re->match( $vars{nom} ) ) {
            $vars{email} = $emails{ $re->matched() };
        }
        else {
            # message d'erreur si l'email n'a pas été trouvé
            print STDERR
                "Impossible de trouver l'email de @vars{qw(prenom nom)}\n";
            next;
        }

        # produit le message
        $tt->process( $modele, \%vars, \*STDOUT )
            or die $tt->error();
    }

    close $fh;

Ce script affiche le résultat sur la sortie standard, afin de faciliter la vérification des messages produits. Pour ajouter les messages aux brouillons de pine, il suffit de faire :

    ./prepare_emails.pl notes.csv emails.txt modele.tt >> ~/mail/postponed-msgs

Il ne lui restera plus qu'à vérifier les messages et à les envoyer. Quant à moi, j'ai enfin pu utiliser les regexps suivies de Regexp::Assemble, et surtout j'ai une nouvelle fois réussi à convaincre ma douce de l'intérêt d'avoir un mongueur de Perl à la maison ! ;-)

À 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]