Fedora-Fr - Communauté francophone Fedora - Linux

Planet de Fedora-Fr : la communauté francophone autour de la distribution Linux Fedora

A propos

Cette page est actualisée toutes les heures.

Cette page est une sélection de blogs autour de Fedora. Fedora-Fr.org décline toute responsabilité au sujet des propos tenus par les auteurs des blogs de ce planet. Leurs propos sont leur entière responsabilité.

Le contenu de ce planet appartient à leurs auteurs respectifs. Merci de consulter leur blogs pour obtenir les licences respectives.

Mot-clefs : Développement

QtScript : exemple d'utilisation

Fabien Nicoleau

J'ai essayé dans mon billet précédent de démystifier QtScript et de montrer au travers de différents exemples quelques unes des possibilités offertes par ce module de la bibliothèque Qt. Après ces premiers pas, je vous propose ici d'utiliser QtScript dans un exemple concret. Il s'agira de créer une calculatrice pour laquelle chaque type d'opération possible sera un plugin développé en JavaScript. Il sera ainsi possible "d'étendre" cette calculatrice comme bon vous semble en codant de nouveaux types d'opérations. Cependant je m'en tiendrai dans cet exemple aux simples addition, soustraction, multiplication et division. J'ai appelé ce petit projet QtExCalc pour Qt Extendable Calculator.

Le projet

Pour commencer, assurez vous que le package qt-devel est installé sur votre poste. Si ce n'est pas le cas, sous Fedora, tapez :

# yum install qt-devel

Il faudra ensuite télécharger les sources du projet, dont vous trouverez l'archive jointe à ce billet. Une fois cette dernière décompressée, déplacez vous dans le dossier qtexcalc puis tapez :

$ qmake-qt4
$ make

Le projet est alors compilé. Vous pouvez l'exécuter :

$ ./qtexcalc

L'application se lance :

capture1.jpg

Elle est très simple. Choisissez un type d'opération proposé dans la liste, saisissez deux nombre, puis cliquez sur le bouton pour afficher le résultat. Tout l'intérêt de ce programme est donc qu'il est modulable, grâce à des scripts JS. A son lancement, elle charge des plugins placés dans le sous-répertoire plugins. Chaque plugin fourni trois informations :

  • Le libellé de son opération, qui sera affiché dans la liste ("Addition" par exemple)
  • Le signe de son opération, qui sera affiché sur le bouton ("+" par exemple)
  • Une procédure de traitement, prenant deux chiffres en paramètre, et renvoyant un résultat. Le traitement effectué est évidemment propre à chaque plugin

Le code

Les sources sont composées d'une classe Dialog comprennant la fenêtre principale, ainsi que les différents traitements associés aux actions de l'utilisateur, une classe Plugin, dont chaque instance "représentera" un des plugins, et qui sera chargée de dialoguer avec. Enfin, un fichier main, simplement chargé de créer la fenêtre principale et de l'afficher. Passons au code.

main.cpp

C'est le seul fichier dont je copierai intégralement le contenu ici, tant il est petit :

#include <QtGui/QApplication>
#include "dialog.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Dialog w;
    w.show();
    return a.exec();
}

Comme expliqué plus tôt, on se contente ici d'afficher la fenêtre du projet.

Classe Plugin

Cette classe a en charge de dialoguer avec le plugin. Elle contient trois méthodes dont voici l'implémentation :

Plugin::Plugin(QScriptEngine* eng)
{
    this->engine = eng;
    this->sign =this->engine->globalObject().property("getOperationSign").call(QScriptValue()).toString() ;
}

QString Plugin::getSign() {
    return this->sign;
}

double Plugin::compute(double num1, double num2) {
    return this->engine->globalObject().property("compute").call(QScriptValue(),QScriptValueList() << num1<<num2).toNumber();
}

Le constructeur prend en paramètre un pointeur sur un moteur de script, qui permettra de dialoguer avec le plugin, et récupère ensuite le signe de l'opération du plugin en appelant sa fonction getOperationSign()

La méthode getSign() renvoie le signe de l'opération du plugin qui a été stocké dans le constructeur.

Enfin, la méthode compute() se charge d'effectuer l'opération. Elle attend deux paramètres qui seront passés à la fonction compute() du plugin. Elle renvoie ensuite le résultat de l'opération.

Classe Dialog

Voici la principale classe du projet. Je laisse de coté ici tout ce qui concerne la gui, pour ne se concentrer que sur QtScript. Commençons par la méthode loadPlugins() qui est appelée lors de la construction de l'objet et qui permet de "charger" les différents plugins :

void Dialog::loadPlugins() {
    QDir dirPlugins("plugins");
    QString fName;
    QScriptEngine * engine;
    QComboBox * cbPlugins = this->ui->cbChoosePlugin;
    cbPlugins->addItem(QString::fromUtf8 ("Choisissez une opération ..."),"");
    foreach(fName,dirPlugins.entryList(QStringList() << "*.qs")) {
        engine = new QScriptEngine();
        QFile file("plugins/"+fName);
        file.open(QIODevice::ReadOnly);
        engine->evaluate(file.readAll());
        file.close();
        cbPlugins->addItem(engine->globalObject().property("getOperationName").call(QScriptValue()).toString(),fName);
        this->plugins[fName] = new Plugin(engine) ;
    }
}

Tous les fichiers dont l'extension est qs (pour QtScript, mais on peut bien sûr utiliser ce qu'on veut) sont listés grâce à la méthode entryList(). Pour chacun des fichiers, on créé un moteur de script puis on évalue le contenu. Ensuite, on ajoute à la liste (addItem()) le nom de l'opération, en appelant la fonction getOperationName du plugin, et on donne le nom du fichier (fName) comme data. Enfin, on construit un nouvel objet Plugin auquel on passe en paramètre le moteur et que l'on stocke dans une QMap, avec le nom de fichier (fName toujours) comme clé. Ainsi, lorsqu'une opération sera choisie dans la liste, on récupèrera le data (correspondant au nom de fichier) grâce auquel on pourra accéder à l'objet correspondant de la QMap.

Passons maintenant au slot choosedPlugin appelé à chaque fois que l'on sélectionne un item dans la liste des opérations :

void Dialog::choosedPlugin(int index) {
    if(index>0) {
        this->ui->bProcess->setText(this->plugins[this->ui->cbChoosePlugin->itemData(index).toString()]->getSign());
        this->ui->bProcess->setEnabled(true);
    }
    else {
        this->ui->bProcess->setText("");
        this->ui->bProcess->setEnabled(false);
    }
}

On vérifie tout d'abord que l'on a pas sélectionné le premier élément, qui est celui demandant de choisir une opération. Si on a bien sélectionné une opération, on récupère la donnée de l'élément (itemData()) sélectionné, qui nous permet d'accéder à l'objet Plugin correspondant, depuis lequel on appelle la méthode getSign(). Le signe de l'opération est appliqué au bouton, qui est ensuite activé.

Passons au dernier slot, compute, appelé lorsque l'utilisateur clique sur le bouton :

void Dialog::compute() {
    if(!this->ui->leNumber1->text().isEmpty() && ! this->ui->leNumber2->text().isEmpty() ) {
        Plugin * selectedPlugin = this->plugins[this->ui->cbChoosePlugin->itemData(this->ui->cbChoosePlugin->currentIndex()).toString()];
        this->ui->lResult->setText(QString::number(selectedPlugin->compute(this->ui->leNumber1->text().toDouble(),this->ui->leNumber2->text().toDouble())));
    }
}

Si les deux champs de saisie ont été remplis, on récupère l'objet Plugin de l'opération sélectionnée. On appelle la méthode compute() du Plugin, en lui passant en paramètre la valeur des champs de saisie, qui nous retourne le résultat de l'opération, résultat que l'on affiche dans le libellé correspondant.

Voici pour terminer, le code du plugin "addition", je ne donne pas le code des 3 autres, qui se devine facilement, et qui est dispo dans les sources :

function getOperationName() {
    return "Addition";
}

function getOperationSign() {
    return "+";
}

function compute(num1,num2) {
    return num1+num2;
}

On y voit donc les trois fonctions nécessaires, donnant le libellé de l'opération, son signe, et enfin le résultat de l'opération.

Voilà pour l'explication de ce petit projet, qui j'espère vous permettra de voir de quelle façon on peut facilement avec Qt gérer des plugins écrits en JavaScript. Si vous avez des questions sur le code, n'hésitez pas à les poser dans les commentaires.


Bon dev'

Fabien (eponyme)

QtScript : exemple d'utilisation

Fabien Nicoleau

J'ai essayé dans mon billet précédent de démystifier QtScript et de montrer au travers de différents exemples quelques unes des possibilités offertes par ce module de la bibliothèque Qt. Après ces premiers pas, je vous propose ici d'utiliser QtScript dans un exemple concret. Il s'agira de créer une calculatrice pour laquelle chaque type d'opération possible sera un plugin développé en JavaScript. Il sera ainsi possible "d'étendre" cette calculatrice comme bon vous semble en codant de nouveaux types d'opérations. Cependant je m'en tiendrai dans cet exemple aux simples addition, soustraction, multiplication et division. J'ai appelé ce petit projet QtExCalc pour Qt Extendable Calculator.

Le projet

Pour commencer, assurez vous que le package qt-devel est installé sur votre poste. Si ce n'est pas le cas, sous Fedora, tapez :

# yum install qt-devel

Il faudra ensuite télécharger les sources du projet, dont vous trouverez l'archive jointe à ce billet. Une fois cette dernière décompressée, déplacez vous dans le dossier qtexcalc puis tapez :

$ qmake-qt4
$ make

Le projet est alors compilé. Vous pouvez l'exécuter :

$ ./qtexcalc

L'application se lance :

capture1.jpg

Elle est très simple. Choisissez un type d'opération proposé dans la liste, saisissez deux nombre, puis cliquez sur le bouton pour afficher le résultat. Tout l'intérêt de ce programme est donc qu'il est modulable, grâce à des scripts JS. A son lancement, elle charge des plugins placés dans le sous-répertoire plugins. Chaque plugin fourni trois informations :

  • Le libellé de son opération, qui sera affiché dans la liste ("Addition" par exemple)
  • Le signe de son opération, qui sera affiché sur le bouton ("+" par exemple)
  • Une procédure de traitement, prenant deux chiffres en paramètre, et renvoyant un résultat. Le traitement effectué est évidemment propre à chaque plugin

Le code

Les sources sont composées d'une classe Dialog comprennant la fenêtre principale, ainsi que les différents traitements associés aux actions de l'utilisateur, une classe Plugin, dont chaque instance "représentera" un des plugins, et qui sera chargée de dialoguer avec. Enfin, un fichier main, simplement chargé de créer la fenêtre principale et de l'afficher. Passons au code.

main.cpp

C'est le seul fichier dont je copierai intégralement le contenu ici, tant il est petit :

#include <QtGui/QApplication>
#include "dialog.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Dialog w;
    w.show();
    return a.exec();
}

Comme expliqué plus tôt, on se contente ici d'afficher la fenêtre du projet.

Classe Plugin

Cette classe a en charge de dialoguer avec le plugin. Elle contient trois méthodes dont voici l'implémentation :

Plugin::Plugin(QScriptEngine* eng)
{
    this->engine = eng;
    this->sign =this->engine->globalObject().property("getOperationSign").call(QScriptValue()).toString() ;
}

QString Plugin::getSign() {
    return this->sign;
}

double Plugin::compute(double num1, double num2) {
    return this->engine->globalObject().property("compute").call(QScriptValue(),QScriptValueList() << num1<<num2).toNumber();
}

Le constructeur prend en paramètre un pointeur sur un moteur de script, qui permettra de dialoguer avec le plugin, et récupère ensuite le signe de l'opération du plugin en appelant sa fonction getOperationSign()

La méthode getSign() renvoie le signe de l'opération du plugin qui a été stocké dans le constructeur.

Enfin, la méthode compute() se charge d'effectuer l'opération. Elle attend deux paramètres qui seront passés à la fonction compute() du plugin. Elle renvoie ensuite le résultat de l'opération.

Classe Dialog

Voici la principale classe du projet. Je laisse de coté ici tout ce qui concerne la gui, pour ne se concentrer que sur QtScript. Commençons par la méthode loadPlugins() qui est appelée lors de la construction de l'objet et qui permet de "charger" les différents plugins :

void Dialog::loadPlugins() {
    QDir dirPlugins("plugins");
    QString fName;
    QScriptEngine * engine;
    QComboBox * cbPlugins = this->ui->cbChoosePlugin;
    cbPlugins->addItem(QString::fromUtf8 ("Choisissez une opération ..."),"");
    foreach(fName,dirPlugins.entryList(QStringList() << "*.qs")) {
        engine = new QScriptEngine();
        QFile file("plugins/"+fName);
        file.open(QIODevice::ReadOnly);
        engine->evaluate(file.readAll());
        file.close();
        cbPlugins->addItem(engine->globalObject().property("getOperationName").call(QScriptValue()).toString(),fName);
        this->plugins[fName] = new Plugin(engine) ;
    }
}

Tous les fichiers dont l'extension est qs (pour QtScript, mais on peut bien sûr utiliser ce qu'on veut) sont listés grâce à la méthode entryList(). Pour chacun des fichiers, on créé un moteur de script puis on évalue le contenu. Ensuite, on ajoute à la liste (addItem()) le nom de l'opération, en appelant la fonction getOperationName du plugin, et on donne le nom du fichier (fName) comme data. Enfin, on construit un nouvel objet Plugin auquel on passe en paramètre le moteur et que l'on stocke dans une QMap, avec le nom de fichier (fName toujours) comme clé. Ainsi, lorsqu'une opération sera choisie dans la liste, on récupèrera le data (correspondant au nom de fichier) grâce auquel on pourra accéder à l'objet correspondant de la QMap.

Passons maintenant au slot choosedPlugin appelé à chaque fois que l'on sélectionne un item dans la liste des opérations :

void Dialog::choosedPlugin(int index) {
    if(index>0) {
        this->ui->bProcess->setText(this->plugins[this->ui->cbChoosePlugin->itemData(index).toString()]->getSign());
        this->ui->bProcess->setEnabled(true);
    }
    else {
        this->ui->bProcess->setText("");
        this->ui->bProcess->setEnabled(false);
    }
}

On vérifie tout d'abord que l'on a pas sélectionné le premier élément, qui est celui demandant de choisir une opération. Si on a bien sélectionné une opération, on récupère la donnée de l'élément (itemData()) sélectionné, qui nous permet d'accéder à l'objet Plugin correspondant, depuis lequel on appelle la méthode getSign(). Le signe de l'opération est appliqué au bouton, qui est ensuite activé.

Passons au dernier slot, compute, appelé lorsque l'utilisateur clique sur le bouton :

void Dialog::compute() {
    if(!this->ui->leNumber1->text().isEmpty() && ! this->ui->leNumber2->text().isEmpty() ) {
        Plugin * selectedPlugin = this->plugins[this->ui->cbChoosePlugin->itemData(this->ui->cbChoosePlugin->currentIndex()).toString()];
        this->ui->lResult->setText(QString::number(selectedPlugin->compute(this->ui->leNumber1->text().toDouble(),this->ui->leNumber2->text().toDouble())));
    }
}

Si les deux champs de saisie ont été remplis, on récupère l'objet Plugin de l'opération sélectionnée. On appelle la méthode compute() du Plugin, en lui passant en paramètre la valeur des champs de saisie, qui nous retourne le résultat de l'opération, résultat que l'on affiche dans le libellé correspondant.

Voici pour terminer, le code du plugin "addition", je ne donne pas le code des 3 autres, qui se devine facilement, et qui est dispo dans les sources :

function getOperationName() {
    return "Addition";
}

function getOperationSign() {
    return "+";
}

function compute(num1,num2) {
    return num1+num2;
}

On y voit donc les trois fonctions nécessaires, donnant le libellé de l'opération, son signe, et enfin le résultat de l'opération.

Voilà pour l'explication de ce petit projet, qui j'espère vous permettra de voir de quelle façon on peut facilement avec Qt gérer des plugins écrits en JavaScript. Si vous avez des questions sur le code, n'hésitez pas à les poser dans les commentaires.


Bon dev'

Fabien (eponyme)

Premiers pas avec QtScript

Fabien Nicoleau

Dans un précédent billet, j'avais parlé de la possibilité d'intégrer très facilement dans vos applications Java des scripts écrits en JavaScript, et de la facilité avec laquelle vos applications peuvent interagir avec les scripts. J'avais ensuite proposé un exemple d'utilisation avec un robot IRC fonctionnant avec des plugins écrits en JavaScript.

La bibliothèque Qt permet elle aussi d'intégrer des scripts JavaScript dans vos applications. Je propose ici de découvrir comment le faire, à travers quelques exemples simples.

Avant de commencer

Avant d'entrer dans le vif du sujet, préparons d'abord le terrain. Je proposerai pour chaque exemple le code dans ce billet, mais je vous conseille, si vous voulez tester, de télécharger l'archive jointe à ce billet contenant les sources des exemples. Une fois décompressée, déplacez vous dans le dossier qtscripting, puis ouvrez une console, et tapez :

qmake-qt4
make

