<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>Lab &amp; stacks</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/" />
    <link rel="self" type="application/atom+xml" href="http://blog.bjornoya.be/lab_stacks/atom.xml" />
    <id>tag:blog.bjornoya.be,2009-07-15:/lab_stacks//1</id>
    <updated>2012-05-15T10:09:12Z</updated>
    
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type 4.261</generator>

<entry>
    <title>Faire une copie des fichiers produits par DBIx::Class::Schema::Loader</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/2012/05/faire-une-copie-des-fichiers-produits-par-dbixclassschemaloader.html" />
    <id>tag:blog.bjornoya.be,2012:/lab_stacks//1.43</id>

    <published>2012-05-15T10:05:58Z</published>
    <updated>2012-05-15T10:09:12Z</updated>

    <summary>DBIx::Class::Schema::Loader est un module qui permet de générer
automatiquement un schéma DBIx::Class::Schema à partir d&apos;une base de
données. C&apos;est donc l&apos;outil indispensable si vous avez une base de
données sous la main que vous voulez manipuler à partir de
DBIx::Class. 


DBIx::Class::Schema::Loader vient avec un utilitaire nommé
dbicdump qui, comme son nom le laisse entendre, permet de copier le
schéma créé automatiquement par DBIx::Class::Schema::Loader sur le
disque dur. 


Dans son utilisation la plus simple, dbicdump s&apos;emploie de la
manière suivante :



dbicdump MyDB::Schema dbi:SQLite:dbname=MyDB.db 




Donc le premier argument est le nom du schéma DBIx::Class::Schema
qui sera employé. 


Le second argument est le dsn qui sera employé pour se connecter à
la base de données. Dans le cas présent, le dsn est une base de
données SQLite pour laquelle il n&apos;est pas nécessaire de donner les
identifiants de connexion. Si ces derniers avaient été nécessaires,
ils auraient été les troisième ét quatrième arguments. 


Après l&apos;exécution de la commande ci-dessus, vous aurez un répertoire
MyDB contenant un fichier nommé Schema.pm et un répertoire nommé
Schema. Dans ce dernier, nous aurons un sous-répertoire nommé
Result, qui lui même contiendra les différents fichiers nécessaires
pour gérer les tables de la base de données. Cette structure de
fichier respecte les conventions habituelles de DBIx::Class. Il est
donc nécessaire de se familiariser avec ces conventions avant de
pouvoir tirer parti complètement de la puissance de cet outil. 


Une question que je n&apos;ai pas encore eu l&apos;occasion d&apos;approfondir est de
savoir s&apos;il est préférable d&apos;utiliser DBIC::Schema::Loader en
mémoire vive ou s&apos;il est préférable d&apos;utiliser dbicdump. Il y a un
coût à l&apos;analyse effectuée par DBIx::Class::Schema::Loader pour
créer les classes DBIC, il faut donc voir dans quelles situations ce
coût est justifié ; probablement lors du début du projet, quand le
schéma de la base de données évolue encore.


Quoi qu&apos;il en soit, dbicdump et DBIx::Class::Schema::Loader sont
deux outils essentiels pour la boîte à outil du mongueur devant
travailler avec des bases de données. 

</summary>
    <author>
        <name>manu</name>
        
    </author>
    
        <category term="Français" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="dbi" label="DBI" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="dbic" label="DBIC" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="dbixclass" label="DBIx::Class" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="perl" label="perl" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="fr" xml:base="http://blog.bjornoya.be/lab_stacks/">
        <![CDATA[<p><code>DBIx::Class::Schema::Loader</code> est un module qui permet de générer
automatiquement un schéma <code>DBIx::Class::Schema</code> à partir d'une base de
données. C'est donc l'outil indispensable si vous avez une base de
données sous la main que vous voulez manipuler à partir de
<code>DBIx::Class</code>. 
</p>
<p>
<code>DBIx::Class::Schema::Loader</code> vient avec un utilitaire nommé
<code>dbicdump</code> qui, comme son nom le laisse entendre, permet de copier le
schéma créé automatiquement par <code>DBIx::Class::Schema::Loader</code> sur le
disque dur. 
</p>
<p>
Dans son utilisation la plus simple, <code>dbicdump</code> s'emploie de la
manière suivante :
</p>


<pre class="src src-sh">dbicdump MyDB::Schema dbi:SQLite:<span style="color: #dfaf8f;">dbname</span>=MyDB.db 
</pre>


<p>
Donc le premier argument est le nom du schéma <code>DBIx::Class::Schema</code>
qui sera employé. 
</p>
<p>
Le second argument est le <code>dsn</code> qui sera employé pour se connecter à
la base de données. Dans le cas présent, le <code>dsn</code> est une base de
données <i>SQLite</i> pour laquelle il n'est pas nécessaire de donner les
identifiants de connexion. Si ces derniers avaient été nécessaires,
ils auraient été les troisième ét quatrième arguments. 
</p>
<p>
Après l'exécution de la commande ci-dessus, vous aurez un répertoire
<code>MyDB</code> contenant un fichier nommé <code>Schema.pm</code> et un répertoire nommé
<code>Schema</code>. Dans ce dernier, nous aurons un sous-répertoire nommé
<code>Result</code>, qui lui même contiendra les différents fichiers nécessaires
pour gérer les tables de la base de données. Cette structure de
fichier respecte les conventions habituelles de <code>DBIx::Class</code>. Il est
donc nécessaire de se familiariser avec ces conventions avant de
pouvoir tirer parti complètement de la puissance de cet outil. 
</p>
<p>
Une question que je n'ai pas encore eu l'occasion d'approfondir est de
savoir s'il est préférable d'utiliser <code>DBIC::Schema::Loader</code> en
mémoire vive ou s'il est préférable d'utiliser <code>dbicdump</code>. Il y a un
coût à l'analyse effectuée par <code>DBIx::Class::Schema::Loader</code> pour
créer les classes <code>DBIC</code>, il faut donc voir dans quelles situations ce
coût est justifié ; probablement lors du début du projet, quand le
schéma de la base de données évolue encore.
</p>
<p>
Quoi qu'il en soit, <code>dbicdump</code> et <code>DBIx::Class::Schema::Loader</code> sont
deux outils essentiels pour la boîte à outil du mongueur devant
travailler avec des bases de données. 
</p></div>
]]>
    </content>
</entry>

