Archives du mot-clé unix

bash

Restaurer son répertoire courant lors d’une ouverture de session sous Linux

Je travaille tous les jours sur un serveur Linux. Je me suis rendu compte que chaque matin, lorsque la session de la veille a été coupée, je retape systématiquement la même commande : “cd le/repertoire/vers/mon/projet/actuel”.

Taper ces quelques mots chaque matin n’est pas vraiment la mer à boire, mais c’est en revanche déjà plus pénible de devoir le refaire 15 fois dans la journée, lorsque surviennent des coupures réseaux qui me font perdre ma session…

J’ai décidé ce matin, de trouver une solution simple et légère pour palier le problème.

La solution

L’idée est toute bête : à chaque changement de répertoire, je stocke le nouveau répertoire courant dans un fichier à la racine de mon “home-directory”. À l’ouverture de session, je lis ce fichier s’il existe et change le répertoire courant en conséquence.

Réalisation

La réalisation se divise en deux étapes :

  1. L’écriture de deux scripts simples pour sauvegarder et restaurer le répertoire courant;
  2. la redéfinition de la commande “cd” pour qu’elle exécute les scripts précédents.

Les scripts

J’ai choisi de stocker mes scripts personnels dans un répertoire nommé “bin“, à la racine de mon “home”. Vous pouvez bien évidemment les mettre où bon vous semble.

Le premier script se nomme savepwd.sh, et voici son contenu :

Rien de fou ici : on change le répertoire (on préfixe cd par un antislash pour éviter d’appeler un éventuel alias; ce qui tombe bien puisque c’est ce qu’on va faire dans la deuxième partie) et on inscrit dans le fichier ~/.savedpwd le répertoire courant après changement.

On spécifie $2 pour une raison que nous verrons plus tard.

Le deuxième script se nomme loadpwd.sh et contient les lignes suivantes :

Encore une fois, rien de spécial : on teste l’existence du fichier ~/.savedpwd, et s’il existe, on remplace le répertoire courant par celui qui y est inscrit.

Les alias

Modifions maintenant le script de démarrage de session, dans mon cas il s’agit du fichier ~/.bashrc, et ajoutons les lignes suivantes :

On remplace simplement la commande “cd” par un appel au script ~/bin/savedpwd.sh et on fait un appel à ~/bin/loadpwd.sh lors du premier chargement du script.

Vous vous posez certainement la question, pourquoi ce paramètre “dummy” ? Tout simplement, si on utilisait le premier paramètre ($1) on a un bogue dans le cas où l’appelant a fait un appel à set dans sa console, et quand on appelle cd sans paramètres : en effet, set définit $1 dans la console actuelle, et devient la valeur par défaut quand le script est appelé sans paramètre.

En faisant par exemple :

Au lieu de retourner à la racine du “home-directory”, on se retrouve dans le répertoire “toto“, s’il existe. En ajoutant le paramètre “dummy“, on empêche le problème de se produire en s’assurant qu’aucun paramètre par défaut n’est appelé lors de l’invocation de ~/bin/savepwd.sh.

C’est fini !

Ça y est, vous pouvez relancer votre session pour prendre en compte les modifications : dès que vous changerez de répertoire courant, celui-ci sera sauvé puis restauré lors de la prochaine ouverture de session !

J’espère en tout cas que cette astuce pourra vous servir ! N’hésitez pas à me suggérer des améliorations ou d’éventuels problèmes (sécurité, utilisation) liés à ces scripts. Bon code ! :)

apache

Installation d’un serveur Git avec Apache sous Linux

Cet article a pour but de vous montrer comment mettre en place rapidement et simplement un serveur Git sous Apache, avec un ou plusieurs dépôts, et une authentification par identifiant/mot de passe.

Démarrage

Nous partirons du principe que vous avec un Linux moderne (j’utilise Debian, mais la démarche devrait être similaire sur n’importe quelle autre distribution).

Installez tout d’abord les paquets nécessaires :

Vérifiez ensuite que git est installé :

Si vous obtenez une sortie similaire, c’est que Git est bien installé.

Création du dépôt

Nous allons commencer par attribuer un répertoire à nos futurs dépôts Git. J’ai personnellement choisi “/home/git” mais libre à vous de choisir autre chose. Notez simplement ce chemin, car nous allons y faire référence quelques fois dans les prochaines étapes.