Tous les exemples sont alors compilés. Bien sûr, pour que la compilation fonctionne, ils vous faut installer le paquet qt-devel. Sous Fedora, tapez :

# yum install qt-devel

Une fois les exemples compilés, vous pouvez vous déplacer dans les dossiers, et lancer les exécutables.

Notez que pour ajouter le support des scripts au projet, il a simplement suffit d'ajouter script à la directive QT (voir le QT += script dans le fichier common.pri), et d'include QtScript dans les sources.

Exemple 1 : 1 + 1

Pour le premier exemple, faisons simple ! Il s'agit ici simplement de voir comment exécuter un code JavaScript depuis du code C++, et de récupérer le résultat.

On commence direct avec le code :

#include <QtScript>
#include <QApplication>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QScriptEngine engine;
    QScriptValue resultat = engine.evaluate("1+1");
    qDebug() << resultat.toString() ;
    return 0;
}

Rien de complexe. On créé d'abord un objet de type QScriptEngine, qui sera notre moteur de script, indispensable. On demande ensuite à notre moteur d'évaluer le code "1+1". Le résultat de l'évaluation est stocké dans un objet de type QScriptValue. Enfin on affiche le résultat. En parcourant la doc sur QScriptValue, vous pourrez vous rendre compte qu'il est possible d'effectuer de nombreuses vérifications afin de s'assurer que le résultat est exploitable, notamment en contrôlant son type.

Voilà pour un début, il n'y a rien d'exceptionnel bien sûr, mais les base sont posées : notre programme a affiché le résultat d'un code externe (en l'occurrence du JavaScript, même si ce n'est qu'une addition, et que le code est "en dur" dans le programme). Pour les plus sceptiques, j'affiche la sortie du programme :

[eponyme@localhost exemple1]$ ./exemple1
"2"
[eponyme@localhost exemple1]$

Surpris ? :D

Passons à la suite.

Exemple 2 : appel d'une fonction

Nous allons dans cette exemple appeler une fonction et lui passer un paramètre, puis afficher ce qu'elle nous renvoie. Le code :

#include <QtScript>
#include <QApplication>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QScriptEngine engine;
    engine.evaluate("function disMoiBonjour(prenom) { return \"Bonjour \"+prenom; }");    
    QScriptValue disBonjour = engine.globalObject().property("disMoiBonjour");
    qDebug() << disBonjour.call(QScriptValue(),QScriptValueList() << "Fabien").toString() ;
    return 0;
}

Comme dans le premier exemple, on créé un moteur, mais cette fois ci au lieu de lui faire évaluer une addition, nous demandons d'évaluer la définition d'une fonction, appelée disMoiBonjour. Cette fonction retourne une phrase "disant bonjour" à la personne dont le prénom est passé en paramètre. La ligne suivante nous permet de récupérer parmi les objets du script (globalObject())  notre fonction disMoiBonjour (property()), conservée dans la variable disBonjour. Il ne nous reste plus qu'à l'appeler grâce à la méthode call(). Notez le second paramètre, qui est une liste de valeurs qui sont passées en arguments à la fonction appelée. Ici, on envoie juste la chaîne "Fabien".

Voici la sortie du programme :

[eponyme@localhost exemple2]$ ./exemple2
"Bonjour Fabien"
[eponyme@localhost exemple2]$

Cet article ayant juste pour but de découvrir des choses simples et basiques possibles avec QtScript, je ne rentre pas plus dans les le détail concernant les différentes méthodes utilisées ici, mais la documentation vous fera découvrir que l'on peut aller beaucoup plus loin.

Au passage, on voit déjà que du code JavaScript dans une chaine de caractères, ça n'est pas très pratique. Vivement qu'on puisse le mettre dans un fichier !

Exemple 3 : code JS dans un fichier et objet global

Pour le moment, nos scripts étaient "en dur" dans le code C++, ce qui n'a que très peu (aucun ?) intérêt. Voyons ici comment externaliser ce code et le placer dans un fichier. La méthode evaluate() prenant en paramètre une chaine de caractères, il sera très simple (surtout avec Qt) de récupérer le contenu d'un fichier texte pour le faire évaluer. Je profite aussi de cet exemple pour montrer comment exporter depuis le code C++ un objet global dans le script. Comme d'habitude, on attaque directement avec le code :

#include <QtScript>
#include <QApplication>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QScriptEngine engine;
    QFile file("script.qs");
    file.open(QIODevice::ReadOnly);
    engine.globalObject().setProperty("name","Fabien");
    engine.evaluate(file.readAll());   
    file.close();
    return 0;
}

Comme on pouvait s'y attendre, la chose est très simple à réaliser grâce à l'objet QFile. On ouvre en lecture seule un fichier nommé script.qs, et on passe son contenu à la méthode evaluate() via la méthode readAll(). Le contenu du fichier est ainsi entièrement évalué. L'autre nouveauté ici est que l'on "envoie" dans le script l'objet "name", dont la valeur est "Fabien", avec la méthode setProperty(). Ainsi, à n'importe quel endroit du script, il sera possible d'accéder à cet objet.

Voyons d'ailleurs le contenu du script (fichier script.qs) :

print('Bonjour '+name);

Ce petit script se contentera donc d'afficher dans la console un bonjour à la personne dont le prénom est stocké dans l'objet name (et assigné depuis le code C++).

Voyons ce que ca donne à l'exécution :

[eponyme@localhost exemple3]$ ./exemple3
Bonjour Fabien
[eponyme@localhost exemple3]$

La encore, pas de grande surprise ! Mais désormais, vous pouvez stocker votre code JS dans un fichier, ce qui ouvre un grand nombre de possibilités, notamment celui de stocker différents scripts afin de charger différentes fonctions, pour un système de plugins.

Exemple 4 : envoi d'un QObject dans le script

L'exemple 3 nous a montré qu'il est possible d'envoyer un objet avec une valeur dans le script, et ce depuis le code C++. Cependant cet objet ne peut prendre qu'une valeur simple. Nous allons voir ici comment envoyer dans le script un QObject (et donc un de ses objets hérités, ce qui en fait beaucoup).

Pour illustrer cet exemple, je vais utiliser un QLabel, habituellement utilisé pour afficher un libellé dans une interface graphique. Voici le code :

#include <QtScript>
#include <QApplication>
#include <QLabel>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QLabel lbl;
    QScriptEngine engine;
    QFile file("script.qs");
    file.open(QIODevice::ReadOnly);
    lbl.setText("UN");
    QScriptValue object = engine.newQObject(&lbl);
    engine.globalObject().setProperty("label",object);   
    engine.evaluate(file.readAll());
    file.close();
    qDebug() <<"Dans le code : " <<  lbl.text();
    return 0;
}

Le QLabel ne sera pas utilisé ici dans une interface, mais on utilise ses méthodes pour le manipuler. Après l'habituelle création du moteur, on créé donc un objet QLabel, pour lequel on modifie le libellé en le passant à "UN", grâce au slot setText(). Ensuite, la méthode newQObject() du moteur est appelée, prenant en paramètre un pointeur sur notre objet de type QLabel. C'est la valeur renvoyée par newQObject qui est utilisée pour le setProperty() de la ligne suivante, déjà utilisé dans l'exemple 3. Ainsi, dans notre script, on pourra utiliser notre objet QLabel n'importe quand grâce à la variable label, dont le nom est passé en premier argument à setProperty(). Comme dans les autres exemples, on évalue le script, puis on ferme le fichier. Enfin, on affiche la valeur de notre QLabel grâce à sa méthode text(). En toute logique, elle devrait renvoyer "UN", à moins que la valeur ait été changée depuis le script ... Voyons justement le code de ce dernier (fichier script.qs) :

print("Dans le script : "+label.text);
label.setText("DEUX");

Deux lignes, très simples. La première affiche la valeur du QLabel accessible grâce à la variable label. La valeur du QLabel est obtenue grâce à la propriété text de ce dernier. Sur la seconde ligne, on change la valeur du QLabel à "DEUX", grâce à setText().

Voyons ce que donne l'exécution :

[eponyme@localhost exemple4]$ ./exemple4
Dans le script : UN
Dans le code :  "DEUX"
[eponyme@localhost exemple4]$

On voit ici l'évolution de notre QObject ! La première valeur affichée ("UN") depuis le script (avec print()) est celle qui avait été fixée dans le code C++. Après avoir affiché la valeur, le script JS a changé la valeur du QLabel, et depuis le code, la valeur affichée est bien "DEUX".

Nous pouvons donc de façon très simple passer à notre script JS des QObject et lui permettre d'en modifier les propriétés.

Exemple 5 : gestion des erreurs

Lorsque l'on débute, ou lorsque l'on commence à faire des choses compliquées, il arrive souvent que l'on bloque car le code JS n'est pas évalué. Dans la plupart des cas, il s'agit juste d'une erreur de syntaxe. Mais il est parfois difficile de savoir pourquoi est ce que notre script n'est pas évalué, et donc qu'il est impossible d'interagir avec lui ensuite. On peut aussi être parfois sûr de son code, et ne pas penser qu'il y a une erreur. Heureusement il est possible, lorsqu'une erreur est rencontrée, d'afficher un message. Voici un code d'exemple :

#include <QtScript>
#include <QApplication>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QScriptEngine engine;
    QScriptValue result = engine.evaluate("print('Bonjour !'");
    if (result.isError()) {
        qDebug() << result.property("message").toString();
    }
    return 0;
}

Dans ce code, on demande au moteur d'évaluer du JavaScript devant afficher "Bonjour !", mais la parenthèse fermante est volontairement oubliée. On se sert de la valeur renvoyée par l'évaluation pour vérifier s'il y a eu une erreur avec isError(). Si c'est le cas, on peut afficher un message explicatif en récupérant la propriété message du résultat d'évaluation.

Voyons l'exécution du programme :

[eponyme@localhost exemple5]$ ./exemple5
"Parse error"
[eponyme@localhost exemple5]$

Notre code n'est pas évalué, et le moteur nous indique une Parse error. On sait au moins ou chercher. Cette vérification vous permettra de gagner un peu de temps si vous n'obtenez pas le résultat espéré.

Exemple 6 : signaux et slots

Les signaux et slots sont l'un des avantages du développement avec la bibliothèque Qt, et n'ont pas été oubliés avec QtScript. Dans ce dernier exemple, nous allons créer une fenêtre basique comprenant deux boutons. Le premier, appelé "Bonjour", devra afficher "Bonjour !" dans la console lorsque l'on clique dessus, et le second, "Au revoir", devra afficher ... "Au revoir !" (et oui !)  lorsque l'on clique dessus. La particularité est que ces affichages dans la console se feront depuis un script JS, et non pas directement depuis des slots dans le code C++. L'intérêt ? Ici, aucun. Il s'agira simplement de montrer que l'on peut connecter un signal à un slot dont l'implémentation est faite dans un script JS, ce qui permet de personnaliser facilement une application en changeant le comportement du slot dans les scripts. Pour l'exemple, nous verrons deux façons différentes de connecter un signal à un slot implémenté dans un script, la première sera faite depuis le code C++, pour le bouton "Bonjour", et la seconde directement dans le script JS, pour le bouton "Au revoir".

Passons au code :

#include <QtScript>
#include <QApplication>
#include <QtGui>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QWidget fenetre;
    QVBoxLayout vbox(&fenetre);
    QPushButton bBonjour("Bonjour");
    QPushButton bAuRevoir("Au revoir");
    vbox.addWidget(&bBonjour);
    vbox.addWidget(&bAuRevoir);

    QScriptEngine engine;
    QFile file("script.qs");
    file.open(QIODevice::ReadOnly);
    QScriptValue objBonjour = engine.newQObject(&bBonjour);
    QScriptValue objAuRevoir = engine.newQObject(&bAuRevoir);
    engine.globalObject().setProperty("objAuRevoir",objAuRevoir);
    engine.evaluate(file.readAll());
    file.close();
    QScriptValue slot = engine.globalObject().property("bonjourHandler");
    qScriptConnect(&bBonjour, SIGNAL(clicked()), objBonjour, slot);

    fenetre.show();
    return app.exec();
}

C'est un peu plus long que d'habitude, certes, mais le premier pavé ne concerne que l'interface graphique, ce qui n'est pas le sujet de ce billet. On y créé les deux boutons et on les place sur la fenêtre.

Ensuite, comme d'habitude, on créé un moteur, qui évaluera du code JS placé dans un fichier. Deux "objets QtScript" sont créés à partir des deux boutons, grâce à newQObject() : objBonjour et objAuRevoir. Ils nous serviront à réaliser les connexions avec les slots du script. L'objet objAuRevoir est exporté dans le script car nous en aurons besoin pour réaliser la connexion au slot

Une fois l'évaluation du script faite, on récupère la propriété bonjourHander, qui est une fonction dans le script JS, que l'on stocke dans la variable slot. Cette variable est utilisée ensuite pour réaliser la connexion entre le signal clicked du bouton "Bonjour" et le slot bonjourHandler, grâce à la fonction qScriptConnect().

Voyons maintenant le contenu du script (fichier script.qs) :

function bonjourHandler () {
    print('Bonjour !');
}
function auRevoirHandler () {
    print('Au revoir !');
}
objAuRevoir.clicked.connect(this.auRevoirHandler);

On y trouve les deux fonctions qui seront appelées lors des clics sur les boutons. bonjourHandler est déjà connectée au bouton "Bonjour" via l'appel à qScriptConnect() dans le code C++. La fonction auRevoirHandler est elle connectée au bouton "Au revoir" à la dernière ligne du script, qui utilise l'objet objAuRevoir exporté dans le programme. Ainsi nos deux fonctions sont connectées à leur bouton respectif, l'une depuis le code C++, l'autre depuis le script JS. A vous de voir ce que vous préférez !

Une petite image pour l'exécution du programme :

tuto_qtscript_exemple6.jpg


C'est sur cet exemple que ce termine cette initiation. Je n'ai pas la prétention avec cet article de donner une vue d'ensemble des possibilités offertes par QtScript. J'en suis d'ailleurs encore au stade de la découverte. Mais ces exemples pourrons vous permettre de vous lancer, et de démystifier un peu les choses ! Je proposerai dans un prochain article un exemple concret d'utilisation de QtScript pour un système de plugins.

Hormis les liens disséminés un peu partout dans l'article, les deux sources qui m'ont le plus aidé sont :

Bon dev' !


Fabien (eponyme)

Premiers pas avec QtScript

Fabien Nicoleau

Dans un précédent billet, j'avais parlé de la possibilité d'intégrer très facilement dans vos applications Java des scripts écrits en JavaScript, et de la facilité avec laquelle vos applications peuvent interagir avec les scripts. J'avais ensuite proposé un exemple d'utilisation avec un robot IRC fonctionnant avec des plugins écrits en JavaScript.

La bibliothèque Qt permet elle aussi d'intégrer des scripts JavaScript dans vos applications. Je propose ici de découvrir comment le faire, à travers quelques exemples simples.

Avant de commencer

Avant d'entrer dans le vif du sujet, préparons d'abord le terrain. Je proposerai pour chaque exemple le code dans ce billet, mais je vous conseille, si vous voulez tester, de télécharger l'archive jointe à ce billet contenant les sources des exemples. Une fois décompressée, déplacez vous dans le dossier qtscripting, puis ouvrez une console, et tapez :

qmake-qt4
make

Tous les exemples sont alors compilés. Bien sûr, pour que la compilation fonctionne, ils vous faut installer le paquet qt-devel. Sous Fedora, tapez :

# yum install qt-devel

Une fois les exemples compilés, vous pouvez vous déplacer dans les dossiers, et lancer les exécutables.

Notez que pour ajouter le support des scripts au projet, il a simplement suffit d'ajouter script à la directive QT (voir le QT += script dans le fichier common.pri), et d'include QtScript dans les sources.

Exemple 1 : 1 + 1

Pour le premier exemple, faisons simple ! Il s'agit ici simplement de voir comment exécuter un code JavaScript depuis du code C++, et de récupérer le résultat.

On commence direct avec le code :

#include <QtScript>
#include <QApplication>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QScriptEngine engine;
    QScriptValue resultat = engine.evaluate("1+1");
    qDebug() << resultat.toString() ;
    return 0;
}

Rien de complexe. On créé d'abord un objet de type QScriptEngine, qui sera notre moteur de script, indispensable. On demande ensuite à notre moteur d'évaluer le code "1+1". Le résultat de l'évaluation est stocké dans un objet de type QScriptValue. Enfin on affiche le résultat. En parcourant la doc sur QScriptValue, vous pourrez vous rendre compte qu'il est possible d'effectuer de nombreuses vérifications afin de s'assurer que le résultat est exploitable, notamment en contrôlant son type.

Voilà pour un début, il n'y a rien d'exceptionnel bien sûr, mais les base sont posées : notre programme a affiché le résultat d'un code externe (en l'occurrence du JavaScript, même si ce n'est qu'une addition, et que le code est "en dur" dans le programme). Pour les plus sceptiques, j'affiche la sortie du programme :

[eponyme@localhost exemple1]$ ./exemple1
"2"
[eponyme@localhost exemple1]$

Surpris ? :D

Passons à la suite.

Exemple 2 : appel d'une fonction

Nous allons dans cette exemple appeler une fonction et lui passer un paramètre, puis afficher ce qu'elle nous renvoie. Le code :