<entry>
    <title>The Library? Get It Uncensored!</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/2011/05/the-library-get-it-uncensored.html" />
    <id>tag:blog.bjornoya.be,2011:/lab_stacks//1.42</id>

    <published>2011-05-19T13:02:56Z</published>
    <updated>2011-05-19T13:03:55Z</updated>

    <summary> table.hbc {border-width:0;border-spacing:0;}table.hbc {border-width:0;border-spacing:0;}table.hbc tr, table.hbc td{border:0;margin:0;padding:0;}table.hbc td{text-align:center;}table.hbc td.hbc_on,table.hbc td.hbc_off {width:3px;height:3px;}table.hbc td.hbc_on {background-color:#000;color:inherit;}table.hbc td.hbc_off {background-color:#fff;color:inherit;}
</summary>
    <author>
        <name>manu</name>
        
    </author>
    
    <category term="inforum2011" label="inforum2011" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="fr" xml:base="http://blog.bjornoya.be/lab_stacks/">
        <![CDATA[ <style type="text/css">table.hbc {border-width:0;border-spacing:0;}table.hbc {border-width:0;border-spacing:0;}table.hbc tr, table.hbc td{border:0;margin:0;padding:0;}table.hbc td{text-align:center;}table.hbc td.hbc_on,table.hbc td.hbc_off {width:3px;height:3px;}table.hbc td.hbc_on {background-color:#000;color:inherit;}table.hbc td.hbc_off {background-color:#fff;color:inherit;}</style><table class="hbc"><tr><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td></tr><tr><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td></tr><tr><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td></tr><tr><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td></tr><tr><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td></tr><tr><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td></tr><tr><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td></tr><tr><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td></tr><tr><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td></tr><tr><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td></tr><tr><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td></tr><tr><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td></tr><tr><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td></tr><tr><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td></tr><tr><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td></tr><tr><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td></tr><tr><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td></tr><tr><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td></tr><tr><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td></tr><tr><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td></tr><tr><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td></tr><tr><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td></tr><tr><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td></tr><tr><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_off"></td></tr><tr><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_off"></td><td class="hbc_off"></td><td class="hbc_on"></td><td class="hbc_on"></td></tr></table>
]]>
    </content>
</entry>

<entry>
    <title>Un peu d&apos;R pour respirer lors de la correction des examens</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/2011/01/un-peu-dr-pour-respirer-lors-de-la-correction-des-examens.html" />
    <id>tag:blog.bjornoya.be,2011:/lab_stacks//1.41</id>

    <published>2011-01-30T11:08:47Z</published>
    <updated>2011-01-30T11:11:42Z</updated>

    <summary>
Mouais, le titre de ce billet rentre définitivement dans le genre «
vanne pourrie ». 


Avec la saison des examens pour les étudiants, vient inexorablement la
période des corrections pour le professeur. Jusqu&apos;à présent, je
pratiquais la « correction par calculatrice ». C&apos;est-à-dire qu&apos;après
avoir corrigé les copies, je prend ma fidèle calculatrice, un examen
de la pile, et je fais la somme des points obtenus pour les
différentes questions, je divise ensuite le total obtenu par le
diviseur de manière à avoir ma cote sur 20. 


Cette méthode est probablement pratiquée par bon nombre de prof, mais
elle ne me satisfaisait pas vraiment. En effet, si le total des points
obtenus par mes étudiants est intéressant, j&apos;aimerais également avoir
les résultats par question1, voire même établir des
corrélations entre certaines variables.


Une solution choisie par beaucoup de professeurs de nos jours est de
s&apos;appuyer sur un tableur comme Excel ou OpenOffice.org Calc. On encode
ses données, on construit ses formules et, hop, nous avons les
résultats. Le seul problème, de mon point de vue (et oui, je chipote),
c&apos;est que l&apos;on doit reconstruire systématiquement son tableur :


avec les données, et c&apos;est difficile de faire autrement !


avec les formules, là par contre, on pourrait abstraire





Et pour abstraire les formules, pourquoi ne pas écrire un script !
Bon, je pourrais le faire avec Perl, ou n&apos;importe quel autre langage
de scripts (Python, Ruby, etc), mais puisque mon but est de faire des
statistiques sur les données, autant utiliser un langage de script
spécialisé dans le traitement des données. J&apos;ai nommé R. Il y a pas
mal de bouquins qui sont sortis sur l&apos;analyse de données, et sur R ces
derniers temps. J&apos;ai donc eu l&apos;occasion de lire quelques introductions
sur ce langage, et donc sans être spécialiste, j&apos;ai pu écrire le
script suivant






Ce code est relativement trivial et n&apos;est probablement pas idiomatique
pour les programmeurs chevronnés. Mais il fonctionne, et je ne demande
qu&apos;à m&apos;améliorer2. 


La première ligne, #!/usr/bin/env Rscript est le shebang classique
pour le script. Dans le cas de R, un script existe de manière à passer
les bons arguments à l&apos;interpréteur R. 


Ce script reçoit des arguments, et je dois donc les récupérer. Je suis
resté dans une gestion basique, et donc j&apos;ai utilisé la fonction
commandArgs(TRUE) qui retourne une liste des arguments. L&apos;ordre de
ces arguments est arbitraire. Dans mon cas :


le premier argument doit être le nom du fichier CSV ;


le second argument doit être le total de l&apos;examen. 





La première opération est donc de calculer le diviseur nécessaire pour
obtenir la cote finale. 


La deuxième opération consiste à « charger » les données. 


Et ensuite, je me construit une seconde structure de données qui
contiendra les résultats de l&apos;opération, et je boucle pour traiter les
données. 


Ensuite, j&apos;affiche la moyenne pour le cours, ainsi que la structure de
données. 


Et le boulot est fait ! 


Côté amélioration, voici ce qui m&apos;a déjà traversé la tête :


gestion des spécificités des fichiers CSV (gestion de différents
séparateurs, des symboles pour encadrer les données, etc) ;


gestion des colonnes « examens » et de colonnes « travaux pratiques
» ;


produire un résultat final en tenant compte du fait que l&apos;examen
compte pour 60% de la cote finale, alors que les travaux compte
pour 40% ;


utiliser getopt pour gérer les arguments du script ;


créer un fichier CSV contenant les données calculées plutôt que de
simplement les afficher.








Notes de bas de page: 

1 ce qui me permet de reconnaître les
questions difficiles, lesquelles pourraient mettre en évidence une
matière que j&apos;ai moins bien expliqué

2 donc si vous avez des suggestions pour
l&apos;améliorer, n&apos;hésitez surtout pas !



</summary>
    <author>
        <name>manu</name>
        
    </author>
    
        <category term="Français" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="r" label="R" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="fr" xml:base="http://blog.bjornoya.be/lab_stacks/">
        <![CDATA[<div id="content">
<p>Mouais, le titre de ce billet rentre définitivement dans le genre «
vanne pourrie ». 
</p>
<p>
Avec la saison des examens pour les étudiants, vient inexorablement la
période des corrections pour le professeur. Jusqu'à présent, je
pratiquais la « correction par calculatrice ». C'est-à-dire qu'après
avoir corrigé les copies, je prend ma fidèle calculatrice, un examen
de la pile, et je fais la somme des points obtenus pour les
différentes questions, je divise ensuite le total obtenu par le
diviseur de manière à avoir ma cote sur 20. 
</p>
<p>
Cette méthode est probablement pratiquée par bon nombre de prof, mais
elle ne me satisfaisait pas vraiment. En effet, si le total des points
obtenus par mes étudiants est intéressant, j'aimerais également avoir
les résultats par question<sup><a class="footref" name="fnr.1" href="#fn.1">1</a></sup>, voire même établir des
corrélations entre certaines variables.
</p>
<p>
Une solution choisie par beaucoup de professeurs de nos jours est de
s'appuyer sur un tableur comme Excel ou OpenOffice.org Calc. On encode
ses données, on construit ses formules et, hop, nous avons les
résultats. Le seul problème, de mon point de vue (et oui, je chipote),
c'est que l'on doit reconstruire systématiquement son tableur :
</p><ol>
<li>
avec les données, et c'est difficile de faire autrement !
</li>
<li>
avec les formules, là par contre, on pourrait abstraire
</li>
</ol>


<p>
Et pour abstraire les formules, pourquoi ne pas écrire un script !
Bon, je pourrais le faire avec Perl, ou n'importe quel autre langage
de scripts (Python, Ruby, etc), mais puisque mon but est de faire des
statistiques sur les données, autant utiliser un langage de script
spécialisé dans le traitement des données. J'ai nommé <a href="http://www.r-project.org/">R</a>. Il y a pas
mal de bouquins qui sont sortis sur l'analyse de données, et sur R ces
derniers temps. J'ai donc eu l'occasion de lire quelques introductions
sur ce langage, et donc sans être spécialiste, j'ai pu écrire le
script suivant
</p>


<script src="https://gist.github.com/802775.js?file=gistfile1.r"></script>

<p>
Ce code est relativement trivial et n'est probablement pas idiomatique
pour les programmeurs chevronnés. Mais il fonctionne, et je ne demande
qu'à m'améliorer<sup><a class="footref" name="fnr.2" href="#fn.2">2</a></sup>. 
</p>
<p>
La première ligne, <code>#!/usr/bin/env Rscript</code> est le <i>shebang</i> classique
pour le script. Dans le cas de R, un script existe de manière à passer
les bons arguments à l'interpréteur R. 
</p>
<p>
Ce script reçoit des arguments, et je dois donc les récupérer. Je suis
resté dans une gestion basique, et donc j'ai utilisé la fonction
<code>commandArgs(TRUE)</code> qui retourne une liste des arguments. L'ordre de
ces arguments est arbitraire. Dans mon cas :
</p><ol>
<li>
le premier argument doit être le nom du fichier CSV ;
</li>
<li>
le second argument doit être le total de l'examen. 
</li>
</ol>


<p>
La première opération est donc de calculer le diviseur nécessaire pour
obtenir la cote finale. 
</p>
<p>
La deuxième opération consiste à « charger » les données. 
</p>
<p>
Et ensuite, je me construit une seconde structure de données qui
contiendra les résultats de l'opération, et je boucle pour traiter les
données. 
</p>
<p>
Ensuite, j'affiche la moyenne pour le cours, ainsi que la structure de
données. 
</p>
<p>
Et le boulot est fait ! 
</p>
<p>
Côté amélioration, voici ce qui m'a déjà traversé la tête :
</p><ol>
<li>
gestion des spécificités des fichiers CSV (gestion de différents
séparateurs, des symboles pour encadrer les données, etc) ;
</li>
<li>
gestion des colonnes « examens » et de colonnes « travaux pratiques
» ;
</li>
<li>
produire un résultat final en tenant compte du fait que l'examen
compte pour 60% de la cote finale, alors que les travaux compte
pour 40% ;
</li>
<li>
utiliser <i>getopt</i> pour gérer les arguments du script ;
</li>
<li>
créer un fichier CSV contenant les données calculées plutôt que de
simplement les afficher.
</li>
</ol>





<div id="footnotes">
<h2 class="footnotes">Notes de bas de page: </h2>
<div id="text-footnotes">
<p class="footnote"><sup><a class="footnum" name="fn.1" href="#fnr.1">1</a></sup> ce qui me permet de reconnaître les
questions difficiles, lesquelles pourraient mettre en évidence une
matière que j'ai moins bien expliqué
</p>
<p class="footnote"><sup><a class="footnum" name="fn.2" href="#fnr.2">2</a></sup> donc si vous avez des suggestions pour
l'améliorer, n'hésitez surtout pas !
</p>
</div>
</div>
]]>
    </content>
</entry>

<entry>
    <title>Petite boîte à outils pour podcasts</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/2010/09/petite-boite-a-outils-pour-podcasts.html" />
    <id>tag:blog.bjornoya.be,2010:/lab_stacks//1.40</id>

    <published>2010-09-12T21:02:27Z</published>
    <updated>2010-09-12T21:05:59Z</updated>

    <summary><![CDATA[L'outil du jour a probablement déjà été écrit, et réécrit par les trois-quarts
des programmeurs de la planète, mais bon, cela fait partie de la petite gym
hebdomadaire. 


J'avais donc besoin d'un outil me permettant de télécharger les fichiers d'un
podcast en ligne de commande. Rien de complexe donc, et aucun outil qui
faisait déjà le travail à ma connaissance (et j'avoue ne pas avoir cherché
!). 


Voici le script : 


#!/usr/bin/env perl
use strict;
use warnings;

package App::podcaster;

use Getopt::Long;
use LWP::UserAgent;
use XML::LibXML;
use File::Spec;
use File::Basename;
use Carp;
use Term::ANSIColor;

__PACKAGE__-&gt;run() unless caller();

sub new {
    my ( $class, %args ) = @_;
    my $self = {};

    $self-&gt;{url}        = delete $args{url};
    $self-&gt;{output_dir} = delete $args{output_dir} || './';
    $self-&gt;{ua}         = LWP::UserAgent-&gt;new(
        agent         =&gt; 'Mozilla/5.0',
        show_progress =&gt; 1,
        cookie_jar    =&gt; {},
    );

    bless $self, $class;
}

sub run {
    my $config = {};
    GetOptions( $config, 'url=s', 'output_dir=s' );
    die _usage() unless _valid($config);

    my $podcaster = App::podcaster-&gt;new(
        url        =&gt; $config-&gt;{url},
        output_dir =&gt; $config-&gt;{output_dir},
    );

    $podcaster-&gt;download_enclosures();
}

sub get_enclosures {
    my ($self) = @_;

    my $res = $self-&gt;{ua}-&gt;get( $self-&gt;{url} );
    if ( $res-&gt;is_success ) {
        my $xml       = $res-&gt;decoded_content;
        my $feed      = XML::LibXML-&gt;load_xml( string =&gt; $xml );
        my @nodes     = $feed-&gt;findnodes('//enclosure/@url');
        my @encl_urls = map { $_-&gt;value } @nodes;
        return @encl_urls;
    }
    else {
        carp $self-&gt;{url}, $res-&gt;status_line, $/;
    }
}

sub download_enclosures {
    my ($self) = @_;

    my @encl_urls = $self-&gt;get_enclosures();
    for (@encl_urls) {
        my $output_filename = $self-&gt;_get_filename($_);
        if ( not -e $output_filename ) {
            my $res =
              $self-&gt;{ua}-&gt;get( $_, ':content_file' =&gt; $output_filename, );

            if ( $res-&gt;is_success ) {
                print colored(
                    ['green'], basename($output_filename) . &quot; is downloaded.&quot;
                ), $/;
            }
            else {
                print colored(
                    ['red'],
                    basename($output_filename)
                      . &quot; can't be downloaded: &quot;
                      . $res-&gt;status_line
                ), $/;
            }
        }
        else {
            print colored( ['green'],
                basename($output_filename) . &quot; is already downloaded.&quot; ),
              $/;
        }
    }
}

sub _get_filename {
    my ( $self, $url ) = @_;

    return File::Spec-&gt;catfile( $self-&gt;{output_dir}, basename($url) );
}

sub _valid {
    my $config = shift;
    if ( exists $config-&gt;{url} and exists $config-&gt;{output_dir} ) {
        return 1;
    }
    else {
        return 0;
    }
}

sub _usage {
    return &quot;Usage: $0 --url http://path.to/rss.xml --output_dir ./\n&quot;;
}

1;


Le script est un modulino, essentiellement car je pense que ce n'est que la
première version de l'outil. J'ai déjà quelques idées de fonctionnalités à
ajouter !


En ce qui concerne l'algorithme, c'est assez simple : 


le programme prend deux arguments, l'URL du podcast et le répertoire où
les fichiers doivent être téléchargés ;


nous récupérons le contenu de l'URL du podcast ;


nous utilisons une expression XPath, //enclosure/@url, pour récupérer les
URLs des différents fichiers ;


nous téléchargeons les différents fichiers. 




Simple, efficace, et j'ai maintenant un outil en ligne de commande pour mes
podcasts ! 
]]></summary>
    <author>
        <name>manu</name>
        
    </author>
    
        <category term="Français" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="cpan" label="cpan" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="lwp" label="lwp" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="modulino" label="modulino" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="perl" label="perl" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="xml" label="xml" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="xpath" label="xpath" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="fr" xml:base="http://blog.bjornoya.be/lab_stacks/">
        <![CDATA[<p>L'outil du jour a probablement déjà été écrit, et réécrit par les trois-quarts
des programmeurs de la planète, mais bon, cela fait partie de la petite gym
hebdomadaire. 
</p>
<p>
J'avais donc besoin d'un outil me permettant de télécharger les fichiers d'un
podcast en ligne de commande. Rien de complexe donc, et aucun outil qui
faisait déjà le travail à ma connaissance (et j'avoue ne pas avoir cherché
!). 
</p>
<p>
Voici le script : 
</p>
<pre class='brush: perl'>
#!/usr/bin/env perl
use strict;
use warnings;

package App::podcaster;

use Getopt::Long;
use LWP::UserAgent;
use XML::LibXML;
use File::Spec;
use File::Basename;
use Carp;
use Term::ANSIColor;

__PACKAGE__-&gt;run() unless caller();

sub new {
    my ( $class, %args ) = @_;
    my $self = {};

    $self-&gt;{url}        = delete $args{url};
    $self-&gt;{output_dir} = delete $args{output_dir} || './';
    $self-&gt;{ua}         = LWP::UserAgent-&gt;new(
        agent         =&gt; 'Mozilla/5.0',
        show_progress =&gt; 1,
        cookie_jar    =&gt; {},
    );

    bless $self, $class;
}

sub run {
    my $config = {};
    GetOptions( $config, 'url=s', 'output_dir=s' );
    die _usage() unless _valid($config);

    my $podcaster = App::podcaster-&gt;new(
        url        =&gt; $config-&gt;{url},
        output_dir =&gt; $config-&gt;{output_dir},
    );

    $podcaster-&gt;download_enclosures();
}

sub get_enclosures {
    my ($self) = @_;

    my $res = $self-&gt;{ua}-&gt;get( $self-&gt;{url} );
    if ( $res-&gt;is_success ) {
        my $xml       = $res-&gt;decoded_content;
        my $feed      = XML::LibXML-&gt;load_xml( string =&gt; $xml );
        my @nodes     = $feed-&gt;findnodes('//enclosure/@url');
        my @encl_urls = map { $_-&gt;value } @nodes;
        return @encl_urls;
    }
    else {
        carp $self-&gt;{url}, $res-&gt;status_line, $/;
    }
}

sub download_enclosures {
    my ($self) = @_;

    my @encl_urls = $self-&gt;get_enclosures();
    for (@encl_urls) {
        my $output_filename = $self-&gt;_get_filename($_);
        if ( not -e $output_filename ) {
            my $res =
              $self-&gt;{ua}-&gt;get( $_, ':content_file' =&gt; $output_filename, );

            if ( $res-&gt;is_success ) {
                print colored(
                    ['green'], basename($output_filename) . &quot; is downloaded.&quot;
                ), $/;
            }
            else {
                print colored(
                    ['red'],
                    basename($output_filename)
                      . &quot; can't be downloaded: &quot;
                      . $res-&gt;status_line
                ), $/;
            }
        }
        else {
            print colored( ['green'],
                basename($output_filename) . &quot; is already downloaded.&quot; ),
              $/;
        }
    }
}

sub _get_filename {
    my ( $self, $url ) = @_;

    return File::Spec-&gt;catfile( $self-&gt;{output_dir}, basename($url) );
}

sub _valid {
    my $config = shift;
    if ( exists $config-&gt;{url} and exists $config-&gt;{output_dir} ) {
        return 1;
    }
    else {
        return 0;
    }
}

sub _usage {
    return &quot;Usage: $0 --url http://path.to/rss.xml --output_dir ./\n&quot;;
}

1;
</pre>
<p>
Le script est un <a href="http://www252.pair.com/comdog/mastering_perl/Chapters/18.modulinos.html">modulino</a>, essentiellement car je pense que ce n'est que la
première version de l'outil. J'ai déjà quelques idées de fonctionnalités à
ajouter !
</p>
<p>
En ce qui concerne l'algorithme, c'est assez simple : 
</p><ol>
<li>
le programme prend deux arguments, l'<b>URL</b> du podcast et le répertoire où
les fichiers doivent être téléchargés ;
</li>
<li>
nous récupérons le contenu de l'URL du podcast ;
</li>
<li>
nous utilisons une expression <a href="http://fr.wikipedia.org/wiki/XPath">XPath</a>, <code>//enclosure/@url</code>, pour récupérer les
URLs des différents fichiers ;
</li>
<li>
nous téléchargeons les différents fichiers. 

</li>
</ol>

<p>Simple, efficace, et j'ai maintenant un outil en ligne de commande pour mes
podcasts ! 
</p>]]>
    </content>
</entry>

<entry>
    <title>Emmener le meilleur avec soi...</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/2010/09/emmener-le-meilleur-avec-soi.html" />
    <id>tag:blog.bjornoya.be,2010:/lab_stacks//1.39</id>

    <published>2010-09-10T21:23:04Z</published>
    <updated>2010-09-10T21:27:41Z</updated>

    <summary><![CDATA[Comme beaucoup de Perl Mongers, je suis un fan absolu du CPAN. Oui, on y
trouve le pire comme le meilleur, mais je dois avouer que je peux vivre
facilement avec le pire d'une part en n'installant pas ces modules (autant que
possible), mais aussi parce qu'une recherche sur le CPAN m'économise souvent
beaucoup de temps. 


Et ce dernier m'est tellement utile que j'accepte aussi de sacrifier une
partie de l'espace de mon disque dur pour le transporter tout le temps avec
moi. Cette opération est réalisée avec le script minicpan, disponible &hellip; sur le CPAN. L'outil s'appuie sur un fichier de configuration assez
simple : 



remote: http://ftp.belnet.be/mirror/ftp.cpan.org/
local: /home/manu/toolbox/minicpan
exact_mirror: 1





Cela me permet d'une part d'avoir de la lecture en abondance lors de mes
déplacements, mais aussi, plus sérieusement, à pouvoir installer de nouveaux
modules même lorsque je n'ai pas de connexion internet. 


Il est alors nécessaire de modifier la configuration de cpan de manière à ce
qu'il utilise les ressources locales plutôt que les ressources
distantes. Voici un bref rappel des commandes cpan utiles dans cette
situation : 


o conf urllist, cela va afficher les URL utilisées par cpan pour
rechercher et installer des modules ;


o conf urllist unshift file:///home/manu/toolbox/minicpan, permet de
rajouter une URL au début de la liste des URL ; comme cpan utilise la
liste dans l'ordre, cela permet de donner la priorité à une ressource ;


o conf urllist shift, permet quant à elle de supprimer le premier élément
de la liste ;


je vous laisse jouer avec o conf urllist pop et o conf urllist push   something ! 


évidemment, ne pas oublier d'utiliser o conf commit pour sauver la
configuration pour le prochain appel à cpan, sauf si vous avez appelé la
commande o conf auto_commit 1 auparavant. 




Avec le CPAN est une chose, mais il est assez agréable aussi de pouvoir
interroger le dépôt via une interface plus agréable que locate, find et
autre grep. C'est pour cela que j'ai installé CPAN::Mini::Webserver qui rend
accessible mon minicpan via une interface web, je je vous en avais déjà parlé précédemment.
]]></summary>
    <author>
        <name>manu</name>
        
    </author>
    
        <category term="Français" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="cpan" label="cpan" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="minicpan" label="minicpan" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="perl" label="perl" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="fr" xml:base="http://blog.bjornoya.be/lab_stacks/">
        <![CDATA[<p>Comme beaucoup de <a href="http://www.pm.org/">Perl Mongers</a>, je suis un fan absolu du <a href="http://search.cpan.org/">CPAN</a>. Oui, on y
trouve le pire comme le meilleur, mais je dois avouer que je peux vivre
facilement avec le pire d'une part en n'installant pas ces modules (autant que
possible), mais aussi parce qu'une recherche sur le <a href="http://search.cpan.org/">CPAN</a> m'économise souvent
beaucoup de temps. 
</p>
<p>
Et ce dernier m'est tellement utile que j'accepte aussi de sacrifier une
partie de l'espace de mon disque dur pour le transporter tout le temps avec
moi. Cette opération est réalisée avec le script <code>minicpan</code>, <a href="http://search.cpan.org/dist/CPAN-Mini/">disponible &hellip; sur le CPAN</a>. L'outil s'appuie sur un fichier de configuration assez
simple : 
</p>


<pre class="src src-conf"><span style="color: #151B54;">remote</span>: http://ftp.belnet.be/mirror/ftp.cpan.org/
<span style="color: #151B54;">local</span>: /home/manu/toolbox/minicpan
<span style="color: #151B54;">exact_mirror</span>: 1
</pre>



<p>
Cela me permet d'une part d'avoir de la lecture en abondance lors de mes
déplacements, mais aussi, plus sérieusement, à pouvoir installer de nouveaux
modules même lorsque je n'ai pas de connexion internet. 
</p>
<p>
Il est alors nécessaire de modifier la configuration de <code>cpan</code> de manière à ce
qu'il utilise les ressources locales plutôt que les ressources
distantes. Voici un bref rappel des commandes <code>cpan</code> utiles dans cette
situation : 
</p><ul>
<li>
<code>o conf urllist</code>, cela va afficher les URL utilisées par <code>cpan</code> pour
rechercher et installer des modules ;
</li>
<li>
<code>o conf urllist unshift file:///home/manu/toolbox/minicpan</code>, permet de
rajouter une URL au début de la liste des URL ; comme <code>cpan</code> utilise la
liste dans l'ordre, cela permet de donner la priorité à une ressource ;
</li>
<li>
<code>o conf urllist shift</code>, permet quant à elle de supprimer le premier élément
de la liste ;
</li>
<li>
je vous laisse jouer avec <code>o conf urllist pop</code> et <code>o conf urllist push   something</code> ! 
</li>
<li>
évidemment, ne pas oublier d'utiliser <code>o conf commit</code> pour sauver la
configuration pour le prochain appel à <code>cpan</code>, sauf si vous avez appelé la
commande <code>o conf auto_commit 1</code> auparavant. 

</li>
</ul>

<p>Avec le <a href="http://search.cpan.org/">CPAN</a> est une chose, mais il est assez agréable aussi de pouvoir
interroger le dépôt via une interface plus agréable que <code>locate</code>, <code>find</code> et
autre <code>grep</code>. C'est pour cela que j'ai installé <a href="http://search.cpan.org/dist/CPAN-Mini-Webserver/">CPAN::Mini::Webserver</a> qui rend
accessible mon minicpan via une interface web, je <a href="http://blog.bjornoya.be/lab_stacks/2009/11/un-outil-en-plus-cpanminiwebserver.html">je vous en avais déjà parlé précédemment</a>.
</p>]]>
    </content>
</entry>

<entry>
    <title>Visualisation d&apos;une requête CQL</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/2010/09/visualisation-dune-requete-cql.html" />
    <id>tag:blog.bjornoya.be,2010:/lab_stacks//1.38</id>

    <published>2010-09-09T21:45:12Z</published>
    <updated>2010-09-09T22:07:17Z</updated>

    <summary><![CDATA[Il y a quelques jours, j'avais expliqué que le sujet de ma gymnastique de
l'esprit portait sur l'interrogation d'une base de données SQL via une requête
de type moteur de recherche. Mes premiers pas portaient sur l'analyse de la
requête en vue de la création d'une structure de données représentant cette
requête. J'avais cité CQL::Parser comme exemple pour l'analyse syntaxique d'un
requête. Dans l'exercice du jour, je ne ferai que visualiser la structure de
données créée par CQL::Parser. Pour la visulisation d'un arbre, je ne connais
rien de mieux que Graphviz. Perl offre également une interface à ce
programme : GraphViz. 


Donc, voici le programme : 


#!/usr/bin/env perl

use strict;
use warnings;

use CQL::Parser;
use GraphViz;
use Term::ANSIColor;
use UNIVERSAL;

my $query    = shift;
my $filename = shift;

my $root;

eval { $root = CQL::Parser-&gt;new()-&gt;parse($query) };
if ($@) {
    die colored(['red'], &quot;We've got some problems: $@&quot;), $/;
}

my $gv = GraphViz-&gt;new( layout =&gt; 'dot', node =&gt; { shape =&gt; 'box' } );

traverse( $root, $gv );

open my $fh, '&gt;', $filename;
print $fh $gv-&gt;as_png();
close $fh;

sub traverse {
    my ( $node, $gv, $parent ) = @_;

    if ( $node-&gt;isa('CQL::BooleanNode') ) {
        $gv-&gt;add_node( $node, label =&gt; $node-&gt;op );
        traverse( $node-&gt;left(),  $gv, $node );
        traverse( $node-&gt;right(), $gv, $node );
        $gv-&gt;add_edge( $parent =&gt; $node ) if defined $parent;
    }
    elsif ( $node-&gt;isa('CQL::TermNode') ) {
        if ( defined $node-&gt;getQualifier ) {
            $gv-&gt;add_node( $node-&gt;getQualifier, label =&gt; $node-&gt;getQualifier );
            $gv-&gt;add_node( $node-&gt;getRelation,
                label =&gt; $node-&gt;getRelation()-&gt;getBase() );
            $gv-&gt;add_node( $node, label =&gt; $node-&gt;getTerm() );
            $gv-&gt;add_edge( $node-&gt;getQualifier =&gt; $node-&gt;getRelation );
            $gv-&gt;add_edge( $node-&gt;getQualifier =&gt; $node );
            $gv-&gt;add_edge( $parent =&gt; $node-&gt;getQualifier ) if defined $parent;
        }
        else {
            $gv-&gt;add_node( $node, label =&gt; $node-&gt;getTerm() );
            $gv-&gt;add_edge( $parent =&gt; $node ) if defined $parent;
        }
    }
    else {
        print colored['green'], ref($node), $/; # cela m'aide à découvrir ce qui n'est pas actuellement géré par la fonction traverse()
    }
}


Le but du programme était juste de me refamiliariser avec le parcours d'arbre,
et donc, il n'exploite probablement pas toutes les subtilités de l'analyse de
CQL::Parser.


Voici quelques exemples de requêtes et des arbres correspondants : 





Une requête représentant la requête « dc.title any fish »





Une requête représentant la requête « dc.title any fish or dc.creator any sanderson »





Une requête représentant la requête « dc.title any fish or (dc.creator any sanderson and dc.identifier = "id:1234567") »



Bon, ce n'est pas grand chose, mais cela m'a déjà permis de lire le code de
CQL::Parser et de réfléchir succinctement à la structure de données créée par
ce dernier. 
]]></summary>
    <author>
        <name>manu</name>
        
    </author>
    
        <category term="Français" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="cql" label="cql" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="graphviz" label="graphviz" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="parsing" label="parsing" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="perl" label="perl" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="query" label="query" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="fr" xml:base="http://blog.bjornoya.be/lab_stacks/">
        <![CDATA[<p><a href="http://blog.bjornoya.be/lab_stacks/2010/09/comprendre-la-question.html">Il y a quelques jours</a>, j'avais expliqué que le sujet de ma gymnastique de
l'esprit portait sur l'interrogation d'une base de données SQL via une requête
de type moteur de recherche. Mes premiers pas portaient sur l'analyse de la
requête en vue de la création d'une structure de données représentant cette
requête. J'avais cité <a href="http://search.cpan.org/dist/CQL-Parser/">CQL::Parser</a> comme exemple pour l'analyse syntaxique d'un
requête. Dans l'exercice du jour, je ne ferai que visualiser la structure de
données créée par <a href="http://search.cpan.org/dist/CQL-Parser/">CQL::Parser</a>. Pour la visulisation d'un arbre, je ne connais
rien de mieux que <a href="http://graphviz.org/">Graphviz</a>. Perl offre également une interface à ce
programme : <a href="http://search.cpan.org/dist/GraphViz/">GraphViz</a>. 
</p>
<p>
Donc, voici le programme : 
</p>
<pre class='brush: perl'>
#!/usr/bin/env perl

use strict;
use warnings;

use CQL::Parser;
use GraphViz;
use Term::ANSIColor;
use UNIVERSAL;

my $query    = shift;
my $filename = shift;

my $root;

eval { $root = CQL::Parser-&gt;new()-&gt;parse($query) };
if ($@) {
    die colored(['red'], &quot;We've got some problems: $@&quot;), $/;
}

my $gv = GraphViz-&gt;new( layout =&gt; 'dot', node =&gt; { shape =&gt; 'box' } );

traverse( $root, $gv );

open my $fh, '&gt;', $filename;
print $fh $gv-&gt;as_png();
close $fh;

sub traverse {
    my ( $node, $gv, $parent ) = @_;

    if ( $node-&gt;isa('CQL::BooleanNode') ) {
        $gv-&gt;add_node( $node, label =&gt; $node-&gt;op );
        traverse( $node-&gt;left(),  $gv, $node );
        traverse( $node-&gt;right(), $gv, $node );
        $gv-&gt;add_edge( $parent =&gt; $node ) if defined $parent;
    }
    elsif ( $node-&gt;isa('CQL::TermNode') ) {
        if ( defined $node-&gt;getQualifier ) {
            $gv-&gt;add_node( $node-&gt;getQualifier, label =&gt; $node-&gt;getQualifier );
            $gv-&gt;add_node( $node-&gt;getRelation,
                label =&gt; $node-&gt;getRelation()-&gt;getBase() );
            $gv-&gt;add_node( $node, label =&gt; $node-&gt;getTerm() );
            $gv-&gt;add_edge( $node-&gt;getQualifier =&gt; $node-&gt;getRelation );
            $gv-&gt;add_edge( $node-&gt;getQualifier =&gt; $node );
            $gv-&gt;add_edge( $parent =&gt; $node-&gt;getQualifier ) if defined $parent;
        }
        else {
            $gv-&gt;add_node( $node, label =&gt; $node-&gt;getTerm() );
            $gv-&gt;add_edge( $parent =&gt; $node ) if defined $parent;
        }
    }
    else {
        print colored['green'], ref($node), $/; # cela m'aide à découvrir ce qui n'est pas actuellement géré par la fonction traverse()
    }
}
</pre>
<p>
Le but du programme était juste de me refamiliariser avec le parcours d'arbre,
et donc, il n'exploite probablement pas toutes les subtilités de l'analyse de
<a href="http://search.cpan.org/dist/CQL-Parser/">CQL::Parser</a>.
</p>
<p>
Voici quelques exemples de requêtes et des arbres correspondants : 
</p>


<div class="figure">
<p><img src="http://farm5.static.flickr.com/4148/4974663507_95f52a0f85_m.jpg" alt="dc.title any fish" align="center" /></p>
<p>Une requête représentant la requête « dc.title any fish »</p>
</div>


<div class="figure">
<p><img src="http://farm5.static.flickr.com/4153/4975276340_a6974fd7fd.jpg" alt="dc.title any fish or dc.creator any sanderson" align="center" /></p>
<p>Une requête représentant la requête « dc.title any fish or dc.creator any sanderson »</p>
</div>


<div class="figure">
<p><img src="http://farm5.static.flickr.com/4087/4975276392_25994a9faf_z.jpg" alt="dc.title any fish or (dc.creator any sanderson and dc.identifier = "id:1234567")" align="center" /></p>
<p>Une requête représentant la requête « dc.title any fish or (dc.creator any sanderson and dc.identifier = "id:1234567") »</p>
</div>

<p>
Bon, ce n'est pas grand chose, mais cela m'a déjà permis de lire le code de
<a href="http://search.cpan.org/dist/CQL-Parser/">CQL::Parser</a> et de réfléchir succinctement à la structure de données créée par
ce dernier. 
</p>]]>
    </content>
</entry>

<entry>
    <title>Tisser des liens entre Instapaper et emacs</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/2010/09/tisser-des-liens-entre-instapaper-et-emacs.html" />
    <id>tag:blog.bjornoya.be,2010:/lab_stacks//1.37</id>

    <published>2010-09-08T21:44:58Z</published>
    <updated>2010-09-08T22:05:11Z</updated>

    <summary><![CDATA[J'ai découvert dernièrement un outil assez utile dans une veille,
Instapaper. Comme le dit sa tagline : « A simple tool to save web pages for reading later ». Comme beaucoup de ces outils web, il offre un bookmarlet
permettant d'intégrer son usage à notre navigateur web, mais quid lorsque l'on
n'utilise pas un navigateur ? Et bien, comme beaucoup de ces outils web (mais pas tous), une API est disponible pour interagir avec ce service web. 


Comme j'utilise régulièrement Twitter pour faire ma veille, et que j'utilise
twittering-mode pour parcourir mes tweets, j'ai donc besoin d'un outil
permettant de faire le lien entre emacs et Instapaper. Je suis encore
largement un débutant avec elisp, j'ai donc préféré faire le boulot avec
Perl. 


La première étape a donc consisté à créer un module :
WebService::Instapaper. La documentation officielle de l'API explique les
grandes lignes du fonctionnement du module, et le code est plutôt facile à
comprendre : 


use strict;
use warnings;

package WebService::Instapaper;

use LWP::UserAgent;
use Carp;
use URI;
use URI::QueryParam;

=head1 VERSION

=cut

# ABSTRACT: turns baubles into trinkets

our $VERSION = '0.01';

use constant INSTAPAPER_URI =&gt; 'https://www.instapaper.com/api/add';

sub new {
    my ( $class, %attr ) = @_;

    my $self = {};
    $self-&gt;{username} = $attr{username} || &quot;&quot;;
    $self-&gt;{password} = $attr{password} || &quot;&quot;;

    $self-&gt;{_ua} = LWP::UserAgent-&gt;new(
        agent   =&gt; 'WebService::Instapaper/0.1',
        cookie  =&gt; {},
        timeout =&gt; 30,
    );

    bless $self, $class;
}

sub username {
    my ( $self, $username ) = @_;

    $self-&gt;{username} = $username;
}

sub password {
    my ( $self, $password ) = @_;

    $self-&gt;{password} = $password;
}

sub credentials {
    my ( $self, $username, $password ) = @_;

    $self-&gt;username($username);
    $self-&gt;password($password);
}

sub add {
    my ( $self, %params ) = @_;

    croak &quot;No username!&quot; unless $self-&gt;{username} ne &quot;&quot;;
    croak &quot;No URI to read_later!&quot; unless exists $params{url};

    my $uri = URI-&gt;new(INSTAPAPER_URI);
    $uri-&gt;query_param( url =&gt; $params{url} );

    my $request = HTTP::Request-&gt;new( GET =&gt; $uri, );
    $request-&gt;authorization_basic( $self-&gt;{username}, $self-&gt;{password} );

    my $response = $self-&gt;{_ua}-&gt;request($request);

    $self-&gt;{_last_code}    = $response-&gt;code();
    $self-&gt;{_last_message} = $response-&gt;message();

    if ( $response-&gt;code() == 201 ) {
        return 1;
    }
    else {
        return 0;
    }
}

sub message {
    my ($self) = @_;

    return $self-&gt;{_last_message} if exists $self-&gt;{_last_message};
}

sub code {
    my ($self) = @_;

    return $self-&gt;{_last_code} if exists $self-&gt;{_last_code};
}

1;


Le module n'est pas encore disponible sur CPAN, mais le dépôt git est disponible via Github. 


Après avoir programmer ce module, j'ai donc écrit un script permettant
d'ajouter des liens à mon compte Instapaper. Voici le code : 


#!/usr/bin/env perl

use strict;
use warnings;

use WebService::Instapaper;

use Getopt::Long;
use Config::INI::Reader;
use File::Spec;

my $config = { config =&gt; File::Spec-&gt;catfile( $ENV{HOME}, '.instapaperrc' ), };

my $ini_config = Config::INI::Reader-&gt;read_file( $config-&gt;{config} );

my $reader = WebService::Instapaper-&gt;new(
    username =&gt; $ini_config-&gt;{Instapaper}-&gt;{username},
    password =&gt; $ini_config-&gt;{Instapaper}-&gt;{password},
);

GetOptions( $config, 'url=s', 'config=s' );

die &quot;Usage: $0 --url http://www.perl.org\n&quot; unless exists $config-&gt;{url};

if ( $reader-&gt;add( url =&gt; $config-&gt;{url} ) ) {
    print &quot;The url (&quot; . $config-&gt;{url} . &quot;) has been added successfully.&quot;;
}
else {
    print &quot;We received the following error: (&quot;
      . $reader-&gt;code . &quot;) &quot;
      . $reader-&gt;message;
}


Ce script, nommé read_later.pl, s'appuie sur un fichier de configuration pour
trouver les informations nécessaires pour se connecter à Instapaper et
utiliser le service. Ce script se trouve évidemment dans mon $PATH. 


La dernière partie de l'exercice consiste donc à écrire un peu d'elisp pour
dire emacs ce qu'il doit faire. Voici le code : 


(defun read-later (url &amp;optional new-window)
  &quot;Use a local script to add an url to the Instapaper website.&quot;
  (interactive (browse-url-interactive-arg &quot;Read Later: &quot;))
  (message (shell-command-to-string (concat &quot;read_later.pl &quot; &quot;--url &quot; url))))


Je peux donc maintenant faire M-x read-later, saisir le lien (et si mon
curseur se trouve sur un lien, browse-url-interactive-arg se charge de me
faciliter le travail), et appuyer sur Enter pour lancer l'ajout du lien. Il
ne reste plus qu'à choisir un raccourci clavier pour qu'à partir de
twittering-mode, je puisse lancer mon script : 


(define-key twittering-mode-map (kbd &quot;R&quot;) 'read-later)


Et voilà !!!
]]></summary>
    <author>
        <name>manu</name>
        
    </author>
    
        <category term="Français" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="elisp" label="elisp" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="emacs" label="emacs" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="instapaper" label="instapaper" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="lwp" label="lwp" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="perl" label="perl" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="webservices" label="web services" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="fr" xml:base="http://blog.bjornoya.be/lab_stacks/">
        <![CDATA[<p>J'ai découvert dernièrement un outil assez utile dans une veille,
<a href="http://www.instapaper.com/">Instapaper</a>. Comme le dit sa <i>tagline</i> : « <i>A simple tool to save web pages for reading later</i> ». Comme beaucoup de ces outils web, il offre un <a href="http://fr.wikipedia.org/wiki/Bookmarklet">bookmarlet</a>
permettant d'intégrer son usage à notre navigateur web, mais quid lorsque l'on
n'utilise pas un navigateur ? Et bien, comme beaucoup de ces outils web (<a href="http://www.pearltrees.com/">mais pas tous</a>), une <a href="http://fr.wikipedia.org/wiki/Interface_de_programmation">API</a> est disponible pour interagir avec ce service web. 
</p>
<p>
Comme j'utilise régulièrement <a href="http://www.twitter.com/">Twitter</a> pour faire ma veille, et que j'utilise
<a href="http://twmode.sourceforge.net/">twittering-mode</a> pour parcourir mes <i>tweets</i>, j'ai donc besoin d'un outil
permettant de faire le lien entre <a href="http://www.gnu.org/software/emacs/">emacs</a> et Instapaper. Je suis encore
largement un débutant avec <a href="http://www.gnu.org/software/emacs/manual/elisp.html">elisp</a>, j'ai donc préféré faire le boulot avec
<a href="http://www.perl.org/">Perl</a>. 
</p>
<p>
La première étape a donc consisté à créer un module :
<b>WebService::Instapaper</b>. La <a href="http://www.instapaper.com/api">documentation officielle de l'API</a> explique les
grandes lignes du fonctionnement du module, et le code est plutôt facile à
comprendre : 
</p>
<pre class='brush: perl'>
use strict;
use warnings;

package WebService::Instapaper;

use LWP::UserAgent;
use Carp;
use URI;
use URI::QueryParam;

=head1 VERSION

=cut

# ABSTRACT: turns baubles into trinkets

our $VERSION = '0.01';

use constant INSTAPAPER_URI =&gt; 'https://www.instapaper.com/api/add';

sub new {
    my ( $class, %attr ) = @_;

    my $self = {};
    $self-&gt;{username} = $attr{username} || &quot;&quot;;
    $self-&gt;{password} = $attr{password} || &quot;&quot;;

    $self-&gt;{_ua} = LWP::UserAgent-&gt;new(
        agent   =&gt; 'WebService::Instapaper/0.1',
        cookie  =&gt; {},
        timeout =&gt; 30,
    );

    bless $self, $class;
}

sub username {
    my ( $self, $username ) = @_;

    $self-&gt;{username} = $username;
}

sub password {
    my ( $self, $password ) = @_;

    $self-&gt;{password} = $password;
}

sub credentials {
    my ( $self, $username, $password ) = @_;

    $self-&gt;username($username);
    $self-&gt;password($password);
}

sub add {
    my ( $self, %params ) = @_;

    croak &quot;No username!&quot; unless $self-&gt;{username} ne &quot;&quot;;
    croak &quot;No URI to read_later!&quot; unless exists $params{url};

    my $uri = URI-&gt;new(INSTAPAPER_URI);
    $uri-&gt;query_param( url =&gt; $params{url} );

    my $request = HTTP::Request-&gt;new( GET =&gt; $uri, );
    $request-&gt;authorization_basic( $self-&gt;{username}, $self-&gt;{password} );

    my $response = $self-&gt;{_ua}-&gt;request($request);

    $self-&gt;{_last_code}    = $response-&gt;code();
    $self-&gt;{_last_message} = $response-&gt;message();

    if ( $response-&gt;code() == 201 ) {
        return 1;
    }
    else {
        return 0;
    }
}

sub message {
    my ($self) = @_;

    return $self-&gt;{_last_message} if exists $self-&gt;{_last_message};
}

sub code {
    my ($self) = @_;

    return $self-&gt;{_last_code} if exists $self-&gt;{_last_code};
}

1;
</pre>
<p>
Le module n'est pas encore disponible sur <a href="http://search.cpan.org/">CPAN</a>, mais <a href="http://github.com/edipretoro/WebService--Instapaper">le dépôt git est disponible via Github</a>. 
</p>
<p>
Après avoir programmer ce module, j'ai donc écrit un script permettant
d'ajouter des liens à mon compte Instapaper. Voici le code : 
</p>
<pre class='brush: perl'>
#!/usr/bin/env perl

use strict;
use warnings;

use WebService::Instapaper;

use Getopt::Long;
use Config::INI::Reader;
use File::Spec;

my $config = { config =&gt; File::Spec-&gt;catfile( $ENV{HOME}, '.instapaperrc' ), };

my $ini_config = Config::INI::Reader-&gt;read_file( $config-&gt;{config} );

my $reader = WebService::Instapaper-&gt;new(
    username =&gt; $ini_config-&gt;{Instapaper}-&gt;{username},
    password =&gt; $ini_config-&gt;{Instapaper}-&gt;{password},
);

GetOptions( $config, 'url=s', 'config=s' );

die &quot;Usage: $0 --url http://www.perl.org\n&quot; unless exists $config-&gt;{url};

if ( $reader-&gt;add( url =&gt; $config-&gt;{url} ) ) {
    print &quot;The url (&quot; . $config-&gt;{url} . &quot;) has been added successfully.&quot;;
}
else {
    print &quot;We received the following error: (&quot;
      . $reader-&gt;code . &quot;) &quot;
      . $reader-&gt;message;
}
</pre>
<p>
Ce script, nommé <code>read_later.pl</code>, s'appuie sur un fichier de configuration pour
trouver les informations nécessaires pour se connecter à Instapaper et
utiliser le service. Ce script se trouve évidemment dans mon <b>$PATH</b>. 
</p>
<p>
La dernière partie de l'exercice consiste donc à écrire un peu d'elisp pour
dire emacs ce qu'il doit faire. Voici le code : 
</p>
<pre class='brush: lisp'>
(defun read-later (url &amp;optional new-window)
  &quot;Use a local script to add an url to the Instapaper website.&quot;
  (interactive (browse-url-interactive-arg &quot;Read Later: &quot;))
  (message (shell-command-to-string (concat &quot;read_later.pl &quot; &quot;--url &quot; url))))
</pre>
<p>
Je peux donc maintenant faire <b>M-x read-later</b>, saisir le lien (et si mon
curseur se trouve sur un lien, <code>browse-url-interactive-arg</code> se charge de me
faciliter le travail), et appuyer sur <b>Enter</b> pour lancer l'ajout du lien. Il
ne reste plus qu'à choisir un raccourci clavier pour qu'à partir de
twittering-mode, je puisse lancer mon script : 
</p>
<pre class='brush: lisp'>
(define-key twittering-mode-map (kbd &quot;R&quot;) 'read-later)
</pre>
<p>
Et voilà !!!
</p>]]>
    </content>
</entry>

<entry>
    <title>Analyse naïve de traffic réseau</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/2010/09/analyse-naive-de-traffic-reseau.html" />
    <id>tag:blog.bjornoya.be,2010:/lab_stacks//1.36</id>

    <published>2010-09-07T21:19:31Z</published>
    <updated>2010-09-07T21:22:31Z</updated>

    <summary><![CDATA[Il y a quelques mois, j'ai eu l'occasion de programmer un outil permettant de
scruter un réseau, et de donner les adresses IP les plus actives en terme de
consommation de la bande passante (en fait, il donne toutes les adresses IP
scrutées et pour chaque adresse IP, le nombre de bytes lié à cette
adresse). C'est donc relativement naïf comme outil, et cela n'avait pas
d'autres buts que de me donner une idée générale de ce qui se passait sur le
réseau. 


Voici le script : 


#!/usr/bin/env perl

use strict;
use warnings;

use Net::Pcap;
use NetPacket::Ethernet qw( :ALL );
use NetPacket::IP;
use DB_File;

my ( $err, $netaddr, $netmask );
my $dev = shift || 'eth0';
my $stat_filename = shift || './stats.db';

tie my %stats, 'DB_File', $stat_filename, O_RDWR|O_CREAT, 0666, $DB_HASH;
$SIG{INT} = \&clean;

die &quot;You need to be root to run this script...\n&quot; if $&gt; != 0;

Net::Pcap::lookupnet( $dev, \$netaddr, \$netmask, \$err) and die &quot;lookupnet $dev failed ($!)\n&quot;;
my $object = Net::Pcap::open_live( $dev, 1024, 1, 0, \$err );

Net::Pcap::loop( $object, -1, \&amp;callback, [ $netaddr, $netmask ] );

sub callback {
    my ( $user_data, $header, $raw_packet ) = @_;
    my ( $netaddr, $netmask ) = @{ $user_data };
    
    my $packet = NetPacket::Ethernet-&gt;decode( $raw_packet );
    if ($packet-&gt;{type} == ETH_TYPE_IP) {
        my $ip = NetPacket::IP-&gt;decode( eth_strip($raw_packet) );
        $stats{ $ip-&gt;{src_ip} } = 0 if not exists $stats{ $ip-&gt;{src_ip} };
        $stats{ $ip-&gt;{dest_ip} } = 0 if not exists $stats{ $ip-&gt;{dest_ip} };
        
        $stats{ $ip-&gt;{src_ip} } += $ip-&gt;{len};
        $stats{ $ip-&gt;{dest_ip} } += $ip-&gt;{len};
    }
}

sub clean {
    my ( $signal ) = @_;
    
    foreach my $key (sort { $stats{$a} &lt;=&gt; $stats{$b} } keys %stats) {
        print $key, ': ', $stats{$key}, $/;
    }

    untie %stats;
    Net::Pcap::close( $object );
}


Alors, ce script emploie le librairie pcap via Net::Pcap pour analyser ce qui
se passe sur le réseau, et NetPacket pour décoder les paquets
intéressants. J'utilise DB_File pour lier un hash à un fichier (ce qui me
permet de stocker l'analyse et de pouvoir réutiliser les résultats
ultérieurement). Et finalement, je capture le signal INT de manière à
afficher le résultat de l'analyse, et de quitter proprement le programme. 


Bref, rien d'exceptionnel, mais toujours utile. 
]]></summary>
    <author>
        <name>manu</name>
        
    </author>
    
        <category term="Français" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="network" label="network" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="pcap" label="pcap" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="perl" label="perl" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="fr" xml:base="http://blog.bjornoya.be/lab_stacks/">
        <![CDATA[<p>Il y a quelques mois, j'ai eu l'occasion de programmer un outil permettant de
scruter un réseau, et de donner les adresses IP les plus actives en terme de
consommation de la bande passante (en fait, il donne toutes les adresses IP
scrutées et pour chaque adresse IP, le nombre de <i>bytes</i> lié à cette
adresse). C'est donc relativement naïf comme outil, et cela n'avait pas
d'autres buts que de me donner une idée générale de ce qui se passait sur le
réseau. 
</p>
<p>
Voici le script : 
</p>
<pre class='brush: perl'>
#!/usr/bin/env perl

use strict;
use warnings;

use Net::Pcap;
use NetPacket::Ethernet qw( :ALL );
use NetPacket::IP;
use DB_File;

my ( $err, $netaddr, $netmask );
my $dev = shift || 'eth0';
my $stat_filename = shift || './stats.db';

tie my %stats, 'DB_File', $stat_filename, O_RDWR|O_CREAT, 0666, $DB_HASH;
$SIG{INT} = \&clean;

die &quot;You need to be root to run this script...\n&quot; if $&gt; != 0;

Net::Pcap::lookupnet( $dev, \$netaddr, \$netmask, \$err) and die &quot;lookupnet $dev failed ($!)\n&quot;;
my $object = Net::Pcap::open_live( $dev, 1024, 1, 0, \$err );

Net::Pcap::loop( $object, -1, \&amp;callback, [ $netaddr, $netmask ] );

sub callback {
    my ( $user_data, $header, $raw_packet ) = @_;
    my ( $netaddr, $netmask ) = @{ $user_data };
    
    my $packet = NetPacket::Ethernet-&gt;decode( $raw_packet );
    if ($packet-&gt;{type} == ETH_TYPE_IP) {
        my $ip = NetPacket::IP-&gt;decode( eth_strip($raw_packet) );
        $stats{ $ip-&gt;{src_ip} } = 0 if not exists $stats{ $ip-&gt;{src_ip} };
        $stats{ $ip-&gt;{dest_ip} } = 0 if not exists $stats{ $ip-&gt;{dest_ip} };
        
        $stats{ $ip-&gt;{src_ip} } += $ip-&gt;{len};
        $stats{ $ip-&gt;{dest_ip} } += $ip-&gt;{len};
    }
}

sub clean {
    my ( $signal ) = @_;
    
    foreach my $key (sort { $stats{$a} &lt;=&gt; $stats{$b} } keys %stats) {
        print $key, ': ', $stats{$key}, $/;
    }

    untie %stats;
    Net::Pcap::close( $object );
}
</pre>
<p>
Alors, ce script emploie le <a href="http://www.tcpdump.org/">librairie pcap</a> via <a href="http://search.cpan.org/dist/Net-Pcap/">Net::Pcap</a> pour analyser ce qui
se passe sur le réseau, et <a href="http://search.cpan.org/dist/NetPacket/">NetPacket</a> pour décoder les paquets
intéressants. J'utilise <a href="http://search.cpan.org/dist/DB_File/">DB_File</a> pour lier un <i>hash</i> à un fichier (ce qui me
permet de stocker l'analyse et de pouvoir réutiliser les résultats
ultérieurement). Et finalement, je capture le signal <b>INT</b> de manière à
afficher le résultat de l'analyse, et de quitter proprement le programme. 
</p>
<p>
Bref, rien d'exceptionnel, mais toujours utile. 
</p>]]>
    </content>
</entry>

<entry>
    <title>Clipboard &amp; Linux, gestion de plusieurs presse-papier</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/2010/09/clipboard-linux-gestion-de-plusieurs-presse-papier.html" />
    <id>tag:blog.bjornoya.be,2010:/lab_stacks//1.35</id>

    <published>2010-09-06T21:59:00Z</published>
    <updated>2010-09-06T22:19:19Z</updated>

    <summary><![CDATA[Hier, je vous ai parlé de Clipboard, un module permettant d'accéder au
presse-papier de votre système d'exploitation. Aujourd'hui, je vais compléter
ces informations avec quelques subtilités concernant l'utilisation de
Clipboard sous Linux. 


Alors que Clipboard utilise les outils natifs sous Windows et Mac OS X, il
s'appuie sur un petit utilitaire sous Linux. Cet utilitaire, xclip, permet de
manipuler le contenu des presse-papier sous X11, et oui, vous avez bien lu,
j'ai bien écrit le contenu des presse-papier. Car sous X11, nous en avons
plusieurs à disposition, et ceci ne va pas sans poser quelques problèmes à
l'occasion. 


Dans la page de documentation d'xclip, on nous explique qu'il y a trois
presse-papier disponibles :


primary, utilisé par défaut, donc, quand vous sélectionnez du texte sous
X11, il est automatiquement copié dans ce presse-papier, et vous pouvez y
accéder via un clic du troisième bouton de votre souris (et si votre souris
n'a pas de troisième bouton, vous pouvez l'émuler ; par exemple, par le
clic simultané du clic gauche et du clic droit) ;


secondary, ce qui est cohérent avec l'existence d'un primary ;


clipboard, qui est employé par votre window manager, et donc, par
exemple sous Gnome, ce qui est copié via Ctrl-c et collé via Ctrl-v. 




Dans un des outils développés accédant au presse-papier pour trouver les
informations à traiter, je préférais utiliser le clipboard plutôt que le
primary (j'utilise ce script quotidiennement, et sur des machines n'ayant
pas toujours une souris externe, et la combinaison clic gauche/clic droit est
parfois irritante sur certains trackpads). J'ai donc été lire le module et
j'ai constaté qu'une gestion des différents presse-papier avait été mise en
place :


une méthode paste_from_selection() permettant de prendre l'information à
partir d'un des presse-papier à disposition ;


une méthode copy_to_selection() permettant de copier de l'information vers
un presse-papier particulier.




Je pouvais donc travailler de la manière suivante : 


  #!/usr/bin/env perl
  
  use strict;
  use warnings;
  
  use Clipboard;
  
  my $text = Clipboard::Xclip-&gt;paste_from_selection('clipboard'); # pour récupérer le texte du presse-papier
  
  Clipboard::Xclip-&gt;copy_to_selection('clipboard', 'Balh blah blah'); # pour coller du texte dans le presse-papier


Malheureusement, j'utilise ces scripts également avec Windows et Mac OS X, il
était donc hors de question d'avoir des appels directs à
paste_from_selection() ou copy_to_selection(). Il me restait donc la
solution de tester $^O pour connaître mon environnement d'exécution et
adapter le comportement de mon programme en fonction, ou alors utiliser le
hack suivant. 


Dans Clipboard::Xclip, une autre méthode est définie pour définir le
presse-papier favori, cette méthode s'appelle favorite_selection(), dont
voici la définition : 


  sub favorite_selection { ($self-&gt;all_selections)[0] } # retourne le premier presse-papier disponible, donc primary
  
  sub all_selections { qw(primary buffer clipboard secondary) } # liste les presse-papier disponibles


Il me suffit donc de redéfinir la fonction favorite_selection() pour lui
faire sélectionner mon presse-papier favori. Voici donc le code :


  undef &amp;Clipboard::Xclip::favorite_selection; # on « supprime » la définition de la méthode favorite_selection
  *Clipboard::Xclip::favorite_selection = sub { 'clipboard' }; # on redéfinit l'action de la méthode


Et maintenant, je peux continuer à utiliser Clipboard-&gt;paste() et
Clipboard-&gt;copy() sans me soucier du reste !
]]></summary>
    <author>
        <name>manu</name>
        
    </author>
    
        <category term="Français" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="clipboard" label="clipboard" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="linux" label="linux" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="perl" label="perl" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="fr" xml:base="http://blog.bjornoya.be/lab_stacks/">
        <![CDATA[<p><a href="http://blog.bjornoya.be/lab_stacks/2010/09/prendre-les-informations-la-ou-elles-sont.html">Hier, je vous ai parlé de Clipboard</a>, un module permettant d'accéder au
presse-papier de votre système d'exploitation. Aujourd'hui, je vais compléter
ces informations avec quelques subtilités concernant l'utilisation de
<a href="http://search.cpan.org/dist/Clipboard/">Clipboard</a> sous Linux. 
</p>
<p>
Alors que <a href="http://search.cpan.org/dist/Clipboard/">Clipboard</a> utilise les outils natifs sous Windows et Mac OS X, il
s'appuie sur un petit utilitaire sous Linux. Cet utilitaire, <a href="http://sourceforge.net/projects/xclip/">xclip</a>, permet de
manipuler le contenu des presse-papier sous X11, et oui, vous avez bien lu,
j'ai bien écrit le contenu <b>des</b> presse-papier. Car sous X11, nous en avons
plusieurs à disposition, et ceci ne va pas sans poser quelques problèmes à
l'occasion. 
</p>
<p>
Dans la page de documentation d'xclip, on nous explique qu'il y a trois
presse-papier disponibles :
</p><ol>
<li>
<i>primary</i>, utilisé par défaut, donc, quand vous sélectionnez du texte sous
X11, il est automatiquement copié dans ce presse-papier, et vous pouvez y
accéder via un clic du troisième bouton de votre souris (et si votre souris
n'a pas de troisième bouton, vous pouvez l'émuler ; par exemple, par le
clic simultané du clic gauche et du clic droit) ;
</li>
<li>
<i>secondary</i>, ce qui est cohérent avec l'existence d'un <i>primary</i> ;
</li>
<li>
<i>clipboard</i>, qui est employé par votre <i>window manager</i>, et donc, par
exemple sous <a href="http://www.gnome.org/">Gnome</a>, ce qui est copié via <i>Ctrl-c</i> et collé via <i>Ctrl-v</i>. 

</li>
</ol>

<p>Dans un des outils développés accédant au presse-papier pour trouver les
informations à traiter, je préférais utiliser le <i>clipboard</i> plutôt que le
<i>primary</i> (j'utilise ce script quotidiennement, et sur des machines n'ayant
pas toujours une souris externe, et la combinaison clic gauche/clic droit est
parfois irritante sur certains <i>trackpads</i>). J'ai donc été lire le module et
j'ai constaté qu'une gestion des différents presse-papier avait été mise en
place :
</p><ul>
<li>
une méthode <code>paste_from_selection()</code> permettant de prendre l'information à
partir d'un des presse-papier à disposition ;
</li>
<li>
une méthode <code>copy_to_selection()</code> permettant de copier de l'information vers
un presse-papier particulier.

</li>
</ul>

<p>Je pouvais donc travailler de la manière suivante : 
</p>
<pre class='brush: perl'>
  #!/usr/bin/env perl
  
  use strict;
  use warnings;
  
  use Clipboard;
  
  my $text = Clipboard::Xclip-&gt;paste_from_selection('clipboard'); # pour récupérer le texte du presse-papier
  
  Clipboard::Xclip-&gt;copy_to_selection('clipboard', 'Balh blah blah'); # pour coller du texte dans le presse-papier
</pre>
<p>
Malheureusement, j'utilise ces scripts également avec Windows et Mac OS X, il
était donc hors de question d'avoir des appels directs à
<code>paste_from_selection()</code> ou <code>copy_to_selection()</code>. Il me restait donc la
solution de tester <code>$^O</code> pour connaître mon environnement d'exécution et
adapter le comportement de mon programme en fonction, ou alors utiliser le
<i>hack</i> suivant. 
</p>
<p>
Dans <a href="http://cpansearch.perl.org/src/KING/Clipboard-0.09/lib/Clipboard/Xclip.pm">Clipboard::Xclip</a>, une autre méthode est définie pour définir le
presse-papier favori, cette méthode s'appelle <code>favorite_selection()</code>, dont
voici la définition : 
</p>
<pre class='brush: perl'>
  sub favorite_selection { ($self-&gt;all_selections)[0] } # retourne le premier presse-papier disponible, donc primary
  
  sub all_selections { qw(primary buffer clipboard secondary) } # liste les presse-papier disponibles
</pre>
<p>
Il me suffit donc de redéfinir la fonction <code>favorite_selection()</code> pour lui
faire sélectionner mon presse-papier favori. Voici donc le code :
</p>
<pre class='brush: perl'>
  undef &amp;Clipboard::Xclip::favorite_selection; # on « supprime » la définition de la méthode favorite_selection
  *Clipboard::Xclip::favorite_selection = sub { 'clipboard' }; # on redéfinit l'action de la méthode
</pre>
<p>
Et maintenant, je peux continuer à utiliser <code>Clipboard-&gt;paste()</code> et
<code>Clipboard-&gt;copy()</code> sans me soucier du reste !
</p>]]>
    </content>
</entry>

<entry>
    <title>Prendre les informations là où elles sont</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/2010/09/prendre-les-informations-la-ou-elles-sont.html" />
    <id>tag:blog.bjornoya.be,2010:/lab_stacks//1.34</id>

    <published>2010-09-05T11:00:00Z</published>
    <updated>2010-09-05T10:09:41Z</updated>

    <summary><![CDATA[Si certains ont défini les programmes informatiques comme étant l'addition
d'un algorithme et d'une structure de données, force est de constater que les
programmes ne sont pas fort utiles si nous ne pouvons pas leur donner des
informations (via des entrées) et s'ils ne peuvent pas nous rendre les
résultats des traitements effectués (via les sorties). 


Comme la majorité des programmes que j'écris en Perl rentre dans la catégorie
des outils censés me rendre la vie plus facile, cette notion d'entrées-sorties
prend une importance particulière. Ainsi, j'ai un outil qui transforme un ISBN
en une référence bibliographique au format BibTeX (en fait, j'ai plusieurs
déclinaisons de cet outil, mais passons !), je dois donc transmettre l'ISBN à
mon programme et je dois récupérer la notice au format BibTeX, l'idéal est
donc de travailler sur base du presse-papier, je copie l'ISBN, je lance le
programme, et, finalement, je peux coller ma notice BibTeX là où bon me
semble. 


Pour accéder au presse-papier via Perl, nous avons un module nommé Clipboard,
ce module est rigoureusement simple dans son utilisation, et terriblement
utile puisqu'il est multi-plateforme (enfin, il permet d'utiliser de manière
transparente de presse-papier sur Windows, Mac OS X et Linux).


Voici un exemple d'utilisation :


#!/usr/bin/env perl

use strict;
use warnings;

use Clipboard;

print Clipboard-&gt;paste; # nous accédons à ce qui a été copié dans le presse-papier

Clipboard-&gt;copy( scalar localtime ); # nous copions la date du jour dans le presse-papier


Rien de sorcier donc ! Et indispensable pour intégrer mes programmes
facilement dans mon informatique quotidienne. 
 ]]></summary>
    <author>
        <name>manu</name>
        
    </author>
    
        <category term="Français" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="clipboard" label="clipboard" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="cpan" label="cpan" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="perl" label="perl" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="fr" xml:base="http://blog.bjornoya.be/lab_stacks/">
        <![CDATA[<p>Si <a href="http://fr.wikipedia.org/wiki/Niklaus_Wirth">certains</a> ont <a href="http://www.cs.inf.ethz.ch/~wirth/books/AlgorithmE0/">défini</a> les programmes informatiques comme étant l'addition
d'un <a href="http://fr.wikipedia.org/wiki/Algorithme">algorithme</a> et d'une <a href="http://fr.wikipedia.org/wiki/Structure_de_donn%C3%A9es">structure de données</a>, force est de constater que les
programmes ne sont pas fort utiles si nous ne pouvons pas leur donner des
informations (via des entrées) et s'ils ne peuvent pas nous rendre les
résultats des traitements effectués (via les sorties). 
</p>
<p>
Comme la majorité des programmes que j'écris en Perl rentre dans la catégorie
des outils censés me rendre la vie plus facile, cette notion d'entrées-sorties
prend une importance particulière. Ainsi, j'ai un outil qui transforme un ISBN
en une référence bibliographique au format <a href="http://www.bibtex.org/">BibTeX</a> (en fait, j'ai plusieurs
déclinaisons de cet outil, mais passons !), je dois donc transmettre l'ISBN à
mon programme et je dois récupérer la notice au format BibTeX, l'idéal est
donc de travailler sur base du <a href="http://fr.wikipedia.org/wiki/Presse-papier_(informatique)">presse-papier</a>, je copie l'ISBN, je lance le
programme, et, finalement, je peux coller ma notice BibTeX là où bon me
semble. 
</p>
<p>
Pour accéder au <a href="http://fr.wikipedia.org/wiki/Presse-papier_(informatique)">presse-papier</a> via Perl, nous avons un module nommé <a href="http://search.cpan.org/dist/Clipboard/">Clipboard</a>,
ce module est rigoureusement simple dans son utilisation, et terriblement
utile puisqu'il est multi-plateforme (enfin, il permet d'utiliser de manière
transparente de <a href="http://fr.wikipedia.org/wiki/Presse-papier_(informatique)">presse-papier</a> sur Windows, Mac OS X et Linux).
</p>
<p>
Voici un exemple d'utilisation :
</p>
<pre class='brush: perl'>
#!/usr/bin/env perl

use strict;
use warnings;

use Clipboard;

print Clipboard-&gt;paste; # nous accédons à ce qui a été copié dans le presse-papier

Clipboard-&gt;copy( scalar localtime ); # nous copions la date du jour dans le presse-papier
</pre>
<p>
Rien de sorcier donc ! Et indispensable pour intégrer mes programmes
facilement dans mon informatique quotidienne. 
</p> ]]>
    </content>
</entry>

<entry>
    <title>Comprendre la question</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/2010/09/comprendre-la-question.html" />
    <id>tag:blog.bjornoya.be,2010:/lab_stacks//1.33</id>

    <published>2010-09-04T15:13:18Z</published>
    <updated>2010-09-04T19:16:40Z</updated>

    <summary><![CDATA[Régulièrement, je me soumet à une forme de gymnastique de l'esprit. Cette
gymnastique consiste à trouver un thème qui m'intéresse, et de le travailler
brièvement. Si le sujet est informatique, il s'agira pour moi de programmer
une implémentation naïve ; sinon, de rédiger une courte synthèse. L'objectif
de cette gymnastique étant de m'obliger à manipuler des notions, qu'elles
soient connues ou non, pour mieux les intégrer et les comprendre.


Hier, cette gymnastique a commencé par une réflexion sur l'analyse de requêtes
telles que celles soumises à un moteur de recherche et la soumission de ces
dernières à un moteur SQL. Bon, le sujet étant assez vaste, je me suis d'abord
intéressé à l'analyse syntaxique d'une requête. J'ai été tout naturellement
sur le CPAN pour voir les modules existants, et ainsi lire les implémentations
actuelles. Je me suis penché plus particulièrement sur deux modules :


Search::QueryParser écrit par Laurent Dami, et 


CQL::Parser écrit par Brian Cassidy. 




Ces deux modules analysent tout les deux des requêtes, mais la forme des
requêtes est différentes, le premier analyse des requêtes « à la Google » et
le second est spécialisé dans les requêtes CQL (CQL signifiant Common Query Language). Les détails d'implémentation sont également différents : 


Search::QueryParser n'utilise pas de tokenizer externe et travaille donc
de la manière suivante : une boucle dont le paramètre est la requête, et des
appels successifs à s/// permet de raccourcir cette requête jusqu'à son
analyse complète. J'aime bien cette approche pour construire rapidement un
analyseur syntaxique de requêtes simples. Par contre, quand le langage de
requête devient plus complexe, je crains que cela ne devienne un peu
laborieux à maintenir ; 


CQL::Parser quant à lui s'appuie sur un tokenizer externe :
String::Tokenizer. Ce dernier traduit une chaîne de caractères en un
tableau, il « suffira » de traduire ce tableau de manière à interroger le
système d'information de notre choix (SGBD, moteur de recherche, etc) ; j'ai
lu rapidement la documentation de String::Tokenizer, et c'est définitivement
un module que je rajouterai dans mon Top 100. 




Du côté de l'implémentation naïve, j'ai essentiellement joué avec la méthode
adoptée par Search::QueryParser. Je me suis contenté de créer un arbre d'une
expression arithmétique. Et c'est tellement naïf que je n'ai même pas valider
l'expression arithmétique. Au niveau de la création de l'arbre, j'ai utilisé
un autre module de mon Top 100, à savoir Tree::DAG_Node. Voici le code
produit :



#!/usr/bin/env perl

use strict;
use warnings;
use diagnostics;

use Tree::DAG_Node;

my $query = shift || '+ 1 2';
my $root;
my $lastnode;

while ($query) {
    if ($query =~ s/^(\+)\s*(\w*)/$2/) {
        if (not defined $root) {
            $root = Tree::DAG_Node-&gt;new();
            $root-&gt;name( $1 );
            $lastnode = $root;
        } else {
            my $node = Tree::DAG_Node-&gt;new();
            $node-&gt;name( $1 );
            $lastnode-&gt;add_daughter( $node );
            $lastnode = $node;
        }
    } elsif ($query =~ s/^(\d+)\s*(\w*)/$2/) {
        my $daughter = $lastnode-&gt;new_daughter;
        $daughter-&gt;name( $1 );
    }
}


my $diagram = $root-&gt;draw_ascii_tree;
print map &quot;$_\n&quot;, @$diagram;



Il reste encore du chemin avant de traduire un 



dc.title any rose and dc.creator = eco




en 



SELECT * FROM books WHERE books.title LIKE '%rose%' and author LIKE '%eco%'





Mais chaque chose en son temps, et le CPAN est plein de modules qui me
serviront de boussole pour terminer cette promenade.
]]></summary>
    <author>
        <name>manu</name>
        
    </author>
    
        <category term="Français" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="cpan" label="cpan" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="cql" label="CQL" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="parsing" label="parsing" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="perl" label="perl" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="query" label="query" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="fr" xml:base="http://blog.bjornoya.be/lab_stacks/">
        <![CDATA[<p>Régulièrement, je me soumet à une forme de gymnastique de l'esprit. Cette
gymnastique consiste à trouver un thème qui m'intéresse, et de le travailler
brièvement. Si le sujet est informatique, il s'agira pour moi de programmer
une implémentation naïve ; sinon, de rédiger une courte synthèse. L'objectif
de cette gymnastique étant de m'obliger à manipuler des notions, qu'elles
soient connues ou non, pour mieux les intégrer et les comprendre.
</p>
<p>
Hier, cette gymnastique a commencé par une réflexion sur l'analyse de requêtes
telles que celles soumises à un moteur de recherche et la soumission de ces
dernières à un moteur <a href="http://fr.wikipedia.org/wiki/SQL">SQL</a>. Bon, le sujet étant assez vaste, je me suis d'abord
intéressé à <a href="http://fr.wikipedia.org/wiki/Analyse_syntaxique">l'analyse syntaxique</a> d'une requête. J'ai été tout naturellement
sur le <a href="http://search.cpan.org">CPAN</a> pour voir les modules existants, et ainsi lire les implémentations
actuelles. Je me suis penché plus particulièrement sur deux modules :
</p><ol>
<li>
<a href="http://search.cpan.org/dist/Search-QueryParser/">Search::QueryParser</a> écrit par <a href="http://search.cpan.org/~dami/">Laurent Dami</a>, et 
</li>
<li>
<a href="http://search.cpan.org/dist/CQL-Parser/">CQL::Parser</a> écrit par <a href="http://search.cpan.org/~bricas/">Brian Cassidy</a>. 

</li>
</ol>

<p>Ces deux modules analysent tout les deux des requêtes, mais la forme des
requêtes est différentes, le premier analyse des requêtes « à la Google » et
le second est spécialisé dans les requêtes <a href="http://www.loc.gov/standards/sru/specs/cql.html">CQL</a> (CQL signifiant <i>Common Query Language</i>). Les détails d'implémentation sont également différents : 
</p><ul>
<li>
<a href="http://search.cpan.org/dist/Search-QueryParser/">Search::QueryParser</a> n'utilise pas de <i>tokenizer</i> externe et travaille donc
de la manière suivante : une boucle dont le paramètre est la requête, et des
appels successifs à <code>s///</code> permet de raccourcir cette requête jusqu'à son
analyse complète. J'aime bien cette approche pour construire rapidement un
analyseur syntaxique de requêtes simples. Par contre, quand le langage de
requête devient plus complexe, je crains que cela ne devienne un peu
laborieux à maintenir ; 
</li>
<li>
<a href="http://search.cpan.org/dist/CQL-Parser/">CQL::Parser</a> quant à lui s'appuie sur un <i>tokenizer</i> externe :
<a href="http://search.cpan.org/dist/String-Tokenizer/">String::Tokenizer</a>. Ce dernier traduit une chaîne de caractères en un
tableau, il « suffira » de traduire ce tableau de manière à interroger le
système d'information de notre choix (<a href="http://fr.wikipedia.org/wiki/SGBD">SGBD</a>, <a href="http://search.cpan.org/dist/KinoSearch/">moteur de recherche</a>, etc) ; j'ai
lu rapidement la documentation de <a href="http://search.cpan.org/dist/String-Tokenizer/">String::Tokenizer</a>, et c'est définitivement
un module que je rajouterai dans mon <b>Top 100</b>. 

</li>
</ul>

<p>Du côté de l'implémentation naïve, j'ai essentiellement joué avec la méthode
adoptée par <a href="http://search.cpan.org/dist/Search-QueryParser/">Search::QueryParser</a>. Je me suis contenté de créer un arbre d'une
expression arithmétique. Et c'est tellement naïf que je n'ai même pas valider
l'expression arithmétique. Au niveau de la création de l'arbre, j'ai utilisé
un autre module de mon <b>Top 100</b>, à savoir <a href="http://search.cpan.org/dist/Tree::DAG_Node/">Tree::DAG_Node</a>. Voici le code
produit :
</p>

<pre class='brush: perl'>
#!/usr/bin/env perl

use strict;
use warnings;
use diagnostics;

use Tree::DAG_Node;

my $query = shift || '+ 1 2';
my $root;
my $lastnode;

while ($query) {
    if ($query =~ s/^(\+)\s*(\w*)/$2/) {
        if (not defined $root) {
            $root = Tree::DAG_Node-&gt;new();
            $root-&gt;name( $1 );
            $lastnode = $root;
        } else {
            my $node = Tree::DAG_Node-&gt;new();
            $node-&gt;name( $1 );
            $lastnode-&gt;add_daughter( $node );
            $lastnode = $node;
        }
    } elsif ($query =~ s/^(\d+)\s*(\w*)/$2/) {
        my $daughter = $lastnode-&gt;new_daughter;
        $daughter-&gt;name( $1 );
    }
}


my $diagram = $root-&gt;draw_ascii_tree;
print map &quot;$_\n&quot;, @$diagram;
</pre>

<p>
Il reste encore du chemin avant de traduire un 
</p>


<pre class="example">dc.title any rose and dc.creator = eco
</pre>


<p>
en 
</p>


<pre class="example">SELECT * FROM books WHERE books.title LIKE '%rose%' and author LIKE '%eco%'
</pre>



<p>
Mais chaque chose en son temps, et le <a href="http://search.cpan.org/">CPAN</a> est plein de modules qui me
serviront de boussole pour terminer cette promenade.
</p>]]>
    </content>
</entry>

<entry>
    <title>Suis-je connecté ou pas ?</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/2010/08/suis-je-connecte-ou-pas.html" />
    <id>tag:blog.bjornoya.be,2010:/lab_stacks//1.32</id>

    <published>2010-08-07T16:56:07Z</published>
    <updated>2010-08-07T16:59:49Z</updated>

    <summary>Après une absence assez longue (la fin de l&apos;année scolaire, les vacances, mais
aussi ma thèse m&apos;ont tenu éloigné de ce blog), je vais reprendre tout
doucement l&apos;écriture d&apos;articles pour ce dernier.


Jusqu&apos;à présent, ce blog a essentiellement été consacré à Perl, mais la
rentrée scolaire approchant me donne envie d&apos;étendre les sujets abordés aux
cours que je donne, cela restera donc largement des thèmes liés à
l&apos;informatique (et plus généralement de l&apos;informatique documentaire), mais
tout en étant plus orienté sciences de l&apos;information. 


Pour la reprise de ce blog, je resterai dans les habitudes du passé, à savoir
un petit billet sur Perl. Aujourd&apos;hui, je parlerai d&apos;un module qui est utile
quand l&apos;on souhaite effectuer un traitement lié au web. Il s&apos;agit de
LWP::Online. Ce module très simple permet de répondre à la question « Suis-je
actuellement connecté au web ? ». 


L&apos;utilisation est tout aussi simple que l&apos;implémentation du module puisqu&apos;il
s&apos;agit d&apos;importer une fonction, online(), qui retourne une valeur
booléenne. 


Un script simpliste ressemblerait donc à ceci :




#!/usr/bin/env perl

use strict;
use warnings;

use LWP::Online qw( online ); # on importe la fonction online() dans notre script

if ( online() ) {
    print &quot;Nous sommes connect&#233;s !\n&quot;;
}
else {
    print &quot;Nous ne sommes pas connect&#233;s !\n&quot;;
}





Le module offre également une fonction offline() qui retourne la valeur
vrai quand nous ne sommes pas connecté au Web. 
</summary>
    <author>
        <name>manu</name>
        
    </author>
    
        <category term="Français" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="cpan" label="cpan" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="lwp" label="lwp" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="perl" label="perl" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="fr" xml:base="http://blog.bjornoya.be/lab_stacks/">
        <![CDATA[<p>Après une absence assez longue (la fin de l'année scolaire, les vacances, mais
aussi ma thèse m'ont tenu éloigné de ce blog), je vais reprendre tout
doucement l'écriture d'articles pour ce dernier.
</p>
<p>
Jusqu'à présent, ce blog a essentiellement été consacré à <a href="http://www.perl.org/">Perl</a>, mais la
rentrée scolaire approchant me donne envie d'étendre les sujets abordés aux
cours que je donne, cela restera donc largement des thèmes liés à
l'informatique (et plus généralement de l'informatique documentaire), mais
tout en étant plus orienté sciences de l'information. 
</p>
<p>
Pour la reprise de ce blog, je resterai dans les habitudes du passé, à savoir
un petit billet sur <a href="http://www.perl.org">Perl</a>. Aujourd'hui, je parlerai d'un module qui est utile
quand l'on souhaite effectuer un traitement lié au web. Il s'agit de
<a href="http://search.cpan.org/dist/LWP-Online">LWP::Online</a>. Ce module très simple permet de répondre à la question « Suis-je
actuellement connecté au web ? ». 
</p>
<p>
L'utilisation est tout aussi simple que l'implémentation du module puisqu'il
s'agit d'importer une fonction, <code>online()</code>, qui retourne une valeur
booléenne. 
</p>
<p>
Un script simpliste ressemblerait donc à ceci :
</p>



<pre class="src src-perl"><span style="color: #cd0000; font-weight: bold; font-style: italic;">#</span><span style="color: #cd0000; font-weight: bold; font-style: italic;">!/usr/bin/env perl
</span>
<span style="color: #0000ee;">use</span> <span style="color: #0000ee;">strict</span>;
<span style="color: #0000ee;">use</span> <span style="color: #0000ee;">warnings</span>;

<span style="color: #0000ee;">use</span> <span style="color: #0000ee;">LWP::Online</span> <span style="color: #00cd00;">qw</span><span style="color: #0000ee; font-style: italic;">( online )</span>; <span style="color: #cd0000; font-weight: bold; font-style: italic;"># </span><span style="color: #cd0000; font-weight: bold; font-style: italic;">on importe la fonction online() dans notre script
</span>
<span style="color: #0000ee;">if</span> ( online() ) {
    <span style="color: #00cd00;">print</span> <span style="color: #0000ee; font-style: italic;">"Nous sommes connect&#233;s !\n"</span>;
}
<span style="color: #0000ee;">else</span> {
    <span style="color: #00cd00;">print</span> <span style="color: #0000ee; font-style: italic;">"Nous ne sommes pas connect&#233;s !\n"</span>;
}
</pre>



<p>
Le module offre également une fonction <code>offline()</code> qui retourne la valeur
<i>vrai</i> quand nous ne sommes pas connecté au Web. 
</p>]]>
    </content>
</entry>

<entry>
    <title>Découverte automatique des fils RSS</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/2010/05/decouverte-automatique-des-fils-rss.html" />
    <id>tag:blog.bjornoya.be,2010:/lab_stacks//1.31</id>

    <published>2010-05-05T20:27:55Z</published>
    <updated>2010-05-05T20:31:21Z</updated>

    <summary><![CDATA[J'ai été confronté à un problème intéressant ce matin. Le problème était le
suivant : 


Une liste de sites dont nous souhaitions avoir les fils RSS. 




Au niveau des solutions, il n'y avait pas trop de choix possibles : 


faire le travail manuellement ;


développer une solution plus automatisée.




Mon cœur penche évidemment pour la solution automatisée. J'avais développé un
petit outil permettant de récupérer les fils RSS d'un site. Cet outil était
vraiment très simple puisque s'appuyait sur la fonction find_feeds() du
module XML::Feed. Voici la version en one-liner : 



perl -MXML::Feed -MData::Dump -e 'dd(XML::Feed-&gt;find_feeds(shift))' http://lesoir.be






J'ai donc modifié mon script de manière à gérer une liste de liens, et fournir
en retour une liste des fils RSS. J'ai également ajouté une petite gestion de
statistiques de manière à savoir le nombre de sites traités et quels sont ceux
n'offrant pas de fils RSS (ou plus exactement, les sites dont XML::Feed n'a
pas pu récupérer les fils RSS). Voici le script final : 




#!/usr/bin/env perl

use strict;
use warnings;

use YAML;
use Getopt::Long;
use XML::Feed;

my $config = {};

GetOptions( $config, "input=s", "output=s" );

die _usage() unless _valid($config);

my $sites = YAML::LoadFile( $config-&gt;{input} );
my @feeds;
my @with_feeds;
my @no_feeds;

foreach my $site ( @{$sites} ) {
    my @site_feeds = XML::Feed-&gt;find_feeds($site);
    if ( scalar(@site_feeds) &gt; 0 ) {
        push @feeds,      @site_feeds;
        push @with_feeds, $site;
    }
    else {
        push @no_feeds, $site;
    }
}

YAML::DumpFile( $config-&gt;{output}, \@feeds );
print "# sites avec feeds : ", scalar(@with_feeds), $/;
print "# sites sans feed : ",  scalar(@no_feeds),   $/;
YAML::DumpFile( './sites_without_feed.yml', \@no_feeds );

sub _usage {
    return "Usage: $0 --input file.yml --output file-output.yml\n";
}

sub _valid {
    my $config = shift;

    if ( exists $config-&gt;{input} and exists $config-&gt;{output} ) {
        return 1;
    }
    else {
        return 0;
    }
}
]]></summary>
    <author>
        <name>manu</name>
        
    </author>
    
        <category term="Français" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="cpan" label="cpan" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="feed" label="feed" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="perl" label="perl" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="rss" label="rss" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="fr" xml:base="http://blog.bjornoya.be/lab_stacks/">
        <![CDATA[<p>J'ai été confronté à un problème intéressant ce matin. Le problème était le
suivant : 
</p>
<div style="text-align: center">
<p>Une liste de sites dont nous souhaitions avoir les fils RSS. 
</p>
</div>

<p>
Au niveau des solutions, il n'y avait pas trop de choix possibles : 
</p><ol>
<li>
faire le travail manuellement ;
</li>
<li>
développer une solution plus automatisée.

</li>
</ol>

<p>Mon cœur penche évidemment pour la solution automatisée. J'avais développé un
petit outil permettant de récupérer les fils RSS d'un site. Cet outil était
vraiment très simple puisque s'appuyait sur la fonction <code>find_feeds()</code> du
module <a href="http://search.cpan.org/dist/XML-Feed/">XML::Feed</a>. Voici la version en <i>one-liner</i> : 
</p>


<pre class="src src-sh">perl -MXML::Feed -MData::Dump -e <span style="color: #483d8b; font-style: italic;">'dd(XML::Feed-&gt;find_feeds(shift))'</span> http://lesoir.be
</pre>




<p>
J'ai donc modifié mon script de manière à gérer une liste de liens, et fournir
en retour une liste des fils RSS. J'ai également ajouté une petite gestion de
statistiques de manière à savoir le nombre de sites traités et quels sont ceux
n'offrant pas de fils RSS (ou plus exactement, les sites dont <a href="http://search.cpan.org/dist/XML-Feed/">XML::Feed</a> n'a
pas pu récupérer les fils RSS). Voici le script final : 
</p>



<pre class="src src-perl"><span style="color: #b22222; font-weight: bold; font-style: italic;">#</span><span style="color: #b22222; font-weight: bold; font-style: italic;">!/usr/bin/env perl
</span>
<span style="color: #000080;">use</span> <span style="color: #0000ff;">strict</span>;
<span style="color: #000080;">use</span> <span style="color: #0000ff;">warnings</span>;

<span style="color: #000080;">use</span> <span style="color: #0000ff;">YAML</span>;
<span style="color: #000080;">use</span> <span style="color: #0000ff;">Getopt::Long</span>;
<span style="color: #000080;">use</span> <span style="color: #0000ff;">XML::Feed</span>;

<span style="color: #000080;">my</span> <span style="color: #00008b;">$config</span> = {};

GetOptions( <span style="color: #00008b;">$config</span>, <span style="color: #483d8b; font-style: italic;">"input=s"</span>, <span style="color: #483d8b; font-style: italic;">"output=s"</span> );

<span style="color: #000080;">die</span> _usage() <span style="color: #000080;">unless</span> _valid(<span style="color: #00008b;">$config</span>);

<span style="color: #000080;">my</span> <span style="color: #00008b;">$sites</span> = YAML::LoadFile( <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">input</span>} );
<span style="color: #000080;">my</span> <span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@feeds</span>;
<span style="color: #000080;">my</span> <span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@with_feeds</span>;
<span style="color: #000080;">my</span> <span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@no_feeds</span>;

<span style="color: #000080;">foreach</span> <span style="color: #000080;">my</span> <span style="color: #00008b;">$site</span> ( @{<span style="color: #00008b;">$sites</span>} ) {
    <span style="color: #000080;">my</span> <span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@site_feeds</span> = XML::Feed-&gt;find_feeds(<span style="color: #00008b;">$site</span>);
    <span style="color: #000080;">if</span> ( <span style="color: #66cd00;">scalar</span>(<span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@site_feeds</span>) &gt; 0 ) {
        <span style="color: #66cd00;">push</span> <span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@feeds</span>,      <span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@site_feeds</span>;
        <span style="color: #66cd00;">push</span> <span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@with_feeds</span>, <span style="color: #00008b;">$site</span>;
    }
    <span style="color: #000080;">else</span> {
        <span style="color: #66cd00;">push</span> <span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@no_feeds</span>, <span style="color: #00008b;">$site</span>;
    }
}

YAML::DumpFile( <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">output</span>}, \<span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@feeds</span> );
<span style="color: #66cd00;">print</span> <span style="color: #483d8b; font-style: italic;">"# sites avec feeds : "</span>, <span style="color: #66cd00;">scalar</span>(<span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@with_feeds</span>), $/;
<span style="color: #66cd00;">print</span> <span style="color: #483d8b; font-style: italic;">"# sites sans feed : "</span>,  <span style="color: #66cd00;">scalar</span>(<span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@no_feeds</span>),   $/;
YAML::DumpFile( <span style="color: #483d8b; font-style: italic;">'./sites_without_feed.yml'</span>, \<span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@no_feeds</span> );

<span style="color: #000080;">sub</span> <span style="color: #0000ff;">_usage</span> {
    <span style="color: #000080;">return</span> <span style="color: #483d8b; font-style: italic;">"Usage: $0 --input file.yml --output file-output.yml\n"</span>;
}

<span style="color: #000080;">sub</span> <span style="color: #0000ff;">_valid</span> {
    <span style="color: #000080;">my</span> <span style="color: #00008b;">$config</span> = <span style="color: #66cd00;">shift</span>;

    <span style="color: #000080;">if</span> ( <span style="color: #66cd00;">exists</span> <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">input</span>} <span style="color: #006400;">and</span> <span style="color: #66cd00;">exists</span> <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">output</span>} ) {
        <span style="color: #000080;">return</span> 1;
    }
    <span style="color: #000080;">else</span> {
        <span style="color: #000080;">return</span> 0;
    }
}
</pre>]]>
    </content>
</entry>

<entry>
    <title>Migration de bases de données Winisis - Première étape</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/2010/04/migration-de-bases-de-donnees-winisis---premiere-etape.html" />
    <id>tag:blog.bjornoya.be,2010:/lab_stacks//1.30</id>

    <published>2010-04-27T11:38:47Z</published>
    <updated>2010-04-27T11:46:16Z</updated>

    <summary><![CDATA[Depuis quelques années maintenant, je suis régulièremnt contacté par mail pour
communiquer les outils permettant de migrer des bases de données
Winisis. Malheureusement, je n'ai jamais pris le temps de faire rédiger une
documentation convenable, et c'est dommage. Je vais donc jeter les bases de
cette documentation sur ce blog, et j'espère que je prendrai le temps dans le
futur de mettre en place une documentation plus complète. 


Avant de passer aux outils proprement dit, voici le cadre dans lequel ils ont
été développés. Dans le cadre d'une formation organisée conjointement par
l'Université Libre de Bruxelles (ULB) et par la Commission Universitaire pour le Développement (CUD). j'ai donné un cours nommé « Systèmes intégrés de
gestion de bibliothèques ». Les stagiaires suivant cette formation devaient
mener un projet, et un de ces stagiaires souhaitait migrer de Winisis vers
Koha (c'était le logiciel servant d'illustration à mon cours). Je me suis donc
penché sur la question. L'objectif était donc de migrer la base de données
ISIS vers du MARC21.


Les bases de données s'appuyant sur un schéma spécifique à chaque utilisateur
(Winisis n'est donc pas un SIGB, mais un SGBD, spécialisé dans les données
bibliographiques, certes, mais un SGBD ; il ressemble donc plus à Microsoft
Access qu'à Koha). la première étape consiste donc à déterminer la structure
de la base de données de manière à établir une table de correspondance vers le
format MARC souhaité (MARC21, Unimarc ou n'importe quelle autre
variante). Aujourd'hui, je ne verrai que cette étape, et l'outil développé
pour cela. Les autres étapes suivront dans les jours à venir. 


La structure d'une base de données ISIS, nommé « Field Definition Table »,
donc « table de définition des champs », dans le jargon ISIS se trouve
stocké dans un fichier texte dont l'extension est « .fdt ». En analysant ce
fichier, on peut donc facilement déterminer la structure de la base de
données. Biblio::Isis est le module de choix pour manipuler les bases de
données ISIS, il offre une fonction permettant de lire cette table de
définition des champs, et donc le code suivant est basé sur ce module :




#!/usr/bin/env perl

use strict;
use warnings;

use Getopt::Long;
use File::Find::Rule;
use Data::Dumper;
use Encode qw( from_to );
use Spreadsheet::WriteExcel;

my $config = {};

my %save_functions = (
    excel =&gt; \&amp;_save_excel,
    dump  =&gt; \&amp;_dump_struct,
);

my $fdt_struct = {};

GetOptions( $config, 'database=s', 'file=s', 'save=s' );

if ( not exists $config-&gt;{database} and not exists $config-&gt;{file} ) {
    die _usage();
}
else {
    my $fdt_file;
    if ( exists $config-&gt;{database} ) {
        $fdt_file = _get_fdt_file( $config-&gt;{database} );
    }
    else {
        $fdt_file = $config-&gt;{file};
    }
    $fdt_struct = _map_fdt_file($fdt_file);
    _print_fdt_struct($fdt_struct);
    _save_fdt_struct( $config-&gt;{save}, @ARGV ) if $config-&gt;{save};
}

sub _usage {
    return
"Usage: $0 [--database 'ISIS database' | --file 'ISIS.FDT' [--save excel|dump filename.xls]]\n";
}

sub _get_fdt_file {
    my $database = shift;

    if ( not -e $config-&gt;{database} ) {
        print "Unable to open the ISIS database: $database\n";
        return;
    }

    my @files =
      File::Find::Rule-&gt;file()-&gt;name( '*.fdt', '*.FDT' )
      -&gt;in( $config-&gt;{database} );

    if ( scalar(@files) &gt; 1 ) {
        print "Too many FDT files!\n";
        return shift(@files);
    }

    if ( scalar(@files) == 0 ) {
        print "No FDT files!!!\n";
        return;
    }

    return shift(@files);
}

sub _map_fdt_file {
    my $fdt_file = shift;

    my $fdt_struct = {};

    # Code heavily based on Biblio::Isis
    my $fieldzone = 0;

    open( my $fileFDT, $fdt_file ) or die "$fdt_file: $!\n";
    binmode($fileFDT);

    while (&lt;$fileFDT&gt;) {
        chomp;
        if ($fieldzone) {
            my $name       = substr( $_, 0,  30 );
            my $tag        = substr( $_, 50, 3 );
            my $repeatable = substr( $_, 61, 1 );

            $name =~ s/\s+$//;
            $tag  =~ s/\s+$//;

            $fdt_struct-&gt;{$tag} = {
                name       =&gt; from_to( $name, 'cp850', 'utf8' ),
                repeatable =&gt; $repeatable,
            };
        }

        if (/^\*\*\*/) {
            $fieldzone = 1;
        }
    }

    close($fileFDT);

    return $fdt_struct;
}

sub _print_fdt_struct {
    my $fdt_struct = shift;

    foreach my $tag ( keys %{$fdt_struct} ) {
        print "$tag\t=&gt;\t" . $fdt_struct-&gt;{$tag}-&gt;{name} . "\n";
    }

    return;
}

sub _save_fdt_struct {
    my $save_function = shift;

    if ( exists $save_functions{$save_function} ) {
        if ( scalar(@_) &gt; 0 ) {
            $save_functions{$save_function}-&gt;(@_);
        }
        else {
            die "No file where to put the schema\n";
        }
    }
    else {
        die "Save function &lt;$save_function&gt; doesn't exist\n";
    }
}

sub _save_excel {
    my $excel_filename = shift;

    my $excel = Spreadsheet::WriteExcel-&gt;new($excel_filename);
    my $ws    = $excel-&gt;add_worksheet();
    $ws-&gt;write( 0, 0, "Champ ISIS" );
    $ws-&gt;write( 0, 1, "Champ MARC21" );
    $ws-&gt;write( 0, 2, "Description" );

    my $row = 1;
    foreach my $tag ( sort keys %{$fdt_struct} ) {
        $ws-&gt;write( $row, 0, $tag );
        $ws-&gt;write( $row, 2,
            from_to( $fdt_struct-&gt;{$tag}-&gt;{name}, 'utf8', 'latin1' ) );

        $row++;
    }
}

sub _dump_struct {
    print Dumper $fdt_struct;
}






L'outil permet donc d'afficher la structure d'une base de données, mais aussi
de créer un fichier Excel contenant cette structure. C'est ce fichier Excel
qui servira de base de à la négociation pour mettre en place la table de
correspondance nécessaire pour lancer la migration. 


L'outil s'emploit de la manière suivante : ./fdt_reader.pl -f ISIS.FDT -s excel isis_db.xls. Le fichier isis_db.xls contient trois colonnes :


la première est le champ ISIS ;


la deuxième est le champ (et le sous-champs) MARC correspondant ;


la troisième est la description associée au champs ISIS, et donc, sur base
de cette description, on se fait une idée précise de la fonction de
celui-ci. 




Nous verrons la prochaine fois comment ce fichier Excel s'intègre dans le
processus de migration. 
]]></summary>
    <author>
        <name>manu</name>
        
    </author>
    
        <category term="Français" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="cpan" label="cpan" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="isis" label="isis" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="migration" label="migration" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="perl" label="perl" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="winisis" label="winisis" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="fr" xml:base="http://blog.bjornoya.be/lab_stacks/">
        <![CDATA[<p>Depuis quelques années maintenant, je suis régulièremnt contacté par mail pour
communiquer les outils permettant de migrer des bases de données
<a href="http://portal.unesco.org/ci/en/ev.php-URL_ID=2071&amp;URL_DO=DO_TOPIC&amp;URL_SECTION=201.html">Winisis</a>. Malheureusement, je n'ai jamais pris le temps de faire rédiger une
documentation convenable, et c'est dommage. Je vais donc jeter les bases de
cette documentation sur ce blog, et j'espère que je prendrai le temps dans le
futur de mettre en place une documentation plus complète. 
</p>
<p>
Avant de passer aux outils proprement dit, voici le cadre dans lequel ils ont
été développés. Dans le cadre d'une formation organisée conjointement par
l'<a href="http://www.ulb.ac.be/">Université Libre de Bruxelles (ULB)</a> et par la <a href="http://www.cud.be/">Commission Universitaire pour le Développement (CUD)</a>. j'ai donné un cours nommé « Systèmes intégrés de
gestion de bibliothèques ». Les stagiaires suivant cette formation devaient
mener un projet, et un de ces stagiaires souhaitait migrer de Winisis vers
<a href="http://www.koha.org">Koha</a> (c'était le logiciel servant d'illustration à mon cours). Je me suis donc
penché sur la question. L'objectif était donc de migrer la base de données
ISIS vers du <a href="http://www.loc.gov/marc/bibliographic/">MARC21</a>.
</p>
<p>
Les bases de données s'appuyant sur un schéma spécifique à chaque utilisateur
(Winisis n'est donc pas un SIGB, mais un SGBD, spécialisé dans les données
bibliographiques, certes, mais un SGBD ; il ressemble donc plus à Microsoft
Access qu'à Koha). la première étape consiste donc à déterminer la structure
de la base de données de manière à établir une table de correspondance vers le
format MARC souhaité (MARC21, Unimarc ou n'importe quelle autre
variante). Aujourd'hui, je ne verrai que cette étape, et l'outil développé
pour cela. Les autres étapes suivront dans les jours à venir. 
</p>
<p>
La structure d'une base de données ISIS, nommé « <b>Field Definition Table</b> »,
donc « <b>table de définition des champs</b> », dans le jargon ISIS se trouve
stocké dans un fichier texte dont l'extension est « <code>.fdt</code> ». En analysant ce
fichier, on peut donc facilement déterminer la structure de la base de
données. <a href="http://search.cpan.org/dist/Biblio-Isis/">Biblio::Isis</a> est le module de choix pour manipuler les bases de
données ISIS, il offre une fonction permettant de lire cette table de
définition des champs, et donc le code suivant est basé sur ce module :
</p>



<pre class="src src-perl"><span style="color: #b22222; font-weight: bold; font-style: italic;">#</span><span style="color: #b22222; font-weight: bold; font-style: italic;">!/usr/bin/env perl
</span>
<span style="color: #000080;">use</span> <span style="color: #0000ff;">strict</span>;
<span style="color: #000080;">use</span> <span style="color: #0000ff;">warnings</span>;

<span style="color: #000080;">use</span> <span style="color: #0000ff;">Getopt::Long</span>;
<span style="color: #000080;">use</span> <span style="color: #0000ff;">File::Find::Rule</span>;
<span style="color: #000080;">use</span> <span style="color: #0000ff;">Data::Dumper</span>;
<span style="color: #000080;">use</span> <span style="color: #0000ff;">Encode</span> <span style="color: #66cd00;">qw</span><span style="color: #483d8b; font-style: italic;">( from_to )</span>;
<span style="color: #000080;">use</span> <span style="color: #0000ff;">Spreadsheet::WriteExcel</span>;

<span style="color: #000080;">my</span> <span style="color: #00008b;">$config</span> = {};

<span style="color: #000080;">my</span> <span style="color: #ff0000; background-color: #eee9e9; font-weight: bold; font-style: italic;">%save_functions</span> = (
    <span style="color: #483d8b; font-style: italic;">excel</span> =&gt; \&amp;_save_excel,
    <span style="color: #483d8b; font-style: italic;">dump</span>  =&gt; \&amp;_dump_struct,
);

<span style="color: #000080;">my</span> <span style="color: #00008b;">$fdt_struct</span> = {};

GetOptions( <span style="color: #00008b;">$config</span>, <span style="color: #483d8b; font-style: italic;">'database=s'</span>, <span style="color: #483d8b; font-style: italic;">'file=s'</span>, <span style="color: #483d8b; font-style: italic;">'save=s'</span> );

<span style="color: #000080;">if</span> ( <span style="color: #006400;">not</span> <span style="color: #66cd00;">exists</span> <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">database</span>} <span style="color: #006400;">and</span> <span style="color: #006400;">not</span> <span style="color: #66cd00;">exists</span> <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">file</span>} ) {
    <span style="color: #000080;">die</span> _usage();
}
<span style="color: #000080;">else</span> {
    <span style="color: #000080;">my</span> <span style="color: #00008b;">$fdt_file</span>;
    <span style="color: #000080;">if</span> ( <span style="color: #66cd00;">exists</span> <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">database</span>} ) {
        <span style="color: #00008b;">$fdt_file</span> = _get_fdt_file( <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">database</span>} );
    }
    <span style="color: #000080;">else</span> {
        <span style="color: #00008b;">$fdt_file</span> = <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">file</span>};
    }
    <span style="color: #00008b;">$fdt_struct</span> = _map_fdt_file(<span style="color: #00008b;">$fdt_file</span>);
    _print_fdt_struct(<span style="color: #00008b;">$fdt_struct</span>);
    _save_fdt_struct( <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">save</span>}, <span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@ARGV</span> ) <span style="color: #000080;">if</span> <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">save</span>};
}

<span style="color: #000080;">sub</span> <span style="color: #0000ff;">_usage</span> {
    <span style="color: #000080;">return</span>
<span style="color: #483d8b; font-style: italic;">"Usage: $0 [--database 'ISIS database' | --file 'ISIS.FDT' [--save excel|dump filename.xls]]\n"</span>;
}

<span style="color: #000080;">sub</span> <span style="color: #0000ff;">_get_fdt_file</span> {
    <span style="color: #000080;">my</span> <span style="color: #00008b;">$database</span> = <span style="color: #66cd00;">shift</span>;

    <span style="color: #000080;">if</span> ( <span style="color: #006400;">not</span> <span style="color: #0000ff;">-e</span> <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">database</span>} ) {
        <span style="color: #66cd00;">print</span> <span style="color: #483d8b; font-style: italic;">"Unable to open the ISIS database: $database\n"</span>;
        <span style="color: #000080;">return</span>;
    }

    <span style="color: #000080;">my</span> <span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@files</span> =
      File::Find::Rule-&gt;file()-&gt;name( <span style="color: #483d8b; font-style: italic;">'*.fdt'</span>, <span style="color: #483d8b; font-style: italic;">'*.FDT'</span> )
      -&gt;in( <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">database</span>} );

    <span style="color: #000080;">if</span> ( <span style="color: #66cd00;">scalar</span>(<span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@files</span>) &gt; 1 ) {
        <span style="color: #66cd00;">print</span> <span style="color: #483d8b; font-style: italic;">"Too many FDT files!\n"</span>;
        <span style="color: #000080;">return</span> <span style="color: #66cd00;">shift</span>(<span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@files</span>);
    }

    <span style="color: #000080;">if</span> ( <span style="color: #66cd00;">scalar</span>(<span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@files</span>) == 0 ) {
        <span style="color: #66cd00;">print</span> <span style="color: #483d8b; font-style: italic;">"No FDT files!!!\n"</span>;
        <span style="color: #000080;">return</span>;
    }

    <span style="color: #000080;">return</span> <span style="color: #66cd00;">shift</span>(<span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@files</span>);
}

<span style="color: #000080;">sub</span> <span style="color: #0000ff;">_map_fdt_file</span> {
    <span style="color: #000080;">my</span> <span style="color: #00008b;">$fdt_file</span> = <span style="color: #66cd00;">shift</span>;

    <span style="color: #000080;">my</span> <span style="color: #00008b;">$fdt_struct</span> = {};

    <span style="color: #b22222; font-weight: bold; font-style: italic;"># </span><span style="color: #b22222; font-weight: bold; font-style: italic;">Code heavily based on Biblio::Isis
</span>    <span style="color: #000080;">my</span> <span style="color: #00008b;">$fieldzone</span> = 0;

    <span style="color: #006400;">open</span>( <span style="color: #000080;">my</span> <span style="color: #00008b;">$fileFDT</span>, <span style="color: #00008b;">$fdt_file</span> ) <span style="color: #006400;">or</span> <span style="color: #000080;">die</span> <span style="color: #483d8b; font-style: italic;">"$fdt_file: $!\n"</span>;
    <span style="color: #006400;">binmode</span>(<span style="color: #00008b;">$fileFDT</span>);

    <span style="color: #000080;">while</span> (&lt;<span style="color: #00008b;">$fileFDT</span>&gt;) {
        <span style="color: #66cd00;">chomp</span>;
        <span style="color: #000080;">if</span> (<span style="color: #00008b;">$fieldzone</span>) {
            <span style="color: #000080;">my</span> <span style="color: #00008b;">$name</span>       = <span style="color: #006400;">substr</span>( <span style="color: #00008b;">$_</span>, 0,  30 );
            <span style="color: #000080;">my</span> <span style="color: #00008b;">$tag</span>        = <span style="color: #006400;">substr</span>( <span style="color: #00008b;">$_</span>, 50, 3 );
            <span style="color: #000080;">my</span> <span style="color: #00008b;">$repeatable</span> = <span style="color: #006400;">substr</span>( <span style="color: #00008b;">$_</span>, 61, 1 );

            <span style="color: #00008b;">$name</span> =~ <span style="color: #66cd00;">s</span><span style="color: #5f9ea0;">/</span><span style="color: #ffffff;">\</span><span style="color: #006400;">s</span><span style="color: #ffffff;">+</span><span style="color: #0000ff;">$</span><span style="color: #5f9ea0;">/</span>/;
            <span style="color: #00008b;">$tag</span>  =~ <span style="color: #66cd00;">s</span><span style="color: #5f9ea0;">/</span><span style="color: #ffffff;">\</span><span style="color: #006400;">s</span><span style="color: #ffffff;">+</span><span style="color: #0000ff;">$</span><span style="color: #5f9ea0;">/</span>/;

            <span style="color: #00008b;">$fdt_struct</span>-&gt;{<span style="color: #00008b;">$tag</span>} = {
                <span style="color: #483d8b; font-style: italic;">name</span>       =&gt; from_to( <span style="color: #00008b;">$name</span>, <span style="color: #483d8b; font-style: italic;">'cp850'</span>, <span style="color: #483d8b; font-style: italic;">'utf8'</span> ),
                <span style="color: #483d8b; font-style: italic;">repeatable</span> =&gt; <span style="color: #00008b;">$repeatable</span>,
            };
        }

        <span style="color: #000080;">if</span> (<span style="color: #5f9ea0;">/</span><span style="color: #0000ff;">^</span><span style="color: #ffffff;">\</span><span style="color: #483d8b; font-style: italic;">*</span><span style="color: #ffffff;">\</span><span style="color: #483d8b; font-style: italic;">*</span><span style="color: #ffffff;">\</span><span style="color: #483d8b; font-style: italic;">*</span><span style="color: #5f9ea0;">/</span>) {
            <span style="color: #00008b;">$fieldzone</span> = 1;
        }
    }

    <span style="color: #006400;">close</span>(<span style="color: #00008b;">$fileFDT</span>);

    <span style="color: #000080;">return</span> <span style="color: #00008b;">$fdt_struct</span>;
}

<span style="color: #000080;">sub</span> <span style="color: #0000ff;">_print_fdt_struct</span> {
    <span style="color: #000080;">my</span> <span style="color: #00008b;">$fdt_struct</span> = <span style="color: #66cd00;">shift</span>;

    <span style="color: #000080;">foreach</span> <span style="color: #000080;">my</span> <span style="color: #00008b;">$tag</span> ( <span style="color: #66cd00;">keys</span> %{<span style="color: #00008b;">$fdt_struct</span>} ) {
        <span style="color: #66cd00;">print</span> <span style="color: #483d8b; font-style: italic;">"$tag\t=&gt;\t"</span> . <span style="color: #00008b;">$fdt_struct</span>-&gt;{<span style="color: #00008b;">$tag</span>}-&gt;{<span style="color: #483d8b; font-style: italic;">name</span>} . <span style="color: #483d8b; font-style: italic;">"\n"</span>;
    }

    <span style="color: #000080;">return</span>;
}

<span style="color: #000080;">sub</span> <span style="color: #0000ff;">_save_fdt_struct</span> {
    <span style="color: #000080;">my</span> <span style="color: #00008b;">$save_function</span> = <span style="color: #66cd00;">shift</span>;

    <span style="color: #000080;">if</span> ( <span style="color: #66cd00;">exists</span> <span style="color: #ff0000; background-color: #eee9e9; font-weight: bold; font-style: italic;">$save_functions</span>{<span style="color: #00008b;">$save_function</span>} ) {
        <span style="color: #000080;">if</span> ( <span style="color: #66cd00;">scalar</span>(<span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@_</span>) &gt; 0 ) {
            <span style="color: #ff0000; background-color: #eee9e9; font-weight: bold; font-style: italic;">$save_functions</span>{<span style="color: #00008b;">$save_function</span>}-&gt;(<span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@_</span>);
        }
        <span style="color: #000080;">else</span> {
            <span style="color: #000080;">die</span> <span style="color: #483d8b; font-style: italic;">"No file where to put the schema\n"</span>;
        }
    }
    <span style="color: #000080;">else</span> {
        <span style="color: #000080;">die</span> <span style="color: #483d8b; font-style: italic;">"Save function &lt;$save_function&gt; doesn't exist\n"</span>;
    }
}

<span style="color: #000080;">sub</span> <span style="color: #0000ff;">_save_excel</span> {
    <span style="color: #000080;">my</span> <span style="color: #00008b;">$excel_filename</span> = <span style="color: #66cd00;">shift</span>;

    <span style="color: #000080;">my</span> <span style="color: #00008b;">$excel</span> = Spreadsheet::WriteExcel-&gt;new(<span style="color: #00008b;">$excel_filename</span>);
    <span style="color: #000080;">my</span> <span style="color: #00008b;">$ws</span>    = <span style="color: #00008b;">$excel</span>-&gt;add_worksheet();
    <span style="color: #00008b;">$ws</span>-&gt;<span style="color: #006400;">write</span>( 0, 0, <span style="color: #483d8b; font-style: italic;">"Champ ISIS"</span> );
    <span style="color: #00008b;">$ws</span>-&gt;<span style="color: #006400;">write</span>( 0, 1, <span style="color: #483d8b; font-style: italic;">"Champ MARC21"</span> );
    <span style="color: #00008b;">$ws</span>-&gt;<span style="color: #006400;">write</span>( 0, 2, <span style="color: #483d8b; font-style: italic;">"Description"</span> );

    <span style="color: #000080;">my</span> <span style="color: #00008b;">$row</span> = 1;
    <span style="color: #000080;">foreach</span> <span style="color: #000080;">my</span> <span style="color: #00008b;">$tag</span> ( <span style="color: #66cd00;">sort</span> <span style="color: #66cd00;">keys</span> %{<span style="color: #00008b;">$fdt_struct</span>} ) {
        <span style="color: #00008b;">$ws</span>-&gt;<span style="color: #006400;">write</span>( <span style="color: #00008b;">$row</span>, 0, <span style="color: #00008b;">$tag</span> );
        <span style="color: #00008b;">$ws</span>-&gt;<span style="color: #006400;">write</span>( <span style="color: #00008b;">$row</span>, 2,
            from_to( <span style="color: #00008b;">$fdt_struct</span>-&gt;{<span style="color: #00008b;">$tag</span>}-&gt;{<span style="color: #483d8b; font-style: italic;">name</span>}, <span style="color: #483d8b; font-style: italic;">'utf8'</span>, <span style="color: #483d8b; font-style: italic;">'latin1'</span> ) );

        <span style="color: #00008b;">$row</span>++;
    }
}

<span style="color: #000080;">sub</span> <span style="color: #0000ff;">_dump_struct</span> {
    <span style="color: #66cd00;">print</span> Dumper <span style="color: #00008b;">$fdt_struct</span>;
}
</pre>




<p>
L'outil permet donc d'afficher la structure d'une base de données, mais aussi
de créer un fichier Excel contenant cette structure. C'est ce fichier Excel
qui servira de base de à la négociation pour mettre en place la table de
correspondance nécessaire pour lancer la migration. 
</p>
<p>
L'outil s'emploit de la manière suivante : <code>./fdt_reader.pl -f ISIS.FDT -s excel isis_db.xls</code>. Le fichier <code>isis_db.xls</code> contient trois colonnes :
</p><ol>
<li>
la première est le champ ISIS ;
</li>
<li>
la deuxième est le champ (et le sous-champs) MARC correspondant ;
</li>
<li>
la troisième est la description associée au champs ISIS, et donc, sur base
de cette description, on se fait une idée précise de la fonction de
celui-ci. 

</li>
</ol>

<p>Nous verrons la prochaine fois comment ce fichier Excel s'intègre dans le
processus de migration. 
</p>]]>
    </content>
</entry>

<entry>
    <title>Dédoublonnage des données lors d&apos;une migration</title>
    <link rel="alternate" type="text/html" href="http://blog.bjornoya.be/lab_stacks/2010/04/dedoublonnage-des-donnees-lors-dune-migration.html" />
    <id>tag:blog.bjornoya.be,2010:/lab_stacks//1.29</id>

    <published>2010-04-26T15:18:18Z</published>
    <updated>2010-04-27T11:45:03Z</updated>

    <summary><![CDATA[Lors de la migration d'un système d'information vers un autre, on est souvent
tenté de se lancer dans une opération d'amélioration de la qualité. Bien qu'il
s'agisse d'un projet en soi, cela peut être le « bon moment », pour autant que
l'on dispose des ressources nécessaires pour le faire. Ressources humaines,
évidemment, mais il faut aussi disposer d'assez de temps que pour pouvoir
mettre en place une méthodologie à même de nous garantir une amélioration de
la qualité.


Cette année quelques étudiants se sont lancés dans cette aventure pour leurs
travaux de fin d'études avec la migration de catalogues de bibliothèque. Et un
problème particulier s'est rapidement posé avec la présence de doublons dans
les listes d'autorité. Les catalogues sont les endroits rêvés pour mesurer la
créativité de l'esprit humain pour ne pas respecter les règles ;-)


Ainsi, dans un catalogue de bibliothèque, nous aurons une liste d'éditeurs
par exemple, et en fonction de la créativité des catalographes, mais aussi de
la quantité de personnes impliquées dans la gestion de ces éditeurs, nous
trouverons des variantes plus ou moins nombreuses : 


Ed. O'Reilly ;


Editions O'Reilly ;


O'Reilly ;


E. O'Reilly ;


etc.




L'idéal serait donc de pouvoir trouver tout les doublons, et de les remplacer
par la forme correcte. Mais comment trouver tout ces doublons ? Sans
ordinateur, la tâche est fastidieuse : 


parcourir la liste d'autorité ;


établir une liste des doublons ;


faire le choix de la forme « correcte » ;


effectuer les remplacements nécessaires. 




Face à ce genre de situations, mon premier réflexe est souvent de déterminer
ce que je peux informatiser, voire même automatiser. Dans le cas présent, ce
serait idéal de pouvoir obtenir une liste des « éventuels doublons », donc,
des expressions suffisamment proches que pour nous mettre la puce à
l'oreille. Dans le cas présent, l'informatique nous offre plusieurs
techniques : 


un algorithme permettant de calculer la distance de Levenshtein,
c'est-à-dire le nombre d'opérations élémentaires pour passer un mot M à un
mot P ; sur base de cet algorithme, nous allons donc pouvoir comparer chaque
entrée de la liste d'autorité avec le reste de cette dernière, et conserver
les éléments dont la distance de Levenshtein est peu importe (ce seuil est
évidemment à paramétrer comme la taille des mailles d'un filet) ;
évidemment, cet algorithme est disponible sur le CPAN : Text::Levenshtein
pour une version en Perl, et Text::LevenshteinXS pour une version en C ;


d'autres techniques, dérivées de la précédente, existent ; par exemple
employer l'algorithme se cachant derrière agrep, un grep permettant de
faire des approximations dans la recherce de chaînes de caractères ; un
module Perl existe qui reproduit ce comportement : String::Approx (il s'agit
ici aussi d'un module XS, donc basé sur du C). 




Donc, les techniques existent, « y a plus qu'à » &hellip; faire une recherche sur
le CPAN, par exemple avec le mot clé « group » ou « similarity », ce qui
m'a permis de décrouvrir String::Similarity::Group qui s'appuie sur
String::Similarity, lequel s'appuie sur un algorithme différent mais faisant
sensiblement la même chose que les techniques expliquées ci-dessus. Bref, une
fois encore le CPAN m'économise du temps, et me permet de mettre en place un
prototype rapidement (sans ce module, j'aurais mettre en place la création des
groupes, ce qui n'est certes pas compliqué, mais qui apporte son lot de
réflexions). 


Voici le prototype en question : 




#!/usr/bin/env perl

use strict;
use warnings;

use Spreadsheet::Read;
use Spreadsheet::Write;
use String::Similarity::Group qw( :all );
use Getopt::Long;

my $config = { threshold =&gt; "0.75", };

GetOptions( $config, "input=s", "output=s", "column=s", "sheet=s", "threshold=s" );

die _usage() unless _valid($config);
my $values = _build_values( $config-&gt;{input}, $config-&gt;{sheet}, $config-&gt;{column} );
my @groups = groups( $config-&gt;{threshold}, $values );

_save_groups( $config-&gt;{output}, \@groups );

sub _usage {
    return
"Usage: $0 --input file.xls --output output.xls --column 4 --sheet 1 --threshold '0.75'\n";
}

sub _valid {
    my $config = shift;

    if (    exists $config-&gt;{input}
        and exists $config-&gt;{output}
        and exists $config-&gt;{sheet}
        and exists $config-&gt;{column} )
    {
        return 1;
    }
    else {
        return 0;
    }
}

sub _build_values {
    my ( $file, $sheet_n, $col ) = @_;

    my @values;
    my $data  = ReadData($file);
    my $sheet = $data-&gt;[$sheet_n];

    for my $row ( 1 .. $sheet-&gt;{maxrow} ) {
        push @values, $sheet-&gt;{cell}[$col][$row];
    }

    return \@values;
}

sub _save_groups {
    my $output = shift;
    my $groups = shift;

    my $sheetname = '001';

    my $xls = Spreadsheet::Write-&gt;new(
        file   =&gt; $output,
        format =&gt; 'xls',
        sheet  =&gt; $sheetname++,
    );

    foreach my $group ( @{$groups} ) {
        foreach my $row ( @{$group} ) {
            $xls-&gt;addrow($row);
        }
        $xls-&gt;addsheet( 'group_' . $sheetname++ );
    }
}






Ce script en ligne de commande accepte les arguments suivants : 


input : cela permet de donner le nom du fichier Excel (ou n'importe quel
format géré par Spreadsheet::Read, donc OpenOffice.org Calc et CSV viennent
s'ajouter à la liste) ;


output : cela permet de spécifier le nom du fichier Excel qui contiendra
les groupes. J'ai pris la convention de créer autant de feuilles de calcul
qu'il n'y a de groupes, et de mettre les candidats doublons dans une feuille
de calcul ;


sheet : cela permet de spécifier le numéro de la feuille de calcul
contenant les données à analyser ;


column : cela permet de spécifier le numéro de la colonne contenant les
données à analyser.


threshold : il s'agit du seuil de similitude à partir duquel
String::Similarity::Group commence à regrouper les élements. C'est donc à
partir de ce paramètre que nous allons pouvoir agir sur la taille des
mailles de notre filet. 




Le reste du script est juste le liant permettant de faire travailler ces
différents modules ensemble. 


Voilà, j'ai donc maintenant à ma disposition un outil permettant de mettre en
évidence des candidats doublons, il faut évidement encore choisir soi-même
quelle est la forme correcte, mais la partie consistant à analyser les données
a été facilité et n'a pris que quelques minutes. 
]]></summary>
    <author>
        <name>manu</name>
        
    </author>
    
        <category term="Français" scheme="http://www.sixapart.com/ns/types#category" />
    
    <category term="cpan" label="cpan" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="dataquality" label="data quality" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="migration" label="migration" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="perl" label="perl" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="fr" xml:base="http://blog.bjornoya.be/lab_stacks/">
        <![CDATA[<p>Lors de la migration d'un système d'information vers un autre, on est souvent
tenté de se lancer dans une opération d'amélioration de la qualité. Bien qu'il
s'agisse d'un projet en soi, cela peut être le « bon moment », pour autant que
l'on dispose des ressources nécessaires pour le faire. Ressources humaines,
évidemment, mais il faut aussi disposer d'assez de temps que pour pouvoir
mettre en place une méthodologie à même de nous garantir une amélioration de
la qualité.
</p>
<p>
Cette année quelques étudiants se sont lancés dans cette aventure pour leurs
travaux de fin d'études avec la migration de catalogues de bibliothèque. Et un
problème particulier s'est rapidement posé avec la présence de doublons dans
les listes d'autorité. Les catalogues sont les endroits rêvés pour mesurer la
créativité de l'esprit humain pour ne pas respecter les règles ;-)
</p>
<p>
Ainsi, dans un catalogue de bibliothèque, nous aurons une liste d'éditeurs
par exemple, et en fonction de la créativité des catalographes, mais aussi de
la quantité de personnes impliquées dans la gestion de ces éditeurs, nous
trouverons des variantes plus ou moins nombreuses : 
</p><ul>
<li>
Ed. O'Reilly ;
</li>
<li>
Editions O'Reilly ;
</li>
<li>
O'Reilly ;
</li>
<li>
E. O'Reilly ;
</li>
<li>
etc.

</li>
</ul>

<p>L'idéal serait donc de pouvoir trouver tout les doublons, et de les remplacer
par la forme correcte. Mais comment trouver tout ces doublons ? Sans
ordinateur, la tâche est fastidieuse : 
</p><ol>
<li>
parcourir la liste d'autorité ;
</li>
<li>
établir une liste des doublons ;
</li>
<li>
faire le choix de la forme « correcte » ;
</li>
<li>
effectuer les remplacements nécessaires. 

</li>
</ol>

<p>Face à ce genre de situations, mon premier réflexe est souvent de déterminer
ce que je peux informatiser, voire même automatiser. Dans le cas présent, ce
serait idéal de pouvoir obtenir une liste des « éventuels doublons », donc,
des expressions suffisamment proches que pour nous mettre la puce à
l'oreille. Dans le cas présent, l'informatique nous offre plusieurs
techniques : 
</p><ul>
<li>
un algorithme permettant de calculer la <a href="http://fr.wikipedia.org/wiki/Distance_de_Levenshtein">distance de Levenshtein</a>,
c'est-à-dire le nombre d'opérations élémentaires pour passer un mot M à un
mot P ; sur base de cet algorithme, nous allons donc pouvoir comparer chaque
entrée de la liste d'autorité avec le reste de cette dernière, et conserver
les éléments dont la distance de Levenshtein est peu importe (ce seuil est
évidemment à paramétrer comme la taille des mailles d'un filet) ;
évidemment, cet algorithme est disponible sur le CPAN : <a href="http://search.cpan.org/dist/Text-Levenshtein/">Text::Levenshtein</a>
pour une version en Perl, et <a href="http://search.cpan.org/dist/Text-LevenshteinXS/">Text::LevenshteinXS</a> pour une version en C ;
</li>
<li>
d'autres techniques, dérivées de la précédente, existent ; par exemple
employer l'algorithme se cachant derrière <code>agrep</code>, un <code>grep</code> permettant de
faire des approximations dans la recherce de chaînes de caractères ; un
module Perl existe qui reproduit ce comportement : <a href="http://search.cpan.org/dist/String-Approx/">String::Approx</a> (il s'agit
ici aussi d'un module XS, donc basé sur du C). 

</li>
</ul>

<p>Donc, les techniques existent, « y a plus qu'à » &hellip; faire une recherche sur
le CPAN, par exemple avec le mot clé « <i>group</i> » ou « <i>similarity</i> », ce qui
m'a permis de décrouvrir <a href="http://search.cpan.org/dist/String-Similarity-Group">String::Similarity::Group</a> qui s'appuie sur
<a href="http://search.cpan.org/dist/String-Similarity/">String::Similarity</a>, lequel s'appuie sur un algorithme différent mais faisant
sensiblement la même chose que les techniques expliquées ci-dessus. Bref, une
fois encore le CPAN m'économise du temps, et me permet de mettre en place un
prototype rapidement (sans ce module, j'aurais mettre en place la création des
groupes, ce qui n'est certes pas compliqué, mais qui apporte son lot de
réflexions). 
</p>
<p>
Voici le prototype en question : 
</p>



<pre class="src src-perl"><span style="color: #b22222; font-weight: bold; font-style: italic;">#</span><span style="color: #b22222; font-weight: bold; font-style: italic;">!/usr/bin/env perl
</span>
<span style="color: #000080;">use</span> <span style="color: #0000ff;">strict</span>;
<span style="color: #000080;">use</span> <span style="color: #0000ff;">warnings</span>;

<span style="color: #000080;">use</span> <span style="color: #0000ff;">Spreadsheet::Read</span>;
<span style="color: #000080;">use</span> <span style="color: #0000ff;">Spreadsheet::Write</span>;
<span style="color: #000080;">use</span> <span style="color: #0000ff;">String::Similarity::Group</span> <span style="color: #66cd00;">qw</span><span style="color: #483d8b; font-style: italic;">( :all )</span>;
<span style="color: #000080;">use</span> <span style="color: #0000ff;">Getopt::Long</span>;

<span style="color: #000080;">my</span> <span style="color: #00008b;">$config</span> = { <span style="color: #483d8b; font-style: italic;">threshold</span> =&gt; <span style="color: #483d8b; font-style: italic;">"0.75"</span>, };

GetOptions( <span style="color: #00008b;">$config</span>, <span style="color: #483d8b; font-style: italic;">"input=s"</span>, <span style="color: #483d8b; font-style: italic;">"output=s"</span>, <span style="color: #483d8b; font-style: italic;">"column=s"</span>, <span style="color: #483d8b; font-style: italic;">"sheet=s"</span>, <span style="color: #483d8b; font-style: italic;">"threshold=s"</span> );

<span style="color: #000080;">die</span> _usage() <span style="color: #000080;">unless</span> _valid(<span style="color: #00008b;">$config</span>);
<span style="color: #000080;">my</span> <span style="color: #00008b;">$values</span> = _build_values( <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">input</span>}, <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">sheet</span>}, <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">column</span>} );
<span style="color: #000080;">my</span> <span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@groups</span> = groups( <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">threshold</span>}, <span style="color: #00008b;">$values</span> );

_save_groups( <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">output</span>}, \<span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@groups</span> );

<span style="color: #000080;">sub</span> <span style="color: #0000ff;">_usage</span> {
    <span style="color: #000080;">return</span>
<span style="color: #483d8b; font-style: italic;">"Usage: $0 --input file.xls --output output.xls --column 4 --sheet 1 --threshold '0.75'\n"</span>;
}

<span style="color: #000080;">sub</span> <span style="color: #0000ff;">_valid</span> {
    <span style="color: #000080;">my</span> <span style="color: #00008b;">$config</span> = <span style="color: #66cd00;">shift</span>;

    <span style="color: #000080;">if</span> (    <span style="color: #66cd00;">exists</span> <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">input</span>}
        <span style="color: #006400;">and</span> <span style="color: #66cd00;">exists</span> <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">output</span>}
        <span style="color: #006400;">and</span> <span style="color: #66cd00;">exists</span> <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">sheet</span>}
        <span style="color: #006400;">and</span> <span style="color: #66cd00;">exists</span> <span style="color: #00008b;">$config</span>-&gt;{<span style="color: #483d8b; font-style: italic;">column</span>} )
    {
        <span style="color: #000080;">return</span> 1;
    }
    <span style="color: #000080;">else</span> {
        <span style="color: #000080;">return</span> 0;
    }
}

<span style="color: #000080;">sub</span> <span style="color: #0000ff;">_build_values</span> {
    <span style="color: #000080;">my</span> ( <span style="color: #00008b;">$file</span>, <span style="color: #00008b;">$sheet_n</span>, <span style="color: #00008b;">$col</span> ) = <span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@_</span>;

    <span style="color: #000080;">my</span> <span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@values</span>;
    <span style="color: #000080;">my</span> <span style="color: #00008b;">$data</span>  = ReadData(<span style="color: #00008b;">$file</span>);
    <span style="color: #000080;">my</span> <span style="color: #00008b;">$sheet</span> = <span style="color: #00008b;">$data</span>-&gt;[<span style="color: #00008b;">$sheet_n</span>];

    <span style="color: #000080;">for</span> <span style="color: #000080;">my</span> <span style="color: #00008b;">$row</span> ( 1 .. <span style="color: #00008b;">$sheet</span>-&gt;{<span style="color: #483d8b; font-style: italic;">maxrow</span>} ) {
        <span style="color: #66cd00;">push</span> <span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@values</span>, <span style="color: #00008b;">$sheet</span>-&gt;{<span style="color: #483d8b; font-style: italic;">cell</span>}[<span style="color: #00008b;">$col</span>][<span style="color: #00008b;">$row</span>];
    }

    <span style="color: #000080;">return</span> \<span style="color: #0000ff; background-color: #eee9e9; font-weight: bold;">@values</span>;
}

<span style="color: #000080;">sub</span> <span style="color: #0000ff;">_save_groups</span> {
    <span style="color: #000080;">my</span> <span style="color: #00008b;">$output</span> = <span style="color: #66cd00;">shift</span>;
    <span style="color: #000080;">my</span> <span style="color: #00008b;">$groups</span> = <span style="color: #66cd00;">shift</span>;

    <span style="color: #000080;">my</span> <span style="color: #00008b;">$sheetname</span> = <span style="color: #483d8b; font-style: italic;">'001'</span>;

    <span style="color: #000080;">my</span> <span style="color: #00008b;">$xls</span> = Spreadsheet::Write-&gt;new(
        <span style="color: #483d8b; font-style: italic;">file</span>   =&gt; <span style="color: #00008b;">$output</span>,
        <span style="color: #483d8b; font-style: italic;">format</span> =&gt; <span style="color: #483d8b; font-style: italic;">'xls'</span>,
        <span style="color: #483d8b; font-style: italic;">sheet</span>  =&gt; <span style="color: #00008b;">$sheetname</span>++,
    );

    <span style="color: #000080;">foreach</span> <span style="color: #000080;">my</span> <span style="color: #00008b;">$group</span> ( @{<span style="color: #00008b;">$groups</span>} ) {
        <span style="color: #000080;">foreach</span> <span style="color: #000080;">my</span> <span style="color: #00008b;">$row</span> ( @{<span style="color: #00008b;">$group</span>} ) {
            <span style="color: #00008b;">$xls</span>-&gt;addrow(<span style="color: #00008b;">$row</span>);
        }
        <span style="color: #00008b;">$xls</span>-&gt;addsheet( <span style="color: #483d8b; font-style: italic;">'group_'</span> . <span style="color: #00008b;">$sheetname</span>++ );
    }
}
</pre>




<p>
Ce script en ligne de commande accepte les arguments suivants : 
</p><ul>
<li>
<code>input</code> : cela permet de donner le nom du fichier Excel (ou n'importe quel
format géré par <a href="http://search.cpan.org/dist/Spreadsheet-Read/">Spreadsheet::Read</a>, donc OpenOffice.org Calc et CSV viennent
s'ajouter à la liste) ;
</li>
<li>
<code>output</code> : cela permet de spécifier le nom du fichier Excel qui contiendra
les groupes. J'ai pris la convention de créer autant de feuilles de calcul
qu'il n'y a de groupes, et de mettre les candidats doublons dans une feuille
de calcul ;
</li>
<li>
<code>sheet</code> : cela permet de spécifier le numéro de la feuille de calcul
contenant les données à analyser ;
</li>
<li>
<code>column</code> : cela permet de spécifier le numéro de la colonne contenant les
données à analyser.
</li>
<li>
<code>threshold</code> : il s'agit du seuil de similitude à partir duquel
<a href="http://search.cpan.org/dist/String-Similarity-Group/">String::Similarity::Group</a> commence à regrouper les élements. C'est donc à
partir de ce paramètre que nous allons pouvoir agir sur la taille des
mailles de notre filet. 

</li>
</ul>

<p>Le reste du script est juste le liant permettant de faire travailler ces
différents modules ensemble. 
</p>
<p>
Voilà, j'ai donc maintenant à ma disposition un outil permettant de mettre en
évidence des candidats doublons, il faut évidement encore choisir soi-même
quelle est la forme correcte, mais la partie consistant à analyser les données
a été facilité et n'a pris que quelques minutes. 
</p>]]>
    </content>
</entry>

</feed>