Tapez les commandes suivantes :

www-data est le nom du user sous lequel tourne le démon apache.

Nous placerons tous les dépôts Git dans le répertoire /home/git/repositories. Dans notre exemple, nous créons un dépôt nommé “depot.git” :

La commande “git init --bare” créé un dépôt Git “bare”, c’est à dire un dépôt qui ne possède pas de copie de travail : c’est un dépôt de “stockage” uniquement; vous n’y ferez jamais de commit mais seulement des “push” ou des “pull”.

La commande “git update-server-info” met à jour les données du serveur dans le dépôt, pour lui permettre d’être accédé à distance.

Vous devrez répéter cette dernière étape pour chaque dépôt que vous souhaiterez créer. Notez que vous pouvez également créer une arborescence de répertoires pour organiser plus finement les dépôts.

Paramétrage d’Apache

Nous devons ensuite informer Apache de la présence de nos dépôts Git. Pour ce faire, nous créons un fichier nommé “git” dans le répertoire “/etc/apache2/sites-available” qui contient les paramètres suivants :

Nous définissons une authentification de type “identifiant/mot de passe” mais libre à vous de choisir une autre méthode d’authentification.

Nous supposons ici que le serveur apache possède déjà au moins un “virtual host”. Si vous souhaitez par exemple limiter l’accès à un “virtual host” en particulier (utile dans le cas où l’on souhaite forcer le passage en HTTPS par exemple), il suffit d’imbriquer le code ci-dessus dans la déclaration de ce “virtual host”.

Lorsqu’une personne tentera d’accéder au sous répertoire “/git/” de notre serveur web, elle devra saisir un identifiant et un mot de passe ayant été renseigné dans le fichier “/home/git/passwd“.

Créons maintenant ce fichier grâce à la commande suivante :

Le programme vous invite à saisir un mot de passe pour votre utilisateur. Créez autant d’utilisateurs que vous le souhaitez à l’aide de la même commande (omettez le paramètre “-c” lors des fois suivantes).

Vérifiez que l’utilisateur www-data ait accès à ce fichier, uniquement en lecture.

Vous n’avez plus qu’à activer le site, les modules dav et dav_fs puis à redémarrer apache pour mettre en ligne le dépôt :

Le dépôt devrait désormais, être accessible.

Un petit test pour la route

Testons l’accès au dépôt depuis un autre poste sur le réseau. Vous pouvez bien évidemment utiliser un poste Windows ou Linux, graphique ou en ligne de commande.

Pour la simplicité de l’exemple (et parce que j’adore ça), nous utiliserons la ligne de commande :

Cette commande devrait réussir et créer un répertoire nommé “depot” dans le répertoire courant.

Ignorez l’avertissement qui dit que le dépôt est vide : cela est tout à fait normal.

Remarque : si vous avez choisi d’héberger votre dépôt en HTTPS, git refusera peut-être de s’y connecter si votre certificat n’est pas signé par une autorité de certification reconnue. Vous pouvez définir la variable d’environnement “GIT_SSL_NO_VERIFY=1” pour ignorer l’erreur temporairement. Ne vous servez évidemment pas de cette variable comme solution à long terme !

Conclusion

La mise en place d’un serveur Git accessible sous Apache est donc plutôt simple. Notre configuration ici est minimale et pourrait encore être améliorée, notamment en permettant une affectation des droits plus précise pour les différents dépôts au sein d’Apache.

Vos recherches sur l’Internet vous auront peut-être conduit à d’autres solutions, utilisant gitosis ou encore ssh. Ces approches proposent d’autres modes d’authentification (par certificat ou en ssh avec un compte sur le système) qui peuvent être très intéressantes mais je n’ai personnellement pas réussi à les faire fonctionner correctement pour l’instant. Si vous avez des ressources à partager à ce sujet, n’hésitez pas à me les transmettre :D

J’espère en tout cas que cet article vous aura été utile, et comme d’habitude, n’hésitez pas à me faire part de vos commentaires ! :)

inline

La directive “inline” démystifiée

Le C++ est sans conteste l’un des langages les plus complets mais aussi les plus complexes existant dans le monde du développement en entreprise. Ses grandes flexibilité et diversité en font à la fois un langage puissant et dangereux. Il ne s’agit pas ici d’en faire une nouvelle présentation; de nombreux ouvrages lui sont déjà consacrés : qu’il s’agisse des “design patterns” ou de fonctionnalités générales, il y en a vraiment pour tous les goûts.