#include <QtScript>
#include <QApplication>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QScriptEngine engine;
    engine.evaluate("function disMoiBonjour(prenom) { return \"Bonjour \"+prenom; }");    
    QScriptValue disBonjour = engine.globalObject().property("disMoiBonjour");
    qDebug() << disBonjour.call(QScriptValue(),QScriptValueList() << "Fabien").toString() ;
    return 0;
}

Comme dans le premier exemple, on créé un moteur, mais cette fois ci au lieu de lui faire évaluer une addition, nous demandons d'évaluer la définition d'une fonction, appelée disMoiBonjour. Cette fonction retourne une phrase "disant bonjour" à la personne dont le prénom est passé en paramètre. La ligne suivante nous permet de récupérer parmi les objets du script (globalObject())  notre fonction disMoiBonjour (property()), conservée dans la variable disBonjour. Il ne nous reste plus qu'à l'appeler grâce à la méthode call(). Notez le second paramètre, qui est une liste de valeurs qui sont passées en arguments à la fonction appelée. Ici, on envoie juste la chaîne "Fabien".

Voici la sortie du programme :

[eponyme@localhost exemple2]$ ./exemple2
"Bonjour Fabien"
[eponyme@localhost exemple2]$

Cet article ayant juste pour but de découvrir des choses simples et basiques possibles avec QtScript, je ne rentre pas plus dans les le détail concernant les différentes méthodes utilisées ici, mais la documentation vous fera découvrir que l'on peut aller beaucoup plus loin.

Au passage, on voit déjà que du code JavaScript dans une chaine de caractères, ça n'est pas très pratique. Vivement qu'on puisse le mettre dans un fichier !

Exemple 3 : code JS dans un fichier et objet global

Pour le moment, nos scripts étaient "en dur" dans le code C++, ce qui n'a que très peu (aucun ?) intérêt. Voyons ici comment externaliser ce code et le placer dans un fichier. La méthode evaluate() prenant en paramètre une chaine de caractères, il sera très simple (surtout avec Qt) de récupérer le contenu d'un fichier texte pour le faire évaluer. Je profite aussi de cet exemple pour montrer comment exporter depuis le code C++ un objet global dans le script. Comme d'habitude, on attaque directement avec le code :

#include <QtScript>
#include <QApplication>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QScriptEngine engine;
    QFile file("script.qs");
    file.open(QIODevice::ReadOnly);
    engine.globalObject().setProperty("name","Fabien");
    engine.evaluate(file.readAll());   
    file.close();
    return 0;
}

Comme on pouvait s'y attendre, la chose est très simple à réaliser grâce à l'objet QFile. On ouvre en lecture seule un fichier nommé script.qs, et on passe son contenu à la méthode evaluate() via la méthode readAll(). Le contenu du fichier est ainsi entièrement évalué. L'autre nouveauté ici est que l'on "envoie" dans le script l'objet "name", dont la valeur est "Fabien", avec la méthode setProperty(). Ainsi, à n'importe quel endroit du script, il sera possible d'accéder à cet objet.

Voyons d'ailleurs le contenu du script (fichier script.qs) :

print('Bonjour '+name);

Ce petit script se contentera donc d'afficher dans la console un bonjour à la personne dont le prénom est stocké dans l'objet name (et assigné depuis le code C++).

Voyons ce que ca donne à l'exécution :

[eponyme@localhost exemple3]$ ./exemple3
Bonjour Fabien
[eponyme@localhost exemple3]$

La encore, pas de grande surprise ! Mais désormais, vous pouvez stocker votre code JS dans un fichier, ce qui ouvre un grand nombre de possibilités, notamment celui de stocker différents scripts afin de charger différentes fonctions, pour un système de plugins.

Exemple 4 : envoi d'un QObject dans le script

L'exemple 3 nous a montré qu'il est possible d'envoyer un objet avec une valeur dans le script, et ce depuis le code C++. Cependant cet objet ne peut prendre qu'une valeur simple. Nous allons voir ici comment envoyer dans le script un QObject (et donc un de ses objets hérités, ce qui en fait beaucoup).

Pour illustrer cet exemple, je vais utiliser un QLabel, habituellement utilisé pour afficher un libellé dans une interface graphique. Voici le code :

#include <QtScript>
#include <QApplication>
#include <QLabel>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QLabel lbl;
    QScriptEngine engine;
    QFile file("script.qs");
    file.open(QIODevice::ReadOnly);
    lbl.setText("UN");
    QScriptValue object = engine.newQObject(&lbl);
    engine.globalObject().setProperty("label",object);   
    engine.evaluate(file.readAll());
    file.close();
    qDebug() <<"Dans le code : " <<  lbl.text();
    return 0;
}

Le QLabel ne sera pas utilisé ici dans une interface, mais on utilise ses méthodes pour le manipuler. Après l'habituelle création du moteur, on créé donc un objet QLabel, pour lequel on modifie le libellé en le passant à "UN", grâce au slot setText(). Ensuite, la méthode newQObject() du moteur est appelée, prenant en paramètre un pointeur sur notre objet de type QLabel. C'est la valeur renvoyée par newQObject qui est utilisée pour le setProperty() de la ligne suivante, déjà utilisé dans l'exemple 3. Ainsi, dans notre script, on pourra utiliser notre objet QLabel n'importe quand grâce à la variable label, dont le nom est passé en premier argument à setProperty(). Comme dans les autres exemples, on évalue le script, puis on ferme le fichier. Enfin, on affiche la valeur de notre QLabel grâce à sa méthode text(). En toute logique, elle devrait renvoyer "UN", à moins que la valeur ait été changée depuis le script ... Voyons justement le code de ce dernier (fichier script.qs) :

print("Dans le script : "+label.text);
label.setText("DEUX");

Deux lignes, très simples. La première affiche la valeur du QLabel accessible grâce à la variable label. La valeur du QLabel est obtenue grâce à la propriété text de ce dernier. Sur la seconde ligne, on change la valeur du QLabel à "DEUX", grâce à setText().

Voyons ce que donne l'exécution :

[eponyme@localhost exemple4]$ ./exemple4
Dans le script : UN
Dans le code :  "DEUX"
[eponyme@localhost exemple4]$

On voit ici l'évolution de notre QObject ! La première valeur affichée ("UN") depuis le script (avec print()) est celle qui avait été fixée dans le code C++. Après avoir affiché la valeur, le script JS a changé la valeur du QLabel, et depuis le code, la valeur affichée est bien "DEUX".

Nous pouvons donc de façon très simple passer à notre script JS des QObject et lui permettre d'en modifier les propriétés.

Exemple 5 : gestion des erreurs

Lorsque l'on débute, ou lorsque l'on commence à faire des choses compliquées, il arrive souvent que l'on bloque car le code JS n'est pas évalué. Dans la plupart des cas, il s'agit juste d'une erreur de syntaxe. Mais il est parfois difficile de savoir pourquoi est ce que notre script n'est pas évalué, et donc qu'il est impossible d'interagir avec lui ensuite. On peut aussi être parfois sûr de son code, et ne pas penser qu'il y a une erreur. Heureusement il est possible, lorsqu'une erreur est rencontrée, d'afficher un message. Voici un code d'exemple :

#include <QtScript>
#include <QApplication>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QScriptEngine engine;
    QScriptValue result = engine.evaluate("print('Bonjour !'");
    if (result.isError()) {
        qDebug() << result.property("message").toString();
    }
    return 0;
}

Dans ce code, on demande au moteur d'évaluer du JavaScript devant afficher "Bonjour !", mais la parenthèse fermante est volontairement oubliée. On se sert de la valeur renvoyée par l'évaluation pour vérifier s'il y a eu une erreur avec isError(). Si c'est le cas, on peut afficher un message explicatif en récupérant la propriété message du résultat d'évaluation.

Voyons l'exécution du programme :

[eponyme@localhost exemple5]$ ./exemple5
"Parse error"
[eponyme@localhost exemple5]$

Notre code n'est pas évalué, et le moteur nous indique une Parse error. On sait au moins ou chercher. Cette vérification vous permettra de gagner un peu de temps si vous n'obtenez pas le résultat espéré.

Exemple 6 : signaux et slots

Les signaux et slots sont l'un des avantages du développement avec la bibliothèque Qt, et n'ont pas été oubliés avec QtScript. Dans ce dernier exemple, nous allons créer une fenêtre basique comprenant deux boutons. Le premier, appelé "Bonjour", devra afficher "Bonjour !" dans la console lorsque l'on clique dessus, et le second, "Au revoir", devra afficher ... "Au revoir !" (et oui !)  lorsque l'on clique dessus. La particularité est que ces affichages dans la console se feront depuis un script JS, et non pas directement depuis des slots dans le code C++. L'intérêt ? Ici, aucun. Il s'agira simplement de montrer que l'on peut connecter un signal à un slot dont l'implémentation est faite dans un script JS, ce qui permet de personnaliser facilement une application en changeant le comportement du slot dans les scripts. Pour l'exemple, nous verrons deux façons différentes de connecter un signal à un slot implémenté dans un script, la première sera faite depuis le code C++, pour le bouton "Bonjour", et la seconde directement dans le script JS, pour le bouton "Au revoir".

Passons au code :

#include <QtScript>
#include <QApplication>
#include <QtGui>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QWidget fenetre;
    QVBoxLayout vbox(&fenetre);
    QPushButton bBonjour("Bonjour");
    QPushButton bAuRevoir("Au revoir");
    vbox.addWidget(&bBonjour);
    vbox.addWidget(&bAuRevoir);

    QScriptEngine engine;
    QFile file("script.qs");
    file.open(QIODevice::ReadOnly);
    QScriptValue objBonjour = engine.newQObject(&bBonjour);
    QScriptValue objAuRevoir = engine.newQObject(&bAuRevoir);
    engine.globalObject().setProperty("objAuRevoir",objAuRevoir);
    engine.evaluate(file.readAll());
    file.close();
    QScriptValue slot = engine.globalObject().property("bonjourHandler");
    qScriptConnect(&bBonjour, SIGNAL(clicked()), objBonjour, slot);

    fenetre.show();
    return app.exec();
}

C'est un peu plus long que d'habitude, certes, mais le premier pavé ne concerne que l'interface graphique, ce qui n'est pas le sujet de ce billet. On y créé les deux boutons et on les place sur la fenêtre.

Ensuite, comme d'habitude, on créé un moteur, qui évaluera du code JS placé dans un fichier. Deux "objets QtScript" sont créés à partir des deux boutons, grâce à newQObject() : objBonjour et objAuRevoir. Ils nous serviront à réaliser les connexions avec les slots du script. L'objet objAuRevoir est exporté dans le script car nous en aurons besoin pour réaliser la connexion au slot

Une fois l'évaluation du script faite, on récupère la propriété bonjourHander, qui est une fonction dans le script JS, que l'on stocke dans la variable slot. Cette variable est utilisée ensuite pour réaliser la connexion entre le signal clicked du bouton "Bonjour" et le slot bonjourHandler, grâce à la fonction qScriptConnect().

Voyons maintenant le contenu du script (fichier script.qs) :

function bonjourHandler () {
    print('Bonjour !');
}
function auRevoirHandler () {
    print('Au revoir !');
}
objAuRevoir.clicked.connect(this.auRevoirHandler);

On y trouve les deux fonctions qui seront appelées lors des clics sur les boutons. bonjourHandler est déjà connectée au bouton "Bonjour" via l'appel à qScriptConnect() dans le code C++. La fonction auRevoirHandler est elle connectée au bouton "Au revoir" à la dernière ligne du script, qui utilise l'objet objAuRevoir exporté dans le programme. Ainsi nos deux fonctions sont connectées à leur bouton respectif, l'une depuis le code C++, l'autre depuis le script JS. A vous de voir ce que vous préférez !

Une petite image pour l'exécution du programme :

tuto_qtscript_exemple6.jpg


C'est sur cet exemple que ce termine cette initiation. Je n'ai pas la prétention avec cet article de donner une vue d'ensemble des possibilités offertes par QtScript. J'en suis d'ailleurs encore au stade de la découverte. Mais ces exemples pourrons vous permettre de vous lancer, et de démystifier un peu les choses ! Je proposerai dans un prochain article un exemple concret d'utilisation de QtScript pour un système de plugins.

Hormis les liens disséminés un peu partout dans l'article, les deux sources qui m'ont le plus aidé sont :

Bon dev' !


Fabien (eponyme)

Cython pour optimiser Python

Patrice Ferlet

Cython permet de coder des extensions python compilées via un mécanisme de traduction en C. Mais il permet aussi de créer un exécutable de votre programme python. Les performances sont spectaculaires et nous allons le voir dans ce billet

Python est l'un des langages les plus aboutis de notre génération. Tant au point de vue sémantique que la souplesse qu'il permet en terme d'environnement et de possibilités. Outre les outils et modules qui permettent de travailler efficacement, des projets existent pour rendre python encore plus optimal. Beaucoup savent déjà que, comme dans pas mal de langages, il est possible de développer des extension en C pour augmenter la vitesse d'exécution d'un programme python. Mais l'écriture est un peu complexe et il souvent on préfère laisser de coté la performance en faveur de la vitesse de développement.

Python étant interprété, le programme que vous aurez créé sera alors soumi à une dépendance forte: Python lui même. Car pour exécuter un programme python, il faut python. Or comme je vous l'ai dit Python a un univers autour de lui qui permet de faire des miracles. Nous allons donc voir l'un des ses astres qui gravite autour de Python: Cython. Avec lui, vous allez pouvoir développer simplement des extension plus optimisées, et en plus pouvoir compiler un programme python en un exécutable binaire natif.

La seule dépendance sera alors la librairie python.

Voici, sous Fedora, ce que vous allez faire:

yum install Cython

Cela installe les librairies Cython + un outil de compilation qui porte le même nom. C'est parti pour l'explication.

Cython est un outil qui va permettre la traduction de votre code python en C. Un peu comme le fait shedskin. La différence est que Cython permet de générer un code en C qui sera exploitable par python. En d'autres termes, et pour être plus exact, Cython crée du code C pour générer une extenion python valide.

Prenon un simple exemple:

#file: hello.py
def printHello:
        print "Hello you !"

Ce code est simple, c'est une fonction qui, lorsqu'on l'appelle, affiche un message "Hello you !". Pour générer l'extension il existe plusieurs méthodes. La plus simple étant de faire un fichier "setup.py" (un standard python qui utilise distutils) ou utiliser Cython.

Comme je péfère expliquer les choses complexe pour les rendre simples après coup, on commence par la méthode Cython.

cython hello.py

Cela génère un fichier hello.c. Oui il est énorme, près de 1000 lignes. Le but n'est pas de faire léger mais rapide :)" class="smiley

Bon, on va compiler le fichier pour en faire une librairie partagée pour Python:

gcc hello.c -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing $(pkg-config python --cflags) -o hello.so

voilà donc un fichier hello.so qui est une extension python que nous pouvons utiliser:

#fichier: test.py
import hello

printHello()

Pour le moment, c'est pas super utile car nous ne faisons qu'une opération très simple. Mais Cython ne s'arrête pas à ça. Il permet d'utiliser des type C et même des librairies C. De plus, nous allons voir que nous pouvons faire un exécutable de notre programme. Procédons par étapes.

Passons à l'utilisation de type C, ce qui va rendre un programme plus rapide. Vous connaissez fibonacci ? voici le code qui permet de trouver le résultat de la suite à un rang donné:

#fichier fibo.py
def fibo(n):
        if n == 0 or n == 1: 
                return n
        return fibo(n-2) + fibo(n-1)

Cette fonction est particulièrement lente, d'abord parcce que nous n'avons pas optimisé le code pour garder en mémoire les suite déjà calculées, mais en plus parce que nous allons passer par Python...

Testons:

#fichier test_fibo.py
from fibo import fibo
fibo(32)

et lançons le test avec la commande "time" qui nous donnera le temps d'éxécution:

time python test_fibo.py
3524578

real    0m1.574s
user    0m1.561s
sys     0m0.008s

1,6 seconde environ... autant dire que c'est pas fameux... Voyons avec cython.

cython fibo.py
gcc fibo.c -o fibo.so -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing $(pkg-config python --cflags)

time python test_fibo.py 
3524578

real    0m1.229s
user    0m1.137s
sys     0m0.089s

Ha y'a du mieux ! 1,2 secondes, on dirait pas mais là on a gangé 30% de temps d'éxécution...

Encore mieux ? possible oui et vous allez voir, pas des moindres !

Entrons dans le vif du sujet, Cython n'est pas python mais un language à part entière. Il permet de créer des types C, je vous l'ai dit. Cela se fait avec le mot clef "cdef". Avec ce mot clef, on peut donc créer une fonction ou variable C, donc en typant et en jouant avec les retours de fonctions. Fibonacci n'utilise que des entiers. Voyons comment redéfinir notre fonction pour la rendre pure C.

En premier lieu, vous renommez fibo.py en fibo.pyx. Pourquoi ? parce que Cython a besoin de savoir que cette fois-ci nous allons utiliser un fichier prévu pour le développement C. Notre fonction va donc ressembler à cela:

#fichier fibo.pyx
cdef int fibo(int n):
        if n == 0 or n == 1: 
                return n
        return fibo(n-2) + fibo(n-1)

Souci , cette fonction ne sera pas vu par des appels externes... en d'autres termes si vous voulez appeler la fonction "fibo" depuis test_fibo.py, ça ne marchera pas. Il faut donc utiliser une des deux méthodes:

  • soit vous définissez en plus une méthode avec "def" qui apelle "fibo"
  • soit vous n'utilisez pas cdef, mais cpdef qui fera l'alias tout seul (pratique hein)

Alors la méthode 2 me parraissant plus sympa:

#fichier fibo.pyx
cpdef int fibo(int n):
        if n == 0 or n == 1: 
                return n
        return fibo(n-2) + fibo(n-1)

Allez on lance la compilation, pensez bien qu'on utilise le fichier .pyx maintenant, pas le .py:

cython fibo.pyx
gcc fibo.c -o fibo.so -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing $(pkg-config python --cflags

Et le miracle:

time python test_fibo.py 
3524578

real    0m0.070s
user    0m0.058s
sys     0m0.012s

Et oui, 70 millisecondes ! Incroyable non ?

Et si maintenant on créait un exécutable ? Soyons fou ! On va utiliser une option de Cython qui permet d'intégrer l'exécutable python dans la fonction "main", cette option s'appelle "--embed" pour "embarquer":

cython --embed test_fibo.py -o test.c

Ici j'ai demandé à sortir un fichier nommé test.c, sinon on aurait test_fibo.c et j'aime pas les underscores :)" class="smiley

Compilons ce source:

gcc test.c -o test $(pkg-config python --cflags) -lpython2.7

l'option "-lpython2.7" est celle qui est valable pour Fedora 14 (qui utilise python 2.7) sinon on adaptez le numéro de version à celle de votre installation. Et nous avons maintenant un fichier "test" exécutable:

./test
3524578

Il marche :)" class="smiley par contre on ne gagne pas grand chose en temps d'éxécution par rapport à test_fibo.py. Mais le but ici était d'avoir un exécutable qui ne demande plus que la librairie python (dans le paquet python-libs de Fedora) et non plus Python lui même.

Voyons ce que demande notre exécutable en dépendance:

ldd test
        linux-vdso.so.1 =>  (0x00007fffba369000)
        libpython2.7.so.1.0 => /usr/lib64/libpython2.7.so.1.0 (0x00007f180b44a000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f180b0ae000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f180ae92000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f180ac8e000)
        libutil.so.1 => /lib64/libutil.so.1 (0x00007f180aa8b000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f180a806000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f180b81c000)

Effectivement il demande libpython et, évidamment, fibo.so est appelé lors de l'exécution, donc pensez à avoir ce fichier dans le même répertoire ou bien placé dans le répertoire de librairies (LD_LIBRARY_PATH, ou dans les lib de python)

Voilà, c'est une "courte" introduction car en fait Cython est bien plus puissant que cela, vous aurez à loisirs d'utiliser les librairies C existante (stdlib, math...) et de rendre vos développement extrêmement puissant et rapide... Voilà encore un point qui me rend de plus en plus fan de Python.

Je vous conseille vivement d'aller voir le site de documentation de cython et notamment la page qui traite de distutils dans le tutoriel cython

Voilà voilà

Scripts JS avec Java et Rhino : mise en œuvre avec un bot IRC

Fabien Nicoleau

Je montrais dans mon billet précédent que grâce à Rhino il est possible d'exécuter du code JavaScript depuis un programme Java, et comment il possible de se servir de ce système pour gérer des plugins. Je propose dans ce billet de mettre en œuvre ces fonctionnalités et de les appliquer à un projet simple : un robot IRC modulaire. Le principe est d'avoir un robot écrit en Java et capable de se connecter à un serveur IRC, puis de communiquer avec lui (envoi et réception de messages). Nous n'ajouterons rien à ce programme Java, aucun savoir faire pour ce robot, à une exception prêt : la possibilité de stocker des plugins qui lui permettront d'avoir de nouvelles fonctionnalités. Attention, le sujet de ce billet n'étant pas le codage d'un robot IRC (il y en a de meilleurs pour ca ^^), celui présenté ici est ultra simple, et se contente du minimum.

Pour comprendre ce programme, il faut avoir quelques notions sur Rhino/Java, des notions sur les expressions régulières, et notamment les groups qui seront bien utiles ici, et savoir en gros ce qu'est un client IRC.

Le décor

Le programme sera donc codé en Java. Une classe JIrcRobot se chargera de se connecter au serveur IRC, et de communiquer avec lui. Une interface permettra de définir les méthodes à implémenter par les plugins. Elle sera originalement nommée Plugin. La classe JIrcRobot chargera les plugins disponibles, c'est à dire ceux contenus dans le dossier originalement nommé plugins et dont l'extension est .js, puisqu'ils contiennent du code JavaScript. Cela permettra aussi de pouvoir désactiver le chargement d'un plugin en modifiant simplement son extension. Une fois les plugins chargés, le programme attendra des messages du serveur. A chaque message reçu, il interrogera tout ses plugins pour savoir si l'un d'eux est concerné par le message. Si c'est le cas, le plugin sera alors exécuté et pourra alors réagir en envoyant des messages au serveur IRC.

Le test

Pour ceux qui préfèrent voir les choses fonctionner tout de suite, et voir comment ca marche, je vous propose de tester tout de suite le programme. Il vous faut récupérer tous les fichiers en annexe de ce programme : le fichier JIrcRobot.java ainsi que tous les fichiers en .js, qui seront à mettre dans un dossier nommé plugins. JIrcRobot.java sera à mettre au même niveau d'arborescence que le dossier plugins. Vous pouvez sinon juste prendre le fichier jircrobot.tar.gz et le décompresser.

Ouvrez ensuite le fichier JIrcRobot.java et éditez les paramètres de connexion, situés sous le commentaire "CONFIGURATION DE LA CONNEXION AU SERVEUR" pour donner un pseudonyme au robot, lui indiquer sur quel serveur il doit se connecter, et quels canaux il doit rejoindre. Sauvegardez les modifications et compilez le programme :

javac JIrcRobot.java

Vous pouvez maintenant l'exécuter :

java JIrcRobot

Le robot charge les plugins puis se connecte au serveur. En l'état actuel, le robot est capable de répondre aux PING du serveur, afin qu'il ne coupe pas la connexion, de rejoindre des canaux de discussion, de se présenter quand il rejoint un canal et souhaiter la bienvenue à ceux qui arrivent ensuite, de se couper si quelqu'un tape "!QUIT" et enfin de crier "COCORICO" si une phrase contient "france". Ces fonctionnalités sont toutes apportées par les plugins. Le programme Java en lui même ne sait rien faire de tout ca.

La classe JIrcRobot

Il est maintenant temps de comprendre comment tout cela fonctionne. Je passe rapidement sur les fonctionnalités liées au protocole IRC. Sans entrer dans le code, il faut juste savoir que la classe propose ces méthodes :

connect() : se connecte au serveur IRC
getNickName() : renvoie le pseudonyme du robot
joinChannels() : envoie les commandes au serveur IRC pour rejoindre des canaux de discussion
loadPlugins() : charge tous les plugins disponibles dans un dossier
readLine() : lis un message provenant du serveur, s'il y en a un d'arrivé
run() : boucle conservant la connexion avec le serveur, et exécutant les plugins si besoin
setStayConnected() : permet d'indiquer si l'on souhaite rester connecté au serveur ou non
write() : envoi un message au serveur IRC


Les méthodes importantes seront détaillées ensuite : loadPlugins et run.

Enfin la classe contient une interface privée, Plugin, qui permet de définir les méthodes que devront implémenter les plugins :

getPattern() : retournera l'expression régulière qui si elle est validée, indiquera que le plugin doit réagir au message arrivé
patternMatch() : cette méthode sera appelée si l'expression régulière du plugin match avec le message arrivé

Nos plugins devront donc implémenter ces deux méthodes.

Le programme Java

Après avoir vu les méthodes permettant de dialoguer avec le serveur, intéressons nous, dans le détail cette fois-ci, au code des deux principales. Avant cela, un rapide coup d'œil à la méthode main du programme :

public static void main(String[] args) {
    JIrcRobot jir = new JIrcRobot();
    jir.loadPlugins("plugins");
    if (jir.connect()) {
        jir.run();
    }
}

Rien de complexe : on créé un objet JIrcRobot, on charge les plugins, on connecte le robot au serveur IRC, et enfin on se met en attente de réception des messages avec la méthode run(). Il est maintenant temps de voir comment sont chargés les plugins, avec la méthode loadPlugins ;

