Article publié dans Linux Magazine 96, juillet/août 2007.
PAR
et PAR::Packer
La perle de ce mois-ci a été rédigée par Laurent Gautrot,
(l.gautrot@free.fr
), de Paris.pm.
La RFC 2445 [1] rappelle qu'il y a un vrai besoin de standard pour l'interopérabilité des services d'agenda ou de calendriers.
Elle définit en outre le format iCalendar pour permettre d'échanger ou de stocker des informations variées comme les calendriers, des listes de choses à faire ou des évènements.
De fait, le format iCalendar est utilisé par de nombreuses applications de gestion d'informations personnelles, qui savent lire et produire des fichiers .ics.
Le format reste très libre, et l'interprétation de la sémantique reste à définir pour l'unicité des évènements, en particulier.
Justement, notre grand chef, après une migration de son système sur une nouvelle version a essayé de synchroniser son Palm Treo680 avec son calendrier Evolution, mais après plusieurs essais infructueux, le système lui présente désormais ses rendez-vous en double, ou en triple. C'est mal.
Heureusement, Perl et CPAN sont là. Une brève recherche (Merci Jérôme) sur CPAN, grâce à l'excellent CPAN Suggest [3] fournit rapidement une réponse et nous aiguille entre autres sur quelques modules excellents et bien documentés de Jesse Vincent (papa entre autres de Request Tracker).
Pour explorer le contenu du calendrier, une ligne comme la suivante suffit (bon, deux, pour préciser le module qui va être utilisé).
use Data::ICal; my $calendar = Data::ICal->new($filename);
iCalendar définit de nombreux composants, rebaptisés Entries qui généralement n'ont pas de sous-composants, mais certains peuvent en avoir. Chaque composant peut alors avoir des propriétés, dont certaines sont uniques et d'autres peuvent apparaître plusieurs fois.
Pour filtrer les méchants doublons, il faut commencer par identifier ce qui fait de deux enregistrements similaires des doublons.
Evolution va assez loin dans le respect des standards en utilisant iCalendar comme format de stockage des données. Un export n'est alors même pas nécessaire.
Pour commencer, je ne m'intéresse qu'aux enregistrements de type VEVENT, qui sont les seuls qui apparaissent dans mon fichier de données. Il existe d'autres types de composants (VTODO, VTIMEZONE, etc.) et chacun a une structure particulière.
Je suis parti du principe que deux VEVENT seraient identiques si leurs dates de début, de fin et leur titre étaient identiques. En fait, c'est erroné parce que certains VEVENT n'ont pas de titre.
Après investigation, il s'avère que ces évènements sont des scories inutiles qui peuvent être supprimées sans état d'âme.
Le script suivant décrit l'intégralité du traitement. Après avoir ouvert un calendrier en entrée et un autre en sortie, je passe en revue tous les composants de mon fichier d'entrée. Le script est normalement invoqué avec deux fichiers en arguments, le fichier d'entrée et le fichier de sortie. À défaut, on utilisera un fichier nommé calendar.ics dans le répertoire courant en entrée et la sortie sera écrite dans le fichier output_calendar.ics.
Dans le cas des VEVENT, il faut vérifier la présence de la propriété
summary
, et prévenir de son absence.
Une table de hachage permet de conserver un index constitué des dates de début,
de fin et du titre. Pour les évènements qui n'apparaissent pas dans cette liste,
il suffit de passer son chemin. Dans le cas contraire, une sauvegarde s'impose,
réalisée à l'aide de la méthode add_entry()
.
Pour tous les autres composant, une conservation à l'identique est réalisée.
En fin de traitement, l'ensemble du calendrier est sauvegardé et un petit résumé est affiché, pour rappeler le nombre de composants de chaque type.
#!/usr/bin/perl use strict; use warnings; use Data::ICal; my $input_calendar = Data::ICal->new( filename => shift || 'calendar.ics' ); my $output_calendar = Data::ICal->new(); my %component = (); my %event = (); open my $output, '>', shift || 'output_calendar.ics' or warn "Could not open output file: $!\n"; ENTRY: foreach my $entry ( @{ $input_calendar->entries } ) { $component{ $entry->ical_entry_type }++; if ( $entry->ical_entry_type =~ /VEVENT/ ) { my $check = ''; if ( $entry->property('summary') ) { foreach my $property (qw/ summary dtstart dtend /) { foreach ( @{ $entry->property($property) } ) { $check .= $_->value; } } $component{' Duplicate VEVENT'}++, next ENTRY if exists $event{$check}; $event{$check} = undef; } else { $component{' Without summary'}++; next ENTRY; } } $component{' Backed up entry'}++; $output_calendar->add_entry($entry); } END { print "Saving output calendar... "; print $output $output_calendar->as_string; close $output or die "Could not close output file: $!\n"; print "done.\n"; print "Processed entries:\n"; foreach my $key ( reverse sort keys %component ) { printf "%10s %i\n", $key, $component{$key}; } }
Au départ, j'avais tenté d'afficher un résumé des événements rencontrés.
Le problème, c'est que j'avais une erreur un peu méchante quand j'ai essayé de
pointer sur une propriété inexistante (précisément la propriété summary
).
Avec le bloc END
, j'avais au moins mon bilan, ce qui m'a permis de
savoir que j'avais parcouru 148 VEVENT
et que le 149ème était un VEVENT
sans summary
.
Bien entendu, il pourrait y avoir de nombreuses améliorations dans la récupération des fichiers de calendrier en entrée et en sortie, mais en l'état, ça fonctionne plutôt bien, et pour un calendrier comportant près de 8500 évènements, le fichier d'origine pesant un peu moins de 4 Mo, la suppression des doublons est opérée en 24 secondes sur mon ordi portable.
% time perl remove_duplicates_from_ical Saving output calendar... done. Processed entries: VTIMEZONE 5 VEVENT 8482 Without summary 1518 Duplicate VEVENT 3808 Backed up 3161 perl remove_duplicates_from_ical 23,54s user 0,40s system 99% cpu 24,004 total
PAR
et PAR::Packer
Comme notre grand chef ne goûte certainement pas aux joies du shell CPAN, il faut trouver un moyen d'empaqueter dans un programme prêt à l'emploi, sans se soucier des dépendances de modules.
Ça tombe plutôt bien, PAR
est fait pour cela. Ce module, permet de trouver et
d'empaqueter dans un binaire, ou dans un énorme script Perl toutes les
dépendances identifiées d'un script.
Initialement écrit par Audrey Tang, et actuellement maintenu par Steffen
Mueller, PAR
dispose d'une commande pour générer directement des exécutables
ou des binaires pour quelques plate-formes. Il semble y avoir quelques
restrictions pour construire des binaires avec des interfaces graphiques sur
quelques plate-formes, consultez la perldoc pour plus d'informations.
Un module a particulièrement retenu mon attention. PAR::Packer
contient la
commmande pp
, dont l'aide en ligne est accessible par perldoc
également.
Dans le cas de ce script, j'ai choisi de générer un binaire, qui bien qu'il soit
compressé (compression généraliste gzip
, niveau de compression 9), pèse tout
de même ses 3 Mo.
% pp -C -x -z 9 -o remdupical remove_duplicates_from_ical Saving output calendar... done. Processed entries: VTIMEZONE 5 VEVENT 8482 Without summary 1518 Duplicate VEVENT 3808 Backed up 3161 % ls -l remdupical remove_duplicates_from_ical -rwxr-xr-x 1 lg lg 3111695 mai 21 11:37 remdupical -rw-r--r-- 1 lg lg 1495 mai 21 11:36 remove_duplicates_from_ical
La taille du binaire généré impressionne un peu (enfin, moi, surtout), et
j'ai eu la mauvaise idée de vouloir lui faire subir une cure
d'amaigrissement. Un passage de strip
sur le binaire permet d'enlever les
symboles de debogage.
% strip rempdupical % ll remdupical remove_duplicates_from_ical -rwxr-xr-x 1 lg lg 1316988 mai 21 11:55 remdupical -rw-r--r-- 1 lg lg 1495 mai 21 11:36 remove_duplicates_from_ical
Sauf que le résultat est le suivant :
% ./remdupical Usage: ./remdupical [ -Alib.par ] [ -Idir ] [ -Mmodule ] [ src.par ] [ program.pl ] ./remdupical [ -B|-b ] [-Ooutfile] src.par
Le script Perl généré est un peu moins imposant que le binaire.
Avec un petit script en Perl, et des outils respectueux des standards, j'ai pu facilement récupérer un calendrier utilisable.
Reste à trouver les raisons des problèmes de synchronisation d'agenda. Mais ceci est une autre histoire.
[1] RFC 2445 - ftp://ftp.rfc-editor.org/in-notes/rfc2445.txt
[2] CPAN Suggest - http://cpantools.com/
[3] Data::ICal
sur CPAN -
http://search.cpan.org/~jesse/Data-ICal/
[4] PAR
sur CPAN -
http://search.cpan.org/~smueller/PAR/lib/PAR.pm
[5] PAR::Packer
-
http://search.cpan.org/~smueller/PAR-Packer/lib/PAR/Packer.pm
Envoyez vos perles à perles@mongueurs.net
, elles seront peut-être
publiées dans un prochain numéro de Linux Magazine.
Auteur: Laurent Gautrot
Copyright © Les Mongueurs de Perl, 2001-2011
pour le site.
Les auteurs conservent le copyright de leurs articles.