Cependant, j’ai décidé aujourd’hui de traiter d’un point en particulier, souvent mal perçu par les débutants et parfois même par des gens plus expérimentés : il s’agit de la directive inline.

Piqûre de rappel

Avant d’avancer sur le chemin de “l’inlining”, rappelons quelques principes élémentaires du C++.

Remarque : En tant que programmeur expérimenté, vous connaissez probablement déjà tout ce qui suit. Vous devriez tout de même prendre le temps de lire cette partie pour deux raisons : la première, ça ne fait jamais de mal. Et la deuxième : si jamais j’écrivais une bêtise, vous pourriez gentiment me le faire remarquer ! :D

Le C++ est un langage compilé (par opposition à langage interprété), ce qui signifie qu’il induit la génération d’un “binaire” lors d’une phase appelée compilation. Ce binaire peut être un fichier exécutable (.exe sous Windows), une bibliothèque (.so/.a sous Unix, .lib/.dll sous Windows) ou un fichier objet intermédiaire (.o sous Unix, .obj sous Windows).

La phase que l’on nomme “compilation” est en fait séparée en trois étapes successives :

  1. Le prétraitement (ou “preprocessing”), qui va se charger de remplacer les différentes macros présentes dans le code par leur véritable valeur. Le résultat de ce prétraitement est passé au “compilateur”.
  2. La compilation, qui transforme le code pré-traité en langage machine au sein de fichiers objets. En pratique, il y a un fichier objet généré par unité de traduction (ou “translation unit”).
  3. L’édition des liens, qui rassemble les fichiers objets générés au sein d’une seule entité (une bibliothèque dynamique ou un exécutable). Si on a déclaré et utilisé une fonction mais que son implémentation est absente, cette étape ne passe pas.

Remarque : Habituellement, dans le cas d’une bibliothèque statique, l’édition des liens n’est pas effectuée : il s’agit d’une simple concaténation des fichiers objets.

Les bonnes pratiques du C++ dictent ensuite que lorsque l’on écrit le code d’une classe, on place sa définition (et donc sa déclaration) dans un fichier dit “header“, et son implémentation dans un fichier “source“.

Il existe une règle nommée “règle de la définition unique” (ou ODR : “One Definition Rule“) qui dit que l’on peut déclarer autant de fois que l’on veut une classe, une fonction, etc. mais qu’on ne peut la définir qu’une seule fois. Nous verrons plus tard en quoi inline influe à ce niveau.

Un exemple simple

Prenons un exemple tout simple avec une classe “Person” qui représente une personne. :)

Voici le fichier header :

Dans ce header, nous avons déclaré et défini le type Person.

Son implémentation, elle, va dans le fichier source :

Si nous reprenons les trois étapes de la compilation, voici ce que se passe pour chacun des fichiers :