public void loadPlugins(String folder) {
    ScriptEngine engine;
    Invocable inv;
    File dir = new File(folder);
    for (File file : dir.listFiles()) {
        if (file.isFile() && file.getName().toLowerCase().endsWith(".js")) {
            try {
                engine = new ScriptEngineManager().getEngineByName("JavaScript");
                engine.put("robot", this);
                engine.eval(new FileReader(file.getAbsolutePath()));
                inv = (Invocable) engine;
                this.plugins.add(inv.getInterface(Plugin.class));
                System.out.println(file.getAbsolutePath() + " loaded.");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Cette méthode va lister (listFiles) le dossier dont le nom lui est passé en paramètre (folder). Pour chaque fichier dont l'extension est .js (endsWith), elle créé un moteur de JavaScript (getEngineByName) et place dans l'environnement du script une variable nommée robot pointant sur l'objet JIrcRobot (put). Ensuite elle lit le contenu du fichier et stocke dans le tableau plugins une instanciation de l'interface Plugin provenant du fichier (getInterface).

Le chargement des plugins consiste donc à stocker des objets implémentant l'interface Plugin et dont le contenu provient des fichiers JavaScript.

Maintenant, la méthode run :

public void run() {
    String buffer;
    Matcher matcher;
    while (this.stayConnected) {
        buffer = this.readLine();
        if (!buffer.equals("")) {
            System.out.println(buffer);
            for (Plugin p : this.plugins) {
                matcher = Pattern.compile(p.getPattern()).matcher(buffer);
                if (matcher.find()) {
                    p.patternMatch(matcher);
                }
            }
        }
    }
}

Cette méthode contient une boucle qui attend un message du serveur IRC (readLine). Une fois qu'un message est reçu, elle boucle sur tous les plugins chargés. Pour chacun d'eux, elle teste si le pattern du plugin (p.getPattern()) match avec le message reçu (buffer). Si c'est le cas (find), la méthode patterMatch() du plugin est appellée et on lui passe en paramètre le matcher, ce qui permettra au plugin d'analyser les groupes capturés grâce à l'expression régulière. C'est assez pratique car le plugin reçoit ainsi seulement ce qui l'intéresse, et qu'il a lui-même spécifié dans la regex.

C'est ici que s'arrête le détail ! Nous avons un système qui stocke des plugins et un autre qui communique avec eux pour savoir s'il faut les solliciter (grâce à un système de regex ici, mais ca pourrait être autrement) et qui qui les exécute si besoin. Ce robot IRC est donc ainsi fait d'un noyau, disposant de peu de fonctionnalités, mais extensible à volonté.

Les plugins

Notre programme étant prêt, concentrons nous maintenant sur les plugins. Nous créerons un fichier par plugin

ping.js

Le serveur IRC envoie régulièrement un message PING auquel le client doit répondre. Si la réponse n'est pas envoyée dans un certain laps de temps, le serveur rompt la connexion. Ce plugin est donc vital. Le message est du type "PING :msg". Le client doit répondre "PONG :msg". msg est parfois le nom du serveur, ou parfois un numéro aléatoire. Pour un PING reçu, on récupèrera donc la partie droite, et on s'en servira pour envoyer le PONG. Voici le contenu de ping.js :

importClass(java.util.regex.Matcher);

function getPattern() {
    return 'PING\\s:(.*)';
}

function patternMatch(matcher) {
    robot.write("PONG :" + matcher.group(1));
}

On commence par importer la classe Java Matcher. On implémente ensuite les deux méthodes nécessaires de l'interface Plugin. Le regex renvoyé par getPattern contient un groupe qui capturera la partie à renvoyer par le PONG. La fonction patternMatch qui est appelée si le regex a matché envoie le PONG suivi du groupe capturé dans le PING. Pour cela elle utilise la variable robot représentant l'objet JIrcRobot pour envoyer un message au serveur.

endOfMOTD.js

Le Message Of The Day (MOTD) est un texte envoyé par le serveur au client lorsqu'il se connecte. Il contient souvent des informations sur le serveur. Une fois le MOTD envoyé, le serveur envoie un message indiquant la fin du MOTD du type : ":calvino.freenode.net 376 jircrobot :End of /MOTD command.". Il est important de réagir à ce message car certains serveurs n'autorisent pas le client à rejoindre des canaux tant que le MOTD n'a pas été envoyé. En attendant la réception de ce message pour rejoindre des canaux, on est sûr qu'on ne sera pas bloqué. Ce plugin réagira donc à ce message, et une fois reçu, appellera la méthode joinChannels de la classe JIrcRobot :

importClass(java.util.regex.Matcher);

function getPattern() {
    return ':.*\\s376\\s.*';
}

function patternMatch(matcher) {
    robot.joinChannels();
}

C'est assez simple ici encore. On donne la regex pour capturer la fin du MOTD et lorsque que le message est arrivé, on utilise la variable robot pour se connecter aux canaux.

quit.js

Ce plugin permettra de stopper le bot. Lorsqu'une personne tapera !QUIT sur un canal, le robot se stoppera. Biensûr dans le cadre d'un "vrai" bot, il faudrait s'assurer que la personne voulant couper le robot en a le droit, en se basant sur son nom d'hôte ou son pseudonyme. Le message envoyé par le serveur sera du type ":someone!~someident@some-host.com PRIVMSG #channel1 :!QUIT". Voici le contenu du plugin :

importClass(java.util.regex.Matcher);

function getPattern() {
    return ':.*\\sPRIVMSG\\s[^ ]+\\s:!QUIT';
}

function patternMatch(matcher) {
    robot.setStayConnected(false);
}

La regex ne capture rien ici car on ne gère pas de droits. Pour quelque chose de plus sécurisé, il faudrait capturer au moins le pseudonyme de la personne. La fonction appelée utilisera la méthode setStayConnected avec false en paramètre, ce qui aura pour effet de stopper la méthode run du robot.

join.js

Afin de rendre notre robot un minimum sociable, ce plugin aura pour tâche de le faire se présenter lorsqu'il rejoint un canal et de souhaiter la bienvenue aux nouveaux arrivants. Pour cela, il suffira de capturer les messages de type JOIN provenant du serveur, qui sont envoyés quand une personne rejoint un canal, ou quand le bot lui même le fait. Le message est du type ":nick!~ident@hostname.fr JOIN :#channel". Il suffit alors de comparer "nick" avec le propre pseudonyme du robot. S'il sont équivalents, c'est que c'est lui qui entre sur un canal, il doit donc se présenter. Si ce n'est pas le sien, alors il souhaite la bienvenue au nouvel arrivant. Il faudra donc capturer le pseudonyme et le nom du canal afin de s'en servir pour envoyer le message :

importClass(java.util.regex.Matcher);

function getPattern() {
    return ':([^!]+).*\\sJOIN\\s:([^ ]+)';
}

function patternMatch(matcher) {
    // Si c'est le robot qui JOIN
    if(matcher.group(1) == robot.getNickname()) {
        robot.write("PRIVMSG "+matcher.group(2)+" :Salut tout le monde, je suis "+matcher.group(1));
    }
    else { // si c'est un nouvel arrivant
        robot.write("PRIVMSG "+matcher.group(2)+" :Salut "+matcher.group(1));
    }
}

On voit donc la regex avec les deux captures (nick + canal). Ces informations sont ensuite utilisées pour le message envoyé.

patriote.js

Un plugin beaucoup moins utile, qui permettra simplement de montrer comment réagir à un texte particulier. Nous souhaitons ici que le bot montre qu'il est fier d'être français. A chaque fois que le mot France (peu importe la casse) sera écrit, il réagira. Le message reçu sera du type ":someone!~someident@some-host.com PRIVMSG #channel :Vive la France". Voyons le code du plugin :

importClass(java.util.regex.Matcher);

function getPattern() {
    return '(?i):[^!]+.*\\sPRIVMSG\\s([^ ]+)\\s:.*france.*';
}

function patternMatch(matcher) {
    robot.write("PRIVMSG "+matcher.group(1)+" :COCORICO !!!");
}

La regex indique que la recherche sera insensible à la casse. On capture simplement le nom du canal d'où le texte est envoyé pour pouvoir répondre.


Voilà pour quelques exemples de plugins. Le but ici est de montrer que presque tout est possible, mais que surtout ce genre de programme, modulable, auquel on peut ajouter des fonctionnalités, est facilement réalisable. Pour les plus courageux qui auront lu jusque ici (j'espère que number80 en fait partie !), n'hésitez pas à poser des questions dans les commentaires. Il n'est pas facile de synthétiser tout cela en un seul billet, il y a donc probablement des passages obscurs !


Fabien (eponyme)

Scripts JS avec Java et Rhino : mise en œuvre avec un bot IRC

Fabien Nicoleau

Je montrais dans mon billet précédent que grâce à Rhino il est possible d'exécuter du code JavaScript depuis un programme Java, et comment il possible de se servir de ce système pour gérer des plugins. Je propose dans ce billet de mettre en œuvre ces fonctionnalités et de les appliquer à un projet simple : un robot IRC modulaire. Le principe est d'avoir un robot écrit en Java et capable de se connecter à un serveur IRC, puis de communiquer avec lui (envoi et réception de messages). Nous n'ajouterons rien à ce programme Java, aucun savoir faire pour ce robot, à une exception prêt : la possibilité de stocker des plugins qui lui permettront d'avoir de nouvelles fonctionnalités. Attention, le sujet de ce billet n'étant pas le codage d'un robot IRC (il y en a de meilleurs pour ca ^^), celui présenté ici est ultra simple, et se contente du minimum.

Pour comprendre ce programme, il faut avoir quelques notions sur Rhino/Java, des notions sur les expressions régulières, et notamment les groups qui seront bien utiles ici, et savoir en gros ce qu'est un client IRC.

Le décor

Le programme sera donc codé en Java. Une classe JIrcRobot se chargera de se connecter au serveur IRC, et de communiquer avec lui. Une interface permettra de définir les méthodes à implémenter par les plugins. Elle sera originalement nommée Plugin. La classe JIrcRobot chargera les plugins disponibles, c'est à dire ceux contenus dans le dossier originalement nommé plugins et dont l'extension est .js, puisqu'ils contiennent du code JavaScript. Cela permettra aussi de pouvoir désactiver le chargement d'un plugin en modifiant simplement son extension. Une fois les plugins chargés, le programme attendra des messages du serveur. A chaque message reçu, il interrogera tout ses plugins pour savoir si l'un d'eux est concerné par le message. Si c'est le cas, le plugin sera alors exécuté et pourra alors réagir en envoyant des messages au serveur IRC.

Le test

Pour ceux qui préfèrent voir les choses fonctionner tout de suite, et voir comment ca marche, je vous propose de tester tout de suite le programme. Il vous faut récupérer tous les fichiers en annexe de ce programme : le fichier JIrcRobot.java ainsi que tous les fichiers en .js, qui seront à mettre dans un dossier nommé plugins. JIrcRobot.java sera à mettre au même niveau d'arborescence que le dossier plugins. Vous pouvez sinon juste prendre le fichier jircrobot.tar.gz et le décompresser.

Ouvrez ensuite le fichier JIrcRobot.java et éditez les paramètres de connexion, situés sous le commentaire "CONFIGURATION DE LA CONNEXION AU SERVEUR" pour donner un pseudonyme au robot, lui indiquer sur quel serveur il doit se connecter, et quels canaux il doit rejoindre. Sauvegardez les modifications et compilez le programme :

javac JIrcRobot.java

Vous pouvez maintenant l'exécuter :

java JIrcRobot

Le robot charge les plugins puis se connecte au serveur. En l'état actuel, le robot est capable de répondre aux PING du serveur, afin qu'il ne coupe pas la connexion, de rejoindre des canaux de discussion, de se présenter quand il rejoint un canal et souhaiter la bienvenue à ceux qui arrivent ensuite, de se couper si quelqu'un tape "!QUIT" et enfin de crier "COCORICO" si une phrase contient "france". Ces fonctionnalités sont toutes apportées par les plugins. Le programme Java en lui même ne sait rien faire de tout ca.

La classe JIrcRobot

Il est maintenant temps de comprendre comment tout cela fonctionne. Je passe rapidement sur les fonctionnalités liées au protocole IRC. Sans entrer dans le code, il faut juste savoir que la classe propose ces méthodes :

connect() : se connecte au serveur IRC
getNickName() : renvoie le pseudonyme du robot
joinChannels() : envoie les commandes au serveur IRC pour rejoindre des canaux de discussion
loadPlugins() : charge tous les plugins disponibles dans un dossier
readLine() : lis un message provenant du serveur, s'il y en a un d'arrivé
run() : boucle conservant la connexion avec le serveur, et exécutant les plugins si besoin
setStayConnected() : permet d'indiquer si l'on souhaite rester connecté au serveur ou non
write() : envoi un message au serveur IRC


Les méthodes importantes seront détaillées ensuite : loadPlugins et run.

Enfin la classe contient une interface privée, Plugin, qui permet de définir les méthodes que devront implémenter les plugins :

getPattern() : retournera l'expression régulière qui si elle est validée, indiquera que le plugin doit réagir au message arrivé
patternMatch() : cette méthode sera appelée si l'expression régulière du plugin match avec le message arrivé

Nos plugins devront donc implémenter ces deux méthodes.

Le programme Java

Après avoir vu les méthodes permettant de dialoguer avec le serveur, intéressons nous, dans le détail cette fois-ci, au code des deux principales. Avant cela, un rapide coup d'œil à la méthode main du programme :

public static void main(String[] args) {
    JIrcRobot jir = new JIrcRobot();
    jir.loadPlugins("plugins");
    if (jir.connect()) {
        jir.run();
    }
}

Rien de complexe : on créé un objet JIrcRobot, on charge les plugins, on connecte le robot au serveur IRC, et enfin on se met en attente de réception des messages avec la méthode run(). Il est maintenant temps de voir comment sont chargés les plugins, avec la méthode loadPlugins ;

public void loadPlugins(String folder) {
    ScriptEngine engine;
    Invocable inv;
    File dir = new File(folder);
    for (File file : dir.listFiles()) {
        if (file.isFile() && file.getName().toLowerCase().endsWith(".js")) {
            try {
                engine = new ScriptEngineManager().getEngineByName("JavaScript");
                engine.put("robot", this);
                engine.eval(new FileReader(file.getAbsolutePath()));
                inv = (Invocable) engine;
                this.plugins.add(inv.getInterface(Plugin.class));
                System.out.println(file.getAbsolutePath() + " loaded.");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Cette méthode va lister (listFiles) le dossier dont le nom lui est passé en paramètre (folder). Pour chaque fichier dont l'extension est .js (endsWith), elle créé un moteur de JavaScript (getEngineByName) et place dans l'environnement du script une variable nommée robot pointant sur l'objet JIrcRobot (put). Ensuite elle lit le contenu du fichier et stocke dans le tableau plugins une instanciation de l'interface Plugin provenant du fichier (getInterface).

Le chargement des plugins consiste donc à stocker des objets implémentant l'interface Plugin et dont le contenu provient des fichiers JavaScript.

Maintenant, la méthode run :

public void run() {
    String buffer;
    Matcher matcher;
    while (this.stayConnected) {
        buffer = this.readLine();
        if (!buffer.equals("")) {
            System.out.println(buffer);
            for (Plugin p : this.plugins) {
                matcher = Pattern.compile(p.getPattern()).matcher(buffer);
                if (matcher.find()) {
                    p.patternMatch(matcher);
                }
            }
        }
    }
}

Cette méthode contient une boucle qui attend un message du serveur IRC (readLine). Une fois qu'un message est reçu, elle boucle sur tous les plugins chargés. Pour chacun d'eux, elle teste si le pattern du plugin (p.getPattern()) match avec le message reçu (buffer). Si c'est le cas (find), la méthode patterMatch() du plugin est appellée et on lui passe en paramètre le matcher, ce qui permettra au plugin d'analyser les groupes capturés grâce à l'expression régulière. C'est assez pratique car le plugin reçoit ainsi seulement ce qui l'intéresse, et qu'il a lui-même spécifié dans la regex.

C'est ici que s'arrête le détail ! Nous avons un système qui stocke des plugins et un autre qui communique avec eux pour savoir s'il faut les solliciter (grâce à un système de regex ici, mais ca pourrait être autrement) et qui qui les exécute si besoin. Ce robot IRC est donc ainsi fait d'un noyau, disposant de peu de fonctionnalités, mais extensible à volonté.

Les plugins

Notre programme étant prêt, concentrons nous maintenant sur les plugins. Nous créerons un fichier par plugin

ping.js

Le serveur IRC envoie régulièrement un message PING auquel le client doit répondre. Si la réponse n'est pas envoyée dans un certain laps de temps, le serveur rompt la connexion. Ce plugin est donc vital. Le message est du type "PING :msg". Le client doit répondre "PONG :msg". msg est parfois le nom du serveur, ou parfois un numéro aléatoire. Pour un PING reçu, on récupèrera donc la partie droite, et on s'en servira pour envoyer le PONG. Voici le contenu de ping.js :

importClass(java.util.regex.Matcher);

function getPattern() {
    return 'PING\\s:(.*)';
}

function patternMatch(matcher) {
    robot.write("PONG :" + matcher.group(1));
}

On commence par importer la classe Java Matcher. On implémente ensuite les deux méthodes nécessaires de l'interface Plugin. Le regex renvoyé par getPattern contient un groupe qui capturera la partie à renvoyer par le PONG. La fonction patternMatch qui est appelée si le regex a matché envoie le PONG suivi du groupe capturé dans le PING. Pour cela elle utilise la variable robot représentant l'objet JIrcRobot pour envoyer un message au serveur.

endOfMOTD.js

Le Message Of The Day (MOTD) est un texte envoyé par le serveur au client lorsqu'il se connecte. Il contient souvent des informations sur le serveur. Une fois le MOTD envoyé, le serveur envoie un message indiquant la fin du MOTD du type : ":calvino.freenode.net 376 jircrobot :End of /MOTD command.". Il est important de réagir à ce message car certains serveurs n'autorisent pas le client à rejoindre des canaux tant que le MOTD n'a pas été envoyé. En attendant la réception de ce message pour rejoindre des canaux, on est sûr qu'on ne sera pas bloqué. Ce plugin réagira donc à ce message, et une fois reçu, appellera la méthode joinChannels de la classe JIrcRobot :

importClass(java.util.regex.Matcher);

function getPattern() {
    return ':.*\\s376\\s.*';
}

function patternMatch(matcher) {
    robot.joinChannels();
}

C'est assez simple ici encore. On donne la regex pour capturer la fin du MOTD et lorsque que le message est arrivé, on utilise la variable robot pour se connecter aux canaux.

quit.js

Ce plugin permettra de stopper le bot. Lorsqu'une personne tapera !QUIT sur un canal, le robot se stoppera. Biensûr dans le cadre d'un "vrai" bot, il faudrait s'assurer que la personne voulant couper le robot en a le droit, en se basant sur son nom d'hôte ou son pseudonyme. Le message envoyé par le serveur sera du type ":someone!~someident@some-host.com PRIVMSG #channel1 :!QUIT". Voici le contenu du plugin :

importClass(java.util.regex.Matcher);

function getPattern() {
    return ':.*\\sPRIVMSG\\s[^ ]+\\s:!QUIT';
}

function patternMatch(matcher) {
    robot.setStayConnected(false);
}

La regex ne capture rien ici car on ne gère pas de droits. Pour quelque chose de plus sécurisé, il faudrait capturer au moins le pseudonyme de la personne. La fonction appelée utilisera la méthode setStayConnected avec false en paramètre, ce qui aura pour effet de stopper la méthode run du robot.

join.js

Afin de rendre notre robot un minimum sociable, ce plugin aura pour tâche de le faire se présenter lorsqu'il rejoint un canal et de souhaiter la bienvenue aux nouveaux arrivants. Pour cela, il suffira de capturer les messages de type JOIN provenant du serveur, qui sont envoyés quand une personne rejoint un canal, ou quand le bot lui même le fait. Le message est du type ":nick!~ident@hostname.fr JOIN :#channel". Il suffit alors de comparer "nick" avec le propre pseudonyme du robot. S'il sont équivalents, c'est que c'est lui qui entre sur un canal, il doit donc se présenter. Si ce n'est pas le sien, alors il souhaite la bienvenue au nouvel arrivant. Il faudra donc capturer le pseudonyme et le nom du canal afin de s'en servir pour envoyer le message :

importClass(java.util.regex.Matcher);

function getPattern() {
    return ':([^!]+).*\\sJOIN\\s:([^ ]+)';
}

function patternMatch(matcher) {
    // Si c'est le robot qui JOIN
    if(matcher.group(1) == robot.getNickname()) {
        robot.write("PRIVMSG "+matcher.group(2)+" :Salut tout le monde, je suis "+matcher.group(1));
    }
    else { // si c'est un nouvel arrivant
        robot.write("PRIVMSG "+matcher.group(2)+" :Salut "+matcher.group(1));
    }
}

On voit donc la regex avec les deux captures (nick + canal). Ces informations sont ensuite utilisées pour le message envoyé.

patriote.js

Un plugin beaucoup moins utile, qui permettra simplement de montrer comment réagir à un texte particulier. Nous souhaitons ici que le bot montre qu'il est fier d'être français. A chaque fois que le mot France (peu importe la casse) sera écrit, il réagira. Le message reçu sera du type ":someone!~someident@some-host.com PRIVMSG #channel :Vive la France". Voyons le code du plugin :

importClass(java.util.regex.Matcher);

function getPattern() {
    return '(?i):[^!]+.*\\sPRIVMSG\\s([^ ]+)\\s:.*france.*';
}

function patternMatch(matcher) {
    robot.write("PRIVMSG "+matcher.group(1)+" :COCORICO !!!");
}

La regex indique que la recherche sera insensible à la casse. On capture simplement le nom du canal d'où le texte est envoyé pour pouvoir répondre.


Voilà pour quelques exemples de plugins. Le but ici est de montrer que presque tout est possible, mais que surtout ce genre de programme, modulable, auquel on peut ajouter des fonctionnalités, est facilement réalisable. Pour les plus courageux qui auront lu jusque ici (j'espère que number80 en fait partie !), n'hésitez pas à poser des questions dans les commentaires. Il n'est pas facile de synthétiser tout cela en un seul billet, il y a donc probablement des passages obscurs !


Fabien (eponyme)

Java : gérer facilement des plugins JavaScript

Fabien Nicoleau

Dans une application, il peut être parfois intéressant d'extraire une partie de l'intelligence afin de répondre à un besoins sépcifique. Un exemple courant est une application fournie à différents clients. Elle comporte un socle commun, puis les spécificités sont développées dans des plugins afin de ne pas avoir à toucher au code de l'application pour une demande particulière. L'autre intérêt est de pouvoir charger dans une application des fonctionnalités supplémentaires, permettant de réduire le poids de l'application en retirant des fonctionnalités inutilisées. Le fait d'avoir du code externe permet aussi d'en facilité les mises à jour.

En Java, il est possible de créer des plugins de différentes façons : avec des classes externes, chargées grâce à un ClassLoader, grâce à groovy, très populaire, ou encore jython, permettant d'exécuter du code Python depuis un programme Java. Il existe de nombreuses autres solutions, comme celle d'utiliser le moteur JavaScript de la fondation Mozilla : Rhino. Sa particularité est qu'il est fourni par défaut avec Java depuis la version 6.

En ayant eu besoin pour un projet, et ayant trouvé peu d'aide en français, je propose ici quelques exemples très basiques afin de mettre en place un système de plugins JavaScript avec Java. 90% de l'aide dont j'ai eu besoin a été trouvée sur cette page de documentation. Je reprends donc ici quelques exemples, commentés, afin de comprendre le principe et d'imaginer les possibilités qu'offre Rhino. N'hésitez pas à poser des questions dans les commentaires si vous avez besoins de précisions.

Tout d'abord, je vous indique ici les import dont vous aurez besoin et qui seront à utiliser tout au long des exemples (mais pas utiles pour tous). Il ne seront pas réécris ensuite, pour plus de lisibilité :

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.FileReader;

Toujours pour simplifier le code affiché, les exceptions ne seront pas gérées, mais renvoyées par le main. Il vous faut évidemment avoir quelques bases de Java et de JavaScript pour pouvoir suivre ces exemples. Dans chacun d'eux, j'utiliserai des fonctions JavaScript. Si vous préférez utiliser du code orienté objet, il vous sera facile de l'adapter grâce aux exemples de la page Oracle citée en préambule.

Enfin, pour chaque exemple, je n'afficherai que la fonction main, qu'il faudra évidemment que vous incluiez dans une classe pour que le tout fonctionne, cependant les sources de chaque exemples sont disponibles en annexe de ce billet.

Exemple 1 : un simple affichage

Pour le premier essai, nous allons exécuter une simple ligne de code JavaScript dans du code Java :

public static void main(String[] args) throws Exception {
     ScriptEngineManager manager = new ScriptEngineManager();
     ScriptEngine engine = manager.getEngineByName("JavaScript");
     engine.eval("println('Mon premier script !');");
}

Rien de plus ! C'est en fait assez simple. Nous commençons par instancier un ScriptEngineManager qui nous permettra de charger le moteur de script souhaité. Nous l'utilisons sur la deuxième ligne : nous chargeons le moteur JavaScript. Aucune installation de bibliothèque nécessaire, car ce moteur est fourni avec Java 6 ! Ces deux premières lignes seront utilisées dans tous nos exemples. Nous terminons en demandant à notre moteur d'évaluer une ligne de code, ici l'affichage du texte "Mon premier script !".

Après compilation et exécution, la ligne s'affiche bien. Rien de transcendant ici, mais nous voyons que nous allons facilement pouvoir exécuter du code JavaScript.

Exemple 2 : interagir avec un script externe

Dans l'optique d'utiliser un système de plugins, il nous faut d'abord pouvoir sortir le code JavaScript du code Java, et le placer dans un fichier externe. De plus il nous faut pouvoir appeler une fonction en lui passant des paramètres, puis pouvoir récupérer le résultat obtenu.

Pour illustrer l'exemple, nous allons imaginer un programme Java qui passe à une fonction nommée compute() deux chiffres qu'elle traitera. Le code Java récupèrera le résultat du traitement effectué par compute() et l'affichera. Enfin, nous allons créer deux plugins JavaScript qui implémenteront tous les deux la fonction compute(). L'un fera une addition (plus.js) des deux chiffres donnés, l'autre une soustraction (moins.js). On commence par le code Java :

public static void main(String[] args) throws Exception {
     ScriptEngineManager manager = new ScriptEngineManager();
     ScriptEngine engine = manager.getEngineByName("JavaScript");
     engine.eval(new FileReader("plus.js"));
     Invocable inv = (Invocable) engine;
     System.out.println(inv.invokeFunction("compute",3,2));
}

Comme dans le premier exemple, nous récupérons le moteur JavaScript. Ensuite plutôt que directement évaluer du code, nous passons un fichier à la fonction eval(), qui contiendra notre code JavaScript. Nous commençons ici par plus.js, qui fera une addition. Vous pourrez ensuite utiliser moins.js. Enfin, nous invoquons la fonction compute() et lui passons deux arguments (3 et 2). Nous récupérons le résultat du traitement, qui est un Object, et qui contiendra ici un chiffre (un Double à vrai dire), nous l'affichons directement.

Voici le contenu de plus.js :

function compute(a,b) {
   return a+b;
}

Celui de moin.js :

function compute(a,b) {
   return a-b;
}

Aucun commentaire nécessaire ! Exécutez le programme avec plus.js, puis moins.js, les résultats seront 5.0 puis 1.0.

Nous voyons ici que nous pouvons très facilement avoir du code Java qui exécute une fonction dont il ne connait pas le contenu. Il en récupère le résultat sous forme d'un objet et peut le traiter. D'un autre coté, nous avons des fichiers contenant du code JavaScript qui traitent de façon différente deux arguments identiques, et renvoient le résultat. Dans une application gérant des plugins, il suffit alors que le nom du fichier JavaScript soit une variable, ou provienne d'un choix de l'utilisateur, et le tour est joué ! Attention tout de même, pour un système robuste, il est important de gérer les exceptions afin de réagir aux problèmes qui pourraient survenir.

Exemple 3 : Exporter des variables dans le script externe

Lorsque l'on mets en place un système de plugins, on souhaite que ces derniers puissent avoir accès aux informations de l'application, souvent par l'intermédiaire d'un noyau depuis lequel on accède à des variables. De cette manière le plugin agit de façon directe avec l'application, en consultant des informations, et en les mettant à jour. Nous allons dans cet exemple voir qu'il est tout à fait possible de faire cela avec Java et Rhino. Nous allons créer une classe Afficheur contenant la méthode affiche() qui se contente d'afficher dans la console une ligne de texte. Ensuite, nous allons instancier un objet de cette classe et l'exporter dans notre script JavaScript. Ce dernier utilisera dans son code l'objet exporté et appellera la méthode affiche() :

private static class Afficheur {
   public void affiche() {
       System.out.println("J'affiche !");
    }
}
public static void main(String[] args) throws Exception {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("JavaScript");
    engine.eval(new FileReader("afficheur.js"));
    Afficheur aff = new Afficheur();
    engine.put("monAfficheur",aff);
    Invocable inv = (Invocable) engine;
    inv.invokeFunction("affiche");
}

On voit ici qu'une fois l'objet Afficheur instancié, on l'exporte dans le script grace à la méthode put(). On indique que le nom de variable par laquelle l'objet sera accessible dans le script sera "monAfficheur". Enfin on appelle la fonction affiche() implémentée dans le script afficheur.js dont voici le contenu :

function affiche() {
   monAfficheur.affiche();
}

La fonction affiche() utilise donc la variable monAfficheur disponnible grace à l'export fait depuis le code Java, et appelle la méthode affiche(). Voilà notre liaison réalisée ! Depuis notre code JavaScript, nous accédons à un objet de notre application Java et pouvons l'utiliser. Ici nous ne réalisons qu'un simple affichage, mais tout est possible. Notez qu'il est possible de définir différent scope lors de l'export des variables permettant ainsi de leur donner des valeurs différentes selon le contexte utilisé lors de l'évaluation. Plus de renseignement dans la documentation.

Exemple 4 : Implémenter une interface

Nous progressons dans les fonctionnalités proposées pour réaliser une gestion de plugins. Mais il est possible d'aller plus loin, et de définir plus précisément les fonctionnalités offertes par les plugins. Pour cela, nous pouvons définir en Java une interface, et l'implémenter depuis un plugin. Une fois le code du plugin évalué, nous appelons une méthode depuis le code Java, dont l'implémentation a été réalisée depuis le code JavaScript ! Il devient alors possible de définir proprement en Java les méthodes qui seront à implémenter par les plugins, sans en connaitre le traitement évidemment, et d'utiliser ensuite dans le code des objets instanciant cette interface. Mieux que du blabla, le code : 

private interface Afficheur2 {
    void affiche2();
}
public static void main(String[] args) throws Exception {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("JavaScript");
    engine.eval("function affiche2() { println('affiche (2)'); }");
    Invocable inv = (Invocable) engine;
    Afficheur2 aff = inv.getInterface(Afficheur2.class);
    aff.affiche2();
}

Pour l'exemple, je n'utilise pas de fichier externe, mais directement une ligne de code JavaScript. Ce code implémente la méthode affiche2() de l'interface Afficheur2 en faisant un simple affichage de ligne. Nous créons un objet de type Afficheur2 puis récupérons l'instanciation de l'interface grâce à la méthode getInterface().

Enfin, nous utilisons ce nouvel objet et appelons la méthode affiche2() qui effectuera ce qui a été défini dans le code JavaScript, c'est à dire afficher une ligne de texte.

Nous voyons ici qu'il nous est donc possible d'écrire du code générique et de l'utiliser. C'est grâce aux plugins utilisés que instanciation est faite et nous pouvons ainsi pour un même code Java obtenir des comportement totalement différents.

Exemple 5 : Utilisation des classes Java depuis le code JavaScript

Comme dernier exemple et pour montrer toute la puissance de Rhino, nous créons un code Java qui se contentera d'évaluer un script :

public static void main(String[] args) throws Exception {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("JavaScript");
    engine.eval(new FileReader("frame.js"));
}

Le script en revanche est particulier, car il importe directement des classes Java et utilise les objets instanciés. Le script frame.js va créer une JFrame et l'afficher. Il affichera aussi le titre de la fenêtre, qu'il récupère directement depuis la propriété de la JFrame :

importClass(javax.swing.JFrame);
importClass(java.awt.Dimension);

var frame = new JFrame("hello");
frame.setSize(new Dimension(250,250));
frame.setVisible(true);
println(frame.title);

Exécutez le programme, une fenêtre apparait !

JFrame depuis un JavaScript


Voilà pour cette petite initiation au scripting avec Java et son application avec l'utilisation de plugins. Il y aurait surement beaucoup plus à dire, mais vous disposez maintenant d'une base pour débuter, et de l'alliance du Java et du JavaScript pour vos applications ! Ce sujet aura aussi été pour moi l'occasion de reprendre la plume sur ce blog, chose qui n'avait pas été faite depuis trop longtemps !


Fabien (eponyme)

Java : gérer facilement des plugins JavaScript

Fabien Nicoleau

Dans une application, il peut être parfois intéressant d'extraire une partie de l'intelligence afin de répondre à un besoins sépcifique. Un exemple courant est une application fournie à différents clients. Elle comporte un socle commun, puis les spécificités sont développées dans des plugins afin de ne pas avoir à toucher au code de l'application pour une demande particulière. L'autre intérêt est de pouvoir charger dans une application des fonctionnalités supplémentaires, permettant de réduire le poids de l'application en retirant des fonctionnalités inutilisées. Le fait d'avoir du code externe permet aussi d'en facilité les mises à jour.

En Java, il est possible de créer des plugins de différentes façons : avec des classes externes, chargées grâce à un ClassLoader, grâce à groovy, très populaire, ou encore jython, permettant d'exécuter du code Python depuis un programme Java. Il existe de nombreuses autres solutions, comme celle d'utiliser le moteur JavaScript de la fondation Mozilla : Rhino. Sa particularité est qu'il est fourni par défaut avec Java depuis la version 6.

En ayant eu besoin pour un projet, et ayant trouvé peu d'aide en français, je propose ici quelques exemples très basiques afin de mettre en place un système de plugins JavaScript avec Java. 90% de l'aide dont j'ai eu besoin a été trouvée sur cette page de documentation. Je reprends donc ici quelques exemples, commentés, afin de comprendre le principe et d'imaginer les possibilités qu'offre Rhino. N'hésitez pas à poser des questions dans les commentaires si vous avez besoins de précisions.

Tout d'abord, je vous indique ici les import dont vous aurez besoin et qui seront à utiliser tout au long des exemples (mais pas utiles pour tous). Il ne seront pas réécris ensuite, pour plus de lisibilité :

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.FileReader;

Toujours pour simplifier le code affiché, les exceptions ne seront pas gérées, mais renvoyées par le main. Il vous faut évidemment avoir quelques bases de Java et de JavaScript pour pouvoir suivre ces exemples. Dans chacun d'eux, j'utiliserai des fonctions JavaScript. Si vous préférez utiliser du code orienté objet, il vous sera facile de l'adapter grâce aux exemples de la page Oracle citée en préambule.

Enfin, pour chaque exemple, je n'afficherai que la fonction main, qu'il faudra évidemment que vous incluiez dans une classe pour que le tout fonctionne, cependant les sources de chaque exemples sont disponibles en annexe de ce billet.

Exemple 1 : un simple affichage

Pour le premier essai, nous allons exécuter une simple ligne de code JavaScript dans du code Java :

public static void main(String[] args) throws Exception {
     ScriptEngineManager manager = new ScriptEngineManager();
     ScriptEngine engine = manager.getEngineByName("JavaScript");
     engine.eval("println('Mon premier script !');");
}

Rien de plus ! C'est en fait assez simple. Nous commençons par instancier un ScriptEngineManager qui nous permettra de charger le moteur de script souhaité. Nous l'utilisons sur la deuxième ligne : nous chargeons le moteur JavaScript. Aucune installation de bibliothèque nécessaire, car ce moteur est fourni avec Java 6 ! Ces deux premières lignes seront utilisées dans tous nos exemples. Nous terminons en demandant à notre moteur d'évaluer une ligne de code, ici l'affichage du texte "Mon premier script !".

Après compilation et exécution, la ligne s'affiche bien. Rien de transcendant ici, mais nous voyons que nous allons facilement pouvoir exécuter du code JavaScript.

Exemple 2 : interagir avec un script externe

Dans l'optique d'utiliser un système de plugins, il nous faut d'abord pouvoir sortir le code JavaScript du code Java, et le placer dans un fichier externe. De plus il nous faut pouvoir appeler une fonction en lui passant des paramètres, puis pouvoir récupérer le résultat obtenu.

Pour illustrer l'exemple, nous allons imaginer un programme Java qui passe à une fonction nommée compute() deux chiffres qu'elle traitera. Le code Java récupèrera le résultat du traitement effectué par compute() et l'affichera. Enfin, nous allons créer deux plugins JavaScript qui implémenteront tous les deux la fonction compute(). L'un fera une addition (plus.js) des deux chiffres donnés, l'autre une soustraction (moins.js). On commence par le code Java :

public static void main(String[] args) throws Exception {
     ScriptEngineManager manager = new ScriptEngineManager();
     ScriptEngine engine = manager.getEngineByName("JavaScript");
     engine.eval(new FileReader("plus.js"));
     Invocable inv = (Invocable) engine;
     System.out.println(inv.invokeFunction("compute",3,2));
}

Comme dans le premier exemple, nous récupérons le moteur JavaScript. Ensuite plutôt que directement évaluer du code, nous passons un fichier à la fonction eval(), qui contiendra notre code JavaScript. Nous commençons ici par plus.js, qui fera une addition. Vous pourrez ensuite utiliser moins.js. Enfin, nous invoquons la fonction compute() et lui passons deux arguments (3 et 2). Nous récupérons le résultat du traitement, qui est un Object, et qui contiendra ici un chiffre (un Double à vrai dire), nous l'affichons directement.

Voici le contenu de plus.js :

function compute(a,b) {
   return a+b;
}

Celui de moin.js :

function compute(a,b) {
   return a-b;
}

Aucun commentaire nécessaire ! Exécutez le programme avec plus.js, puis moins.js, les résultats seront 5.0 puis 1.0.

Nous voyons ici que nous pouvons très facilement avoir du code Java qui exécute une fonction dont il ne connait pas le contenu. Il en récupère le résultat sous forme d'un objet et peut le traiter. D'un autre coté, nous avons des fichiers contenant du code JavaScript qui traitent de façon différente deux arguments identiques, et renvoient le résultat. Dans une application gérant des plugins, il suffit alors que le nom du fichier JavaScript soit une variable, ou provienne d'un choix de l'utilisateur, et le tour est joué ! Attention tout de même, pour un système robuste, il est important de gérer les exceptions afin de réagir aux problèmes qui pourraient survenir.

Exemple 3 : Exporter des variables dans le script externe

Lorsque l'on mets en place un système de plugins, on souhaite que ces derniers puissent avoir accès aux informations de l'application, souvent par l'intermédiaire d'un noyau depuis lequel on accède à des variables. De cette manière le plugin agit de façon directe avec l'application, en consultant des informations, et en les mettant à jour. Nous allons dans cet exemple voir qu'il est tout à fait possible de faire cela avec Java et Rhino. Nous allons créer une classe Afficheur contenant la méthode affiche() qui se contente d'afficher dans la console une ligne de texte. Ensuite, nous allons instancier un objet de cette classe et l'exporter dans notre script JavaScript. Ce dernier utilisera dans son code l'objet exporté et appellera la méthode affiche() :

private static class Afficheur {
   public void affiche() {
       System.out.println("J'affiche !");
    }
}
public static void main(String[] args) throws Exception {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("JavaScript");
    engine.eval(new FileReader("afficheur.js"));
    Afficheur aff = new Afficheur();
    engine.put("monAfficheur",aff);
    Invocable inv = (Invocable) engine;
    inv.invokeFunction("affiche");
}

On voit ici qu'une fois l'objet Afficheur instancié, on l'exporte dans le script grace à la méthode put(). On indique que le nom de variable par laquelle l'objet sera accessible dans le script sera "monAfficheur". Enfin on appelle la fonction affiche() implémentée dans le script afficheur.js dont voici le contenu :

function affiche() {
   monAfficheur.affiche();
}

La fonction affiche() utilise donc la variable monAfficheur disponnible grace à l'export fait depuis le code Java, et appelle la méthode affiche(). Voilà notre liaison réalisée ! Depuis notre code JavaScript, nous accédons à un objet de notre application Java et pouvons l'utiliser. Ici nous ne réalisons qu'un simple affichage, mais tout est possible. Notez qu'il est possible de définir différent scope lors de l'export des variables permettant ainsi de leur donner des valeurs différentes selon le contexte utilisé lors de l'évaluation. Plus de renseignement dans la documentation.

Exemple 4 : Implémenter une interface

Nous progressons dans les fonctionnalités proposées pour réaliser une gestion de plugins. Mais il est possible d'aller plus loin, et de définir plus précisément les fonctionnalités offertes par les plugins. Pour cela, nous pouvons définir en Java une interface, et l'implémenter depuis un plugin. Une fois le code du plugin évalué, nous appelons une méthode depuis le code Java, dont l'implémentation a été réalisée depuis le code JavaScript ! Il devient alors possible de définir proprement en Java les méthodes qui seront à implémenter par les plugins, sans en connaitre le traitement évidemment, et d'utiliser ensuite dans le code des objets instanciant cette interface. Mieux que du blabla, le code : 

private interface Afficheur2 {
    void affiche2();
}
public static void main(String[] args) throws Exception {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("JavaScript");
    engine.eval("function affiche2() { println('affiche (2)'); }");
    Invocable inv = (Invocable) engine;
    Afficheur2 aff = inv.getInterface(Afficheur2.class);
    aff.affiche2();
}

Pour l'exemple, je n'utilise pas de fichier externe, mais directement une ligne de code JavaScript. Ce code implémente la méthode affiche2() de l'interface Afficheur2 en faisant un simple affichage de ligne. Nous créons un objet de type Afficheur2 puis récupérons l'instanciation de l'interface grâce à la méthode getInterface().

Enfin, nous utilisons ce nouvel objet et appelons la méthode affiche2() qui effectuera ce qui a été défini dans le code JavaScript, c'est à dire afficher une ligne de texte.

Nous voyons ici qu'il nous est donc possible d'écrire du code générique et de l'utiliser. C'est grâce aux plugins utilisés que instanciation est faite et nous pouvons ainsi pour un même code Java obtenir des comportement totalement différents.

Exemple 5 : Utilisation des classes Java depuis le code JavaScript

Comme dernier exemple et pour montrer toute la puissance de Rhino, nous créons un code Java qui se contentera d'évaluer un script :

public static void main(String[] args) throws Exception {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("JavaScript");
    engine.eval(new FileReader("frame.js"));
}

Le script en revanche est particulier, car il importe directement des classes Java et utilise les objets instanciés. Le script frame.js va créer une JFrame et l'afficher. Il affichera aussi le titre de la fenêtre, qu'il récupère directement depuis la propriété de la JFrame :

importClass(javax.swing.JFrame);
importClass(java.awt.Dimension);

var frame = new JFrame("hello");
frame.setSize(new Dimension(250,250));
frame.setVisible(true);
println(frame.title);

Exécutez le programme, une fenêtre apparait !

JFrame depuis un JavaScript


Voilà pour cette petite initiation au scripting avec Java et son application avec l'utilisation de plugins. Il y aurait surement beaucoup plus à dire, mais vous disposez maintenant d'une base pour débuter, et de l'alliance du Java et du JavaScript pour vos applications ! Ce sujet aura aussi été pour moi l'occasion de reprendre la plume sur ce blog, chose qui n'avait pas été faite depuis trop longtemps !


Fabien (eponyme)

De l'action sur le front de la conversion ODT

Aurélien Bompard

L’été s’est révélé très fructueux pour mes différents projets logiciels autour du format ODT. Voici un petit tour d’horizon des dernières nouveautés.

Dokuwiki

J’ai publié une nouvelle version de mon plugin d’export ODT pour Dokuwiki. Le changement principal est la prise en compte de la coloration syntaxique fournie par Dokuwiki. Vous pouvez voir un exemple de ce que ça donne sur cette page de mon wiki. Les couleurs du wiki et celles du document ODT ne sont pas exactement les mêmes, c’est normal :

C’est une fonctionnalité qu’on m’avait déjà demandé par le passé, et il s’est avéré que ce n’était pas si compliqué que ça à réaliser, principalement grâce au fait que la syntaxe CSS et la syntaxe des styles dans ODT XML sont très similaires. Ça c’est un format bien conçu, chapeau.

La nouvelle version du plugin est disponible en téléchargement depuis la page officielle du plugin ou directement chez moi. Vérifiez la signature GPG si vous le voulez/pouvez.

XHTML2ODT

Mon projet principal autour du format ODT est clairement XHTML2ODT, le convertisseur XHTML vers ODT (comme son nom l’indique :)" class="smiley ). Au début de la semaine, j’ai publié la version 1.0, ce qui est toujours une étape importante dans un projet logiciel. Le code qui la constitue a servi de fondation à deux plugins d’export depuis plusieurs mois maintenant, et est très largement couvert par plus d’une centaine de tests unitaires.

J’ai profité des jours suivants dans la semaine pour avancer le développement du projet sur certaines fonctionnalités qui semblaient intéressantes. Dans la même veine que pour le plugin Dokuwiki, j’ai ajouté la prise en compte de la coloration syntaxique fournie par deux moteurs : Pygments (en Python) et GeSHi (en PHP). Tout n’est pas encore parfaitement géré, mais le fonctionnement de la coloration syntaxique pour GeSHi ouvre peut-être la voie à une gestion plus large des styles CSS inclus directement dans le code HTML, par le biais de la balise <style> (c’est ainsi que GeSHi procède).

Enfin, GeSHi a aussi une fonctionnalité amusante : il peut numéroter les lignes du code. Cette fonctionnalité est aussi prise en compte et conservée lors de la conversion en ODT.

Tout ce nouveau code a été publié dans la version 1.1 de XHTML2ODT, que vous pourrez trouver sur la page de téléchargement. Au passage, j’en ai profité pour corriger un petit bug dans la conversion des espaces en mode préformaté, et pour ajouter un petit script de conversion en bash. Le but n’est pas de l’amener au même niveau de fonctionnalités que ses cousins Python et PHP, mais de montrer simplement comment mettre en œuvre les feuilles XSL.

Enfin, autre changement non technique mais non moins important : la licence s’est assouplie. J’ai passé le code de GPL v2 ou plus à LGPL v2 ou plus. Concrètement, cela signifie que si vous insérer le code dans une autre application, et que vous la distribuez, vous n’avez plus besoin de mettre votre application sous GPL. La seule contrainte restante est de publier les changements que vous avez fait sur le code XHTML2ODT lui-même, si vous en avez fait. C’est tout, rien d’autre.

Dernière petite nouvelle, j’ai écrit un article de quatre pages pour le magazine (papier !) Programmez! au sujet d’XHTML2ODT, il devrait sortir à la rentrée. Je vous tiendrai au courant, ça va de soi :)" class="smiley

Export ODT pour Trac

J’ai publié une nouvelle version du plugin d’export Trac, pour profiter des évolutions de la bibliothèque principale en ce qui concerne la coloration syntaxique. Vous pouvez voir le résultat en vous rendant sur la page WikiProcessing (fournie par défaut dans Trac) et en cliquant tout en bas sur le lien “OpenDocument”. Ou plus directement, en cliquant ici. Tada ! Le petit bout de code en C au milieu de la page est colorisé.

Dans cette nouvelle version, j’ai aussi tiré parti du système d’ajout de styles proposé maintenant directement dans XHTML2ODT, ce qui simplifie grandement le plugin. Si vous voulez faire un plugin d’export ODT pour une appli en Python, le plugin Trac peut être une bonne source d’inspiration.

Export ODT pour Dotclear

Là aussi, j’ai publié une nouvelle version du plugin d’export Dotclear pour profiter des avancées de la bibliothèque. Dotclear ne fait pas de coloration syntaxique par défaut, mais il existe un plugin pour cela, qui s’appelle syntaxehl, et qui utilise GeSHi en dessous. Le plugin peut fonctionner avec ou sans numérotation des lignes, les deux modes sont traduits en ODT.

Pour que ce soit plus parlant, voici un exemple de ce qui est produit par le plugin SyntaxeHL :

/* Commentaire */
class syntaxeHl
{   
 
    public static function registerFunc($wiki2xhtml)

    {   
        $dir = dirname(__FILE__).'/geshi/geshi/';
        $od = opendir($dir);

        while($f = readdir($od))
        {   
            if(is_file($dir.$f) && substr($f,-4,4)=='.php')

            {   
                $lang = str_replace('.php','',$f);
                $wiki2xhtml->registerFunction('macro:['.$lang.']',array('syntaxeHl','parse'));

            }
        }
    }
 
    public static function parse($text,$args)

    {   
        global $core;
        $settings = $core->blog->settings->syntaxehl;

 
        $text = trim($text);
        $args = preg_replace("/^(\[(.*)\]$)/","$2",$args);

        $geshi = new GeSHi($text,$args);
 
        if(!$settings->get('syntaxehl_enable_klink'))  $geshi->enable_keyword_links(false);

        if($settings->get('syntaxehl_enable_linenum')) $geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
        //if($settings->get('syntaxehl_enable_css_classes')) $geshi->enable_classes();

        $geshi->enable_classes();

Et maintenant, avec la numérotation des lignes :


  1. /* Commentaire */
  2. class syntaxeHl
  3. {
  4.  
  5. public static function registerFunc($wiki2xhtml)
  6. {
  7. $dir = dirname(__FILE__).'/geshi/geshi/';
  8. $od = opendir($dir);
  9. while($f = readdir($od))
  10. {
  11. if(is_file($dir.$f) && substr($f,-4,4)=='.php')
  12. {
  13. $lang = str_replace('.php','',$f);
  14. $wiki2xhtml->registerFunction('macro:['.$lang.']',array('syntaxeHl','parse'));
  15. }
  16. }
  17. }
  18.  
  19. public static function parse($text,$args)
  20. {
  21. global $core;
  22. $settings = $core->blog->settings->syntaxehl;
  23.  
  24. $text = trim($text);
  25. $args = preg_replace("/^(\[(.*)\]$)/","$2",$args);
  26. $geshi = new GeSHi($text,$args);
  27.  
  28. if(!$settings->get('syntaxehl_enable_klink')) $geshi->enable_keyword_links(false);
  29. if($settings->get('syntaxehl_enable_linenum')) $geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
  30. //if($settings->get('syntaxehl_enable_css_classes')) $geshi->enable_classes();
  31. $geshi->enable_classes();

Pour voir ce que cela donne en ODT, il vous suffit d’exporter ce billet de blog (le bouton est en haut à droite). Sympathique, non ?

Enfin, comme pour le plugin Trac, j’en ai profité pour déléguer l’ajout des styles à XHTML2ODT lui-même, ce qui simplifie grandement le code. Si vous cherchez à faire un plugin d’export ODT en PHP, le plugin d’export Dotclear est un bon candidat pour chercher l’inspiration.

Vous pouvez télécharger et installer le plugin depuis sa page sur DotAddict.

Flattr

Ça fait plusieurs semaines que j’entends parler de Flattr, ce nouveau service qui veut révolutionner le micro-paiement. Tuxicoman y a même consacré un article assez élogieux. En deux mots, la différence par rapport à PayPal, c’est qu’on affecte avant toute chose un “budget”, c’est à dire une somme fixe d’argent, qui sera distribuée entre les différentes choses pour lesquelles vous aurez cliqué sur le bouton “Flattr”. Son concernés bien évidemment les logiciels (libres ou pas), mais aussi la musique, les vidéos, les écrits, et autres.

À la fin du mois, votre budget mensuel est donc réparti, et si vous n’avez cliqué sur rien il est distribué à des organisation caritatives. Le budget minimum est de deux euros, c’est donc pas la mort. Si vous ne remplissez plus votre compte, il est désactivé temporairement bien sûr.

Le fait d’affecter un budget mensuel fixe, qui partira de toute façon, est à mon avis ce qui fait toute la différence. J’ai décider de me créer un compte pour voir ce que ça donne à l’usage, et pour soutenir les projets qui me plaisent. Bien sûr, j’en ai profité pour enregistrer les projets dont je suis l’auteur, mais que ce soit clair : je ne m’attends pas à rouler sur l’or du jour au lendemain ;-)" class="smiley Toutefois, si mes quelques bouts de code vous plaisent et que vous avez un compte sur Flattr, vous trouverez tous les liens sur mon profil. Les projets de conversion ODT ci-dessus sont normalement tous enregistrés.

À vous les studios

Voilà pour le flash nouveautés concernant mes projets de conversion ODT, j’espère que ces nouvelles versions vous plairont. N’hésitez pas à remonter les bugs sur les trackers associés, que vous pourrez trouve à partir de ma page d’index.

Bon, OK, certaines personnes pourraient objecter qu’il y aurait de meilleures façons de passer ses vacances.

Une plate-forme pour mes développements

Aurélien Bompard

Un bon gros weekend de geek. Mais il fallait le faire, ça commençait à trop tarder, avec les petits plugins que j’éparpille un peu partout, et les quelques développements que je réalise, il était temps que je mette en place une plate-forme pour assurer la maintenance de tout cela. Mon choix s’est porté sur Trac, voici pourquoi.

Mais pourquoi, au fait ?

J’avais installé un wiki sur mon site il y a quelques mois, pour noter tout ce qui n’a pas la même durée de vie qu’un article de blog. Et j’ai pris aussi l’habitude de développer avec Git, en hébergeant le code source chez Gitorious.

Gitorious est un très bon hébergeur de code source, basé sur Git donc, avec une interface assez jolie et ergonomique. Il est sous licence AGPL contrairement à son concurrent le plus direct, GitHub, qui est propriétaire. Vu mon penchant pour le logiciel libre, je me suis naturellement orienté vers Gitorious. Il a toutefois un défaut important : il n’y a pas de gestionnaire de tickets. C’est prévu, mais pour l’instant ils en sont à la conception, puisqu’ils voudraient mettre en place un système vraiment décentralisé et communiquant. On pourrait ainsi référencer et suivre les tickets ouverts sur d’autres instances de Gitorious.

C’est bien beau, mais pour l’instant ça veut dire que je dois trouver un autre moyen pour gérer les tickets des logiciels que je développe et auxquels je contribue. Ce n’est pas bien grave, il y a de nombreuses options en logiciel libre, donc j’ai cherché celle qui me conviendrait le mieux. Parmi mes contraintes, il y a :

  1. être en développement actif
  2. être relativement simple et facile à manipuler, je n’ai pas envie de noyer des rapporteurs de bugs potentiels
  3. la possibilité d’utiliser OpenID. Je n’ai pas envie de forcer les rapporteurs de bugs à ouvrir un nième compte à usage unique. En plus, grâce à OpenID, je déporte la problématique de gestion du spam sur le fournisseur OpenID, ce qui m’arrange pas mal.
  4. la possibilité de personnaliser sommairement l’interface, pour l’adapter au thème du reste du site.

Voici le résultat des courses (à mettre bien entendu en regard de mon besoin, qui n’est pas forcément celui de tout le monde).

La gestion de ticket en logiciel libre

Tout d’abord, j’ai cherché ce qui m’imposerait un minimum de dépendances. Sachant que mon espace web fonctionne très majoritairement en PHP/PostgreSQL, j’ai cherché des gestionnaires de tickets sur les mêmes technos.

Flyspray

Flyspray est un gestionnaire de tickets léger en PHP. Il a l’air pas mal du tout, mais son activité de développement semble relativement faible, et il n’a pas encore OpenID, donc j’ai laissé tomber.

Mantis

Attention, gros poids lourd. Mantis est un gestionnaire de tickets très puissant, mais qui échoue sans réserves au point n°2. Peut-être que ce sera un outils parfait pour quelqu’un dont c’est le métier, qui va rester toute la journée devant et apprendre à le maîtriser, mais pour moi et le quidam moyen qui ouvrira des tickets chez moi, c’est trop compliqué.

InDefero

Ça c’est très intéressant. InDefero est un petit dernier qui se veut être un clone de Google Code en PHP. Il a l’air vraiment très bien, mais pour l’instant il ne gère pas OpenID. Dommage, tout le reste avait l’air très bien.

Bon, ne voyant rien sortir en PHP/PostgreSQL, j’ai décidé de relâcher la contrainte PHP, et d’aller regarder ce qui se fait dans les autres langages.

Bugzilla

Je connais pas mal, vu que c’est ce qui est installé chez Fedora. Trop complexe à utiliser, trop complexe à installer, et trop gourmand en ressources.

Redmine

Là, on a du très lourd. Redmine est un gestionnaire de projets écrit en Ruby, qui fait exactement tout ce que je veux, et même plus. Le mec cool qui m’héberge a en plus déjà installé un Redmine sur le serveur, mais vu que mes réglages sont assez différents (accès à tout le monde), il faudra que je m’installe une autre instance. A priori pas un problème.

Trac

Trac, je connais déjà. C’est un très bon gestionnaire de projets, simple et efficace, écrit en Python (donc un plus pour moi), et pour lequel j’ai déjà écrit plusieurs plugins. Lui aussi, il fait tout ce que je veux.

Redmine ou Trac

Les deux finalistes sont donc Redmine et Trac. Je me suis dit que c’était l’occasion d’essayer Redmine, donc j’ai essayé de comprendre comment c’était installé sur la machine. Et bien, je dois dire que je n’ai pas compris. C’est manifestement installé dans Apache avec Passenger, mais à part ça je n’ai rien réussi à recoller. En plus j’ai essayé de ne pas toucher aux fichiers pour éviter de casser quoi que ce soit, ce qui fait qu’au bout d’une demi-heure de farfouillages et de recherches sur le net, j’ai perdu la motivation. Ça marche, mais je ne comprends pas pourquoi, et ça j’aime pas tellement.

Donc bon, je me suis rabattu sur le bon vieux Trac que je connais bien maintenant. Finalement, le fait qu’il soit écrit en Python fait que je peux mieux le maîtriser, et écrire des plugins si nécessaire, plugins qui pourront peut-être servir à d’autres.

Comme j’ai plusieurs projets indépendants, je vais faire gérer plusieurs “environnements” à la même instance de Trac, ce qui économisera la mémoire. Cela se fait assez facilement grâce à la fonctionnalité correspondante de Trac, c’est à dire --env-parent-dir pour tracd ou la variable d’environnement TRAC_ENV_PARENT_DIR dans mod_wsgi (que j’utilise). Toutes les instructions sont sur le site de Trac.

Adaptations

Alors, reprenons le besoin : mon code est hébergé chez Gitorious, il ne me faut donc qu’un gestionnaire de tickets. Comme Trac fournit un wiki, je vais m’en servir à la place de celui de Gitorious, pour pouvoir référencer les tickets d’incidents, et profiter des possibilités d’adaptation graphique de Trac. Par contre, Trac fournit aussi un gestionnaire de code source, et ça je n’en ai pas besoin.

Le navigateur de sources

Et oui, si mon code est hébergé chez Gitorious, ça serait bizarre de se servir de l’explorateur de code de Trac. J’ai donc cherché plusieurs solutions pour remplacer le navigateur de Trac par un lien vers Gitorious, mais rien ne s’est révélé très satisfaisant. En plus de ça, la syntaxe wiki de Trac permet de référencer un fichier dans l’arborescence des sources, avec un lien du type [source:monfichier.ext]. Dans le meilleur des mondes, ces liens arriveraient directement dans Gitorious, sur le bon fichier, et même à la bonne ligne.

J’allais quand même pas rater une occasion d’améliorer le monde, non ? ;-)" class="smiley Donc j’ai fait un plugin Gitorious pour Trac qui fait exactement ça : il remplace le lien du navigateur de sources par un lien vers Gitorious (il faut configurer le nom du projet cible), et intercepte tous les liens vers les sources en les réécrivant pour pointer vers le navigateur de Gitorious. Si vous êtes dans le même cas que moi, n’hésitez pas à vous servir de mon plugin.

Il y a des fonctionnalités de Trac qui n’existent pas chez Gitorious, je n’ai donc pas tout remplacé, mais c’est déjà assez sympa.

Autres plugins

Trac dispose d’un plugin pour l’authentification par OpenID, donc y’a qu’à installer. Il me fallait aussi évidemment le plugin Git, histoire que mes modifications apparaissent dans la timeline. Et j’en ai bien sûr profité pour installer mon plugin d’export ODT. Enfin, j’ai installé le plugin TranslatedPages pour faciliter la traduction des pages du wiki.

La quantité de plugins disponible pour Trac est vraiment phénoménale, on trouve tout ce qu’on veut !

Graphisme

Je n’allais pas quand même laisser les visiteurs et les rapporteurs de bugs avec une rétine intacte, c’est pas le genre de la maison. J’ai donc modifié le template de Trac par défaut pour que mon espace de développement soit aussi moche que le reste de mon site. La cohérence, c’est important. Les instructions se trouvent sur le site de Trac, avec quelques exemples pour les cas classiques (ajouter une CSS, etc.).

Le langage de templates utilisé par Trac est Genshi, c’est très proche du XML/XSL, ça me rappelle le langage TAL de Zope, donc c’est marrant. En plus je commence à bien le connaître puisque je l’utilise au boulot.

J’ai aussi modifié la page qui sert d’index aux projets. Là aussi, les instructions sont déjà toutes prêtes sur le site de Trac. Je vous laisse souffrir admirer.

Téléchargements

Il existe plusieurs moyens de proposer un espace de téléchargement de fichiers dans Trac. On peut tout simplement faire un lien vers un répertoire d’Apache. On peut ajouter un lien dans la barre de navigation principale pour pointer vers ce répertoire. On peut aussi attacher les fichiers à une page du wiki, mais ce n’est pas ce qu’il y a de plus pratique. Et évidemment, il y a trois plugins différents pour gérer un véritable espace de téléchargement (Trac : there’s a plugin for it).

Mais moi je voulais quelque chose d’un peu différent : je voulais pouvoir générer automatiquement des archives de la branche de développement toutes les nuits, je voulais pouvoir signer les publications, etc. J’ai donc opté pour un dossier dans Apache, qui est rempli par un script externe. Ce script tourne toutes les nuits et créée deux archives (tar.gz et zip) pour chaque tag de chaque projet, ainsi que deux archives de la branche de développement. Il en profite pour générer un bel index, et un flux Atom. C’est pas très compliqué, il y a peut-être 50 lignes de shell, donc pour l’instant je ne le publie pas sauf si quelqu’un se déclare intéressé. La génération du flux Atom est laissée à un autre script que j’ai écrit.

Conclusion

Je dispose maintenant d’une bonne plateforme d’hébergement de code, simple, bien intégrée avec Gitorious, extensible, automatisée, et surtout que je connaît bien.

Si jamais je décide de pousser l’auto-hébergement plus loin et de me passer de Gitorious, Trac est tout à fait à même de répondre au besoin. Je ne tire pas un trait sur Redmine pour autant, peut-être qu’un jour j’aurais l’occasion de l’utiliser activement. En attendant, le fait que je sois capable d’écrire des plugins pour Trac est quand même un facteur de poids dans la balance.

Si vous voulez plus de détails sur l’implémentation de tout ça, n’hésitez pas à me contacter (mais c’est pas très sorcier au final).

Des scripts divers et variés

Aurélien Bompard

J’ai (enfin) fait une page pour décrire les quelques scripts que j’ai fait et qui pourraient servir à d’autres. Pour faciliter la lecture des utilisateurs de flux RSS et pour éviter un clic aux autres, je vais tout recopier dans ce billet, mais la page de référence est sur mon wiki.

J’ai écrit un certain nombre de scripts pour me faciliter la vie de tous les jours. Et oui, comme tout bon informaticien, je suis prêt à passer 8 heures à peaufiner un script qui me fera gagner 30 secondes tous les mois… Mais bon, là n’est pas la question.

Mes scripts

Il se trouve que je pense que certains de ces scripts pourraient être utiles à d’autres, donc dans le plus pur esprit du Logiciel Libre, je les met à disposition sur cet espace de développement. Tous ces scripts sont sous licence GPL v3. Voici une petite présentation.

backup-delicious.py

Un simple script pour sauvegarder ses signets stockés sur delicious.com dans un fichier XML, au cas où Delicious aurait un problème (site cassé, perte de données, politique commerciale à la con, revente de Yahoo, etc.).

birthdaysfromvcard.py

Le script créé un fichier iCalendar avec les anniversaires des gens trouvés dans un fichier vCard (vcf).

Très pratique pour ne plus oublier les anniversaires des ses connaissances (voire de sa famille…) :)" class="smiley

files2feed.py

Le script créé un ficher Atom XML (équivalent RSS) à partir des derniers fichiers modifiés ou ajoutés dans un répertoire et ses sous-répertoires.

Cas d’utilisation : je partage mes fichiers par le web, et j’aimerais bien proposer un flux RSS pour les mises à jour et les nouveaux fichiers partagés (ici c’est un flux Atom, mais c’est le même principe).

make-songs-list.py

Le script créé un PDF à partir des chansons disponibles pour Performous, un excellent jeu de karaoké.

Il utilise la pochette de l’album si elle est dispo, et essaye d’en mettre un maximum sur le minimum de pages, pour qu’il n’y ait plus qu’à imprimer et à distribuer aux joueurs.

podcast-transcode.py

Cas d’utilisation : je dispose d’un lecteur de vidéos portable que j’utilise dans les transports en commun, mais il n’est pas assez puissant pour décoder les vidéos d’aujourd’hui (résolutions assez élevées, codecs gourmands, etc.) Il faut donc que je convertisse et que je redimensionne les vidéos des podcasts auxquels je suis abonné.

Fonctionnement : le script prend en entrée un flux RSS, et convertit toutes les vidéos incluses en attachement au format AVI/DivX/MP3, en les redimensionnant à la taille demandée. Une fois la conversion effectuée, la balise d’attachement est mise à jour pour pointer sur l’adresse de la vidéo convertie.

Pour les vidéos de TED, le script peut même ajouter les sous-titres s’ils sont dispo, et si le script tedtalksubs.py est installé (c’est à dire dans le PATH). C’est un autre de mes scripts, téléchargeable au même endroit (enfin, dans ce cas-là, dire “de mes scripts” est un peu fort, puisque j’ai quasiment tout pompé sur un autre).

Le script tedtalksubs.py est indépendant, il permet de lister, télécharger et convertir au format SRT les sous-titres des vidéos de TED.

rss-mirror.py

Cas d’utilisation : quand je tombe sur une page web intéressante mais que je n’ai pas le temps de lire là tout de suite, je la sauvegarde dans Instapaper. Le service me fournit ensuite un flux RSS des pages en attente de lecture. Pour utiliser au mieux le temps que je passe dans les transports en commun, j’aimerais y lire ces pages, mais je n’ai pas d’abonnement internet mobile. Par contre, j’ai un lecteur portable qui dispose d’un navigateur web.

Fonctionnement : le script prend un ou plusieurs flux RSS en entrée, et fait un miroir local de toutes les entrées en utilisant wget ou httrack. Le niveau de profondeur des liens récupérés est configurable, mais par défaut il ne prend que la page indiquée, pas les liens trouvés sur cette page. Il créé ensuite un index des pages téléchargées en utilisant le fabuleux iUI.

Le répertoire de téléchargement peut ensuite être synchronisé sur un lecteur portable, qui n’a alors besoin que d’un navigateur web. Dans mon cas, je l’utilise avec Instapaper, mais n’importe quel flux RSS peut être utilisé. Par exemple, des signets sur Delicious, les favoris de Tiny Tiny RSS ou Google Reader, etc.).

C’est en quelque sorte le lecteur de flux hors-ligne du pauvre :)" class="smiley

Conclusion

Voilà, si vous avez des commentaires, des remarques, ou des propositions d’amélioration à faire sur ces scripts, n’hésitez pas. Une petite description est disponible en en-tête de chacun d’eux, et vous pouvez les lancer avec --help pour connaître les options disponibles. Ils nécessitent tous au moins python 2.5, les autres dépendances étant spécifiques à chacun des scripts.

Statistiques de téléchargement

Remi Collet

Un compteur dans le menu de droite permet désormais de connaitre le nombre de RPM télécharger depuis le dépôt remi.

Ce compteur est actualisé quotidiennement.

Pour afficher ce compteur, j'ai créé mon premier Widget pour DotClear. C'est un truc très trivial. On configure le titre du widget et le chemin du fichier XML contenant les données qui seront affichées en liste. Mais si cela peut en intéresser certains, vous pouvez télécharger l'archive : downloadStat-0.1.tgz (1 Kio). C'était surtout l'occasion... Lire Statistiques de téléchargement

XHTML2ODT est annoncé sur LinuxFR

Aurélien Bompard

Pour essayer de faire connaître XHTML2ODT, j’ai écrit un article sur LinuxFR qui en fait la promotion, ou du moins qui annonce à tout le monde que ça existe. Oui oui, écrire un article sur LinuxFR, c’est parler au monde. Rien que ça ;-)" class="smiley

Je rappelle brièvement de quoi il s’agit : XHTML2ODT a pour objectif de convertir une page web (HTML) en document ODT, éditable ensuite par tous les traitements de texte qui utilisent ce format, notamment OpenOffice. Pour ce faire, il s’appuie sur un document OpenOffice “modèle”, dans lequel on peut définir les styles que l’on veut.

Comme il n’y a pas de gestionnaire de bugs pour l’instant, vous pouvez éventuellement utiliser les commentaires de cet article pour ça. Mais bon, il faudrait que j’en installe un quand même, ce serait plus propre. Je pencherais bien pour un Trac, mais maintenant faut voir si mon gentil hébergeur sera d’accord…

Mise à jour : Finalement j’ai fait un site dédié pour XHTML2ODT, avec Trac. Le code reste hébergé par Gitorious pour l’instant.

llaumgui.com sous jQuery

Guillaume Kulakowski

Après plusieurs années de résistance à vouloir faire tourner Dotclear avec MooTools, j'ai décidé de raccrocher les gants. Ce blog tourne à présent sous jQuery, la solution de framework Javascript officiellement retenue par Dotclear. Vraisemblablement, je ne devrais plus trop maintenir mon script dcRemember permettant de faire tourner les fonctions JS de bases de dotclear sous MooTools.

Mais pourquoi lâcher MooTools alors que c'est le meilleur framework JS au monde (si ça c'est pas un troll ;-)) ? La raison est simplement qu'avec l'intégration de jQuery de plus en plus forte dans eZ Publish, je risque d'utiliser de moins en moins MooTools. J'avais donc besoin d'un labo sous jQuery et l'un des buts premiers de ce blog est justement de servir de labo.

GLPI : Notifications et modèles de message

Remi Collet

Une des demandes d'évolution la plus commune est la personnalisation des messages de notification envoyés aux utilisateurs. Il existe même des correctifs (non officiels) disponibles pour GLPI 0.72.

La possibilité d'affiner les notifications pour chaque entité est aussi un besoin évident pour les grosses structures.

Version cible 0.78. Désormais la configuration des options de notifications est réalisée à partir de la fiche d'une entité : Chaque administrateur d'entité peut donc activer ou désactiver les notifications pour son parc. Évidement l'administrateur général conserve le privilège d'accorder ce droit et de désactiver globalement une notification... Lire GLPI : Notifications et modèles de message

Nouveau plugin pour Dotclear : notes de bas de page

Aurélien Bompard

Quand on écrit un billet avec Dotclear, on dispose de deux modes : le mode “wiki” ou le mode “xhtml”.

En mode wiki, on dispose d’une syntaxe codifiée et simplifiée, mais il faut apprendre cette syntaxe, et ce qui est affiché à l’écran est très différent de ce qui sera affiché sur le site ensuite. Il y a quelques temps, je préférais ce mode de rédaction (c’est plus rigolo :)" class="smiley ), mais c’est moins facile d’accès pour les débutants, et après tout, on ne fait pas des programmes que pour les informaticiens.

Le second mode affiche le texte dans la zone d’édition avec le formatage qu’il aura à l’affichage final, un peu comme votre traitement de texte préféré[1]. En plus, et c’est important pour moi, il y a un onglet “source” qui permet de voir le XHTML généré, et éventuellement de faire des retouches, ou d’ajouter des blocs de code.

Mais voilà, le mode wiki a beaucoup plus de fonctionnalités que ce qui est repris dans la barre de formatage du mode xhtml. Notamment, il y en a une que j’aime bien utiliser : les notes de bas de page[2]. En mode wiki, on entoure la note par des symboles “$$”, et Dotclear génère automatiquement l’appel de note, la note de bas de page, et les liens entre les deux. C’est très pratique, je voulais retrouver cette possibilité en mode xhtml.

J’ai donc fait un plugin pour Dotclear qui ajoute un bouton à la barre de formatage, et qui génère les appels de note, la zone de notes de bas de page, les liens entre les notes, tout en s’occupant d’incrémenter le numéro de note à chaque fois. Le plugin s’appelle footnotesToolbar, et il est disponible sur le lab Dotclear. Si vous pensez qu’il peut vous être utile, n’hésitez pas à le télécharger et à me donner votre avis !

Bonne rédaction :)" class="smiley

Mise à jour : j’ai aussi ajouté un bout de code pour lui faire afficher les notes de bas de page au survol de l’appel de notes. C’est une très bonne idée que j’ai piquée à Dokuwiki, sur conseil d’antistress (voir commentaires ci-dessous).

Notes :

[1] par exemple OpenOffice.org.

[2] c’est ça

Export ODT pour Trac

Aurélien Bompard

Et voilà, je viens de boucler une première version utilisable d’un plugin d’export ODT pour Trac.

Parce que une fois c’est bien, deux fois c’est mieux, et trois fois bonjour les dég… euh, non en fait ça devrait marcher pareil que le reste. Il faut excuser mon humour pourri, mais je cauchemarde rêve de l’XML en ce moment.

Après avoir bien cherché, je suis obligé de me rendre à l’évidence : il n’y a toujours personne d’autre que moi qui ait écrit de feuille de style XSLT pour convertir de l’HTML en ODT. Dans l’autre sens, il y en a pléthore, mais dans ce sens, rien. Comme j’ai réutilisé pour le plugin Trac les feuilles que j’avais écrites pour le plugin Dotclear, je me suis dit qu’il n’était pas forcément complètement stupide d’en faire un projet séparé. C’est donc ce que j’ai fait, j’ai ouvert un projet sur Gitorious appelé xhtml2odt (oui, très original, je sais). J’en ai profité pour écrire tout un tas de tests unitaires, pour m’éviter les régressions et aussi parce que ça se prête quand même bien aux conversions :)" class="smiley .

Je vais maintenant adapter le plugin Dotclear pour qu’il utilise ces feuilles, qui ont été légèrement améliorées lors de la réalisation du plugin Trac.

Si vous décidez de les utiliser pour un autre plugin d’export ODT dans une autre appli, ce serait cool de m’envoyer un petit mail, ça m’intéresse :)" class="smiley

