Article publié dans Linux Magazine 94, mai 2007.
La perle de ce mois-ci a été rédigée par Philippe "BooK" Bruhat
(book@mongueurs.net
), de Lyon.pm et Paris.pm.
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).
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é :
la ligne To:
des en-têtes,
To: "[% prenom %] [% nom %]" <[% email %]>
et la ligne du message contenant la note :
voici ta note de l'interro 2: [% note %]
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^W
modèle.
Regexp::Assemble
pour trouver les adressesIl 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 ! ;-)
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.