Le processus commence par le choix de l’unité de traduction à compiler : ici, il s’agit du fichier “person.cpp”.

  • Le préprocesseur analyse chaque ligne, procède à l’inclusion du fichier “person.hpp” (directive #include) tel qu’on le ferait avec un copier-coller. Au passage, tous les commentaires dans les fichiers sont supprimés, et les éventuelles macros sont remplacées.

On se retrouve avec un fichier qui se rapproche théoriquement de ça :

  • Le compilateur vérifie la syntaxe de l’ensemble du fichier et compile chaque implémentation de fonction (ou méthode) qu’il rencontre. Ici, les codes du constructeur Person::Person() et du “getter” name() sont effectivement transformés en langage machine au sein d’un fichier objet.
  • Enfin, si le programme fait référence à Person::Person() ou Person::name(), l’édition des liens associera l’appel de fonction à son adresse effective.

Les idées fausses sur la directive “inline”

S’en suit ici un florilège des idées reçues que j’ai déjà entendu (ou prononcé :D) ça et là sur inline :

  • “Ça sert à ordonner au compilateur de ne jamais compiler le code de la fonction.”
  • “C’est quand on écrit directement du code dans la définition d’une classe.”
  • “C’est pour accélérer les appels à une fonction.”
  • “Ça sert à déclarer des macros intelligentes.”
  • “Ça indique que la fonction a une liaison interne.”

En réalité, voici la raison d’être du mot clé inline, telle que définie par Bjarne Stroustrup :

The inline specifier is a hint to the compiler that it should attempt to generate code for a call of the inline function rather than laying down the code for the function once and then calling through the usual function call mechanism.

Pour ceux que l’anglais rebute :

La directive inline est une information donnée au compilateur lui indiquant qu’il devrait essayer de générer du code pour chaque appel de la fonction plutôt que de générer une seule fois le code de façon générique et d’utiliser le mécanisme habituel d’appel de fonction.

En gros, on apprend que la directive inline n’est pas un ordre, mais une simple indication, que le compilateur est d’ailleurs libre de refuser. Souvenez-vous que c’est son travail d’optimiser le code généré, pas le vôtre.

En pratique, on utilisera donc pas inline pour des raisons d’optimisation, mais simplement pour modifier la “One Definition Rule”. En effet, là où une fonction ne doit habituellement avoir qu’une seule définition parmi toutes les unités de traductions, le fait de la rendre inline change la règle et indique que la fonction doit avoir la même définition dans chacune des unités de traduction qui l’utilise.

Un exemple

Prenons pour exemple le célèbre cas de la fonction factorielle.

Remarque : Le choix de cet exemple n’est pas innocent. Bjarne Stroustrup utilise lui-même cet exemple lorsqu’il parle de la directive inline.

Une implémentation naïve de factorielle est la suivante :

Note : Cette fonction n’est pas optimale (on répète inutilement le test “(n <= 1)" à chaque itération. Mais elle convient très bien pour notre exemple.

Supposons que cette fonction est déclarée dans un header de notre bibliothèque et qu’un utilisateur de cette bibliothèque utilise quelque-part dans son code la fonction, par exemple :

Lors de la compilation de ce code, il peut se passer plusieurs choses :

  1. Le compilateur peut décider de compiler la fonction factorial comme n’importe qu’elle autre fonction. Elle aura en pratique un passage de paramètre, une pile d’appel etc.
  2. Ou il peut décider de remplacer factorial(6) par 6 * factorial(5) directement.
  3. Enfin, un compilateur très intelligent peut carrément décider “d’inliner” complètement l’appel et de remplacer factorial(6) par 720, optimisant de ce fait drastiquement le programme.

On notera que l’appel d’une fonction inline est sémantiquement identique à celle d’une fonction “classique”. Il est possible d’en hériter, de la surcharger, etc.

Utilisation au quotidien

Voici quelques usages corrects de fonctions “inline” :

  • Dans le premier cas, la méthode value() est directement définie au sein de la définition de la classe. Elle est implicitement déclarée inline. L’ajout du mot clé inline serait redondant et donc inutile.
  • Dans le second cas, la méthode add() est simplement déclarée (sans mot clé particulier) au sein de la classe. Sa définition est écrite dans le fichier header, en dehors de celle de la classe, mais toujours dans le même namespace, tel qu’on le ferait si on implémentait cette fonction dans le fichier source. Dans ce cas, la définition de la fonction étant écrite au sein même du fichier header (et donc potentiellement présente dans plusieurs unités de traduction), on doit cependant ajouter le mot clé inline pour s’affranchir de la “One Definition Rule“.
  • Enfin, dans le dernier cas, la méthode sub n’est pas une vraie méthode mais un template. L’ajout de la directive inline n’est pas obligatoire, car encore une fois, la définition de la méthode se situe dans la définition de la classe. Elle est donc implicitement inline.

N’utilisez inline que sur de très petites fonctions (notion subjective mais en gros : si votre fonction fait plus qu’une simple opération arithmétique ou un retour de valeur, elle n’a surement pas d’intérêt à être inline) et si possible, uniquement sur celles qui ont vocation à être appelées souvent. Les meilleurs candidats pour inline sont généralement bien sûr les getters, les setters, ou encore les destructeurs virtuels vides.

Conclusion

La première fois que l’on m’a parlé du mot clef inline, on me l’a présenté comme un moyen d’optimiser les appels de fonction. Pendant très longtemps, j’ai d’ailleurs soutenu cette version aveuglement. Mais nous avons vu aujourd’hui que les compilateurs sont suffisamment capables pour déterminer d’eux-même quand, quoi et comment optimiser.

En pratique, on retiendra que de bonnes connaissances concernant la “One Definition Rule” et la directive inline sont indispensables à l’écriture d’un code réutilisable et maintenable.

J’espère que cet article vous aura appris quelque-chose (ou à défaut intéressé). N’hésitez pas à me signaler dans les commentaires les éventuelles erreurs que j’aurais pu commettre.

Bon code !

yellow

Découverte de libsystools 2.1

Cette semaine, j’ai eu l’immense honneur d’effectuer la “release” de la version 2.1 de notre bibliothèque de base : libsystools. Cette version est le fruit de nombreuses heures de travail de l’équipe freelan et apporte un bon lot de nouveautés par rapport à la dernière version.

Cet article va tenter de vous faire découvrir ces nouveautés, d’expliquer certaines décisions de design et bien entendu de vous donner envie de l’essayer !

Présentation

Il y a 2 ans, le code de freelan commençait à être volumineux. Bon nombre de classes outils étaient écrites qui pouvaient intéresser d’autres projets. Nous avons donc décidé de rendre cet ensemble de classes autonome et c’est ainsi qu’est né libsystools.

libsystools propose une interface C++ moderne pour un certain nombre de bibliothèques C mais aussi des classes nouvelles (notamment SmartBuffer).

Ses principaux domaines d’application sont :

  • La gestion du xml (parsing, génération, modification, signature);
  • la gestion de la cryptographie (RSA, AES, SHA);
  • la gestion des certificats;
  • la gestion des ports série;
  • la gestion du SSL (TLS, DTLS);
  • la gestion du réseau (Socket, SocketAddress, SocketAddressResolver) en IPv4 et IPv6.
  • la gestion facilité de la mémoire (SmartBuffer);
  • une implémentation basique d’un client UPnP;
  • une implémentation basique d’un client HTTP;
  • une implémentation simple d’une classe de log;
  • la gestion de l’encodage (UTF-8, UTF-16, ISO-8859-1, etc.);
  • différentes méthodes pour calculer de checksum, compresser des flux, etc.

libsystools a été conçue dès le départ pour être extrêmement portable, qu’il s’agisse des systèmes d’exploitation ou des architectures. Elle est donc utilisable sur Windows (MinGW, Visual Studio 2010), Linux (gcc), Mac OSX (gcc), UNIX (gcc) aussi bien en 32 bits qu’en 64 bits.

L’ensemble de ses classes est documenté et différents exemples sont fournis qui traitent de chacun de ses domaines d’application.

libsystools se base sur différentes bibliothèques :

  • libxml2
  • libxmlsec1
  • openssl
  • libiconv
  • boost

Faciliter l’existant

Les bibliothèques sur lesquelles se base libsystools sont bien écrites et fiables. Cependant, elles ne fournissent qu’une interface en C.

Lorsqu’on utilise que peu ces bibliothèques dans un projet, il est acceptable de se servir simplement de ces interfaces C. Cependant, lorsqu’un projet en fait un usage intensif, le code devient très rapidement très dur à maintenir.

Qui n’a jamais eu à écrire un code de ce style ?

Bien sûr, un bon programmeur C++ simplifiera l’écriture de ce genre de code avec l’utilisation de classes conteneurs qui se chargent de libérer la ressource associée lors de leur destruction. Mais devoir refaire ce travail d’encapsulation à chaque utilisation est fastidieux, et ne règle qu’une partie du problème.

C’est dans cet esprit qu’on été développées les classes de libsystools : des wrappers simples, faciles à utiliser et efficaces.

En utilisant libsystools, le code précédent aurait plutôt ressemblé à :

Ce qui est bien plus lisible et maintenable, et présente l’avantage d’être “exception-safe”.

Utilisation & Exemples

Nous pourrions exposer les différents aspects conceptuels qui ont motivé chaque décision pendant des heures et sur plusieurs pages, mais après tout, en programmation, il n’y a pas plus explicite que le code. Voici donc quelques exemples d’utilisation commentés.

SmartBuffer, ou comment gérer la mémoire de façon intelligente

S’il ne fallait garder qu’une seule classe, ce serait celle-ci : SmartBuffer est au coeur de libsystools. Utilisée partout, SmartBuffer représente un tableau d’octets à taille variable et dont les différentes instances peuvent partager leur mémoire.

Dans bon nombre de cas, l’utilisation de SmartBuffer permet de simplifier le code existant en remplaçant les paramètres de type :

Par un simple :

La lecture et la maintenance s’en trouvent tous deux grandement facilités.

XML, les wrappers autour de libxml2

Avec l’engouement de tous les domaines pour le web, il est rare aujourd’hui d’avoir du code à produire qui ne nécessite pas de manipuler du XML. En C++, plusieurs solutions existent.

Nous avons opté pour la libxml2 pour plusieurs raisons :

  • C’est une bibliothèque mature, complète et quotidiennement maintenue;
  • elle est portable sur plusieurs architectures et plateformes;
  • elle s’interface très bien avec OpenSSL au sein de libxmlsec1 pour le support des signatures XML.

Le module XML de libsystools se décompose en trois parties :

  • Les éléments DOM, qui représentent de façon statique un arbre XML;
  • les “writers” qui permettent de générer du XML;
  • les objets XPath, qui permettent d’effectuer des requêtes dans les éléments DOM.

Voyons un exemple d’utilisation :

Reprenons cet exemple point par point :

  • Ligne 20 : nous déclarons un xml::Initializer qui existera pour toute la portée du main. Cet objet permet d’initialiser la libxml2 et de garantir sa libération au moment opportun, de façon automatique.
  • Ligne 28 : nous créons un XmlDocumentWriter. Cet objet permet une construction facilité d’un arbre XML en construisant l’un après l’autre ses différents éléments.
  • Lignes 30 à 38 : Nous créons chacun des noeuds XML. Les différentes fonctions prennent des systools::String en paramètre et supportent donc parfaitement la gestion des différents encodages.
  • Ligne 40 : Nous récupérons un arbre XML complet à partir du writer.
  • Ligne 46 : Nous récupérons l’instance XPath associé à l’arbre XML (cette instance est partagée par tous les noeuds d’un même arbre)
  • Ligne 48 : Nous associons un nom court au namespace nommé “namespace” dans l’instance XPath.
  • Ligne 53 : Nous effectuons une requête XPath sur la racine du document.
  • Ligne 57 : Nous effectuons une requête XPath sur un sous-noeud du document.

La manipulation du XML est grandement facilitée. On voit que seules quelques instructions suffisent à construire un arbre, à y naviguer et à en extraire les informations utiles.

Le module réseau

Une autre partie importante de libsystools est son module réseau. La manipulation des différentes fonctions et structures réseau d’un système a toujours été la bête noire des programmeurs novices. Entre les spécificités propres à chaque système d’exploitation, et les fonctions dépréciées suite au prochain passage à IPv6 (ça approche, si si, croyez-moi !), il n’est pas toujours évident de produire un code propre et robuste.

Le module réseau de libsystools a été conçu pour résoudre cette problématique. Il propose :

  • Une classe qui permet représenter une adresse de socket (IPv4, IPv6, etc.);
  • une classe pour effectuer des résolutions de nom ou d’adresse sur le réseau;
  • des classes Socket et SecureSocket qui encapsulent respectivement une socket classique et une socket SSL (en TLS ou DTLS);
  • une classe “Select” qui encapsule de façon objet les appels à la méthode “select()”;
  • une classe pour représenter les adresses Ethernet;
  • des outils de conversion entre adresse IP et représentation numérique.

Démontrons la simplicité de son utilisation en montrant le code d’un example qui va successivement :

  1. Chercher l’adresse de socket associée à 127.0.0.1:34000 en TCP.
  2. Créer une socket
  3. Connecter cette socket sur le serveur TCP situé à l’adresse recherchée en 1.
  4. Attendre la réception d’un message.
  5. Se déconnecter et libérer la socket et toutes les ressources associées.

Pas si mal pour un code d’une cinquantaine de lignes qui gère correctement toutes les erreurs potentielles et la libération des ressources, non ?

Pour conclure

Vous n’avez eu ici qu’un très bref aperçu des possibilités offertes par la libsystools. Pour découvrir plus avant la bibliothèque, je vous invite à consulter sa documentation en ligne.

En tout cas, j’espère sincèrement que cette présentation aura attisé votre curiosité et vous aura donné envie d’essayer la bibliothèque lors de vos prochains développements. Bien entendu, si vous avez des questions générales sur la bibliothèque et des interrogations concernant son évolution future, n’hésitez pas à commenter cet article ou directement contacter l’équipe sur la mailing-list (en anglais).

Bon code ! :)