Voilà, si vous utilisez Trac et que vous êtes intéressé par l’export ODT des les pages du wiki, n’hésitez pas à tester ! Ce n’est pas encore parfait mais c’est déjà suffisamment avancé pour que vos retours soient utiles.

Mise à jour : le projet XHTML2ODT a maintenant son propre site web. Le code reste hébergé par Gitorious pour l’instant.

GLPI : Urgence - Impact - Priorité

Remi Collet

ITIL définit la priorité d'un ticket par le produit de son urgence et de son impact sur l'activité. GLPI qui ne définissait que la priorité, suit désormais cette bonne pratique.

Version cible 0.80. L'administrateur configure une matrice de calcul (une matrice par défaut est fournie à l'installation) : Cette page de configuration permet de définir le nombre de niveau d'urgence et d'impact utilisés, 3 dans l'exemple, jusque 5. L'urgence est définit par l'utilisateur (la victime) lors de l'ouverture du ticket (sauf si 1... Lire GLPI : Urgence - Impact - Priorité

Revues de la semaine

Remi Collet

Soumises : Bug 542075 - Review Request: php-pear-Net-URL2 - Class for parsing and handling URL Bug 542077 - Review Request: php-pear-HTTP-Request2 - Provides an easy way to perform HTTP requests Bug 542084 - Review Request: php-pear-HTTP-OAuth - Implementation of the OAuth spec Bug 542087 - Review Request: php-pear-Services-Twitter - PHP... Lire Revues de la semaine