Skip to content

Key Signing Assistant (concept)

Dans les allées de la DebConf14, j’ai discuté avec Franklin de l’intérêt pour un développeur d’utiliser son téléphone portable lors d’une key signing party.

Les schémas sont un brouillon d’une utilisation possible du téléphone. L’objectif n’est pas de remplacer la rencontre réelle ou la validation mais juste d’aider à l’échange et validation des clefs.

Actuellement, ce n’est qu’un concept ; rien n’est implémenté.

Le principe général est d’utiliser le téléphone comme un terminal pour l’échange et la validation. Les données partent et reviennent sur la station de travail du développeur par l’intermédiaire d’un serveur web.

  • Le téléphone portable considéré doit être un smartphone ;
  • La seule autorisation à donner pour le téléphone est l’accès à internet ;
  • On considère que les échanges réseau sont fait en https. Je ne pense pas que ce soit indispensable mais il n’y a aucune raison de s’en priver.

Avant la key signing party

Le développeur dispose d’un téléphone sur lequel l’application est installée.
Le processus pour installer ses propres informations est le suivant :

Avant la key signing party

Pendant la key signing party

Le processus est à reproduire pour chaque participant.

Pendant la key signing party

Après la key signing party

Une fois rentré chez lui, le développeur récupère l’ensemble de ses validations sur sa machine de travail :

Après la key signing party

Qu’en pensez-vous ?

Source des schémas

Schémas réalisés avec Inkscape, à partir d’icônes Tango et Gnome-Tango.
Les fichiers svg et png sont disponibles dans le répertoire http://stephane.yaal.fr/ksa/.

Écrire des tests automatisés (ou pas)

Les tests automatisés sont des tests qui sont exécutés par la machine régulièrement. Par example, à chaque fois qu’un commit est réalisé dans un système de gestion de version (comme subversion, mercurial, git, voire CVS pour les plus nostalgiques). L’utilisation de tests automatisés permet d’espérer des gains de temps et de sérénité.

L’article vise à montrer les avantages et les limites des tests automatisés ainsi que des pistes pour que des personnes (ou équipes) sans expérience dans le domaine puissent s’y mettre.

I. Pourquoi ne PAS faire de tests automatisés

Il existe des cas où ne pas avoir de tests automatisés peut être un choix pertinent.

1. Nouveauté

Lorsque l’on découvre quelque chose d’autre de nouveau, le besoin d’expérimenter et de comprendre le fonctionnement est prioritaire. Se contraindre à ajouter des tests en même temps peut gêner l’expérimentation. Dans ce cas, il est préférable de ne pas en faire pour se concentrer sur la nouveauté. Il est plus simple de n’apprendre qu’une chose à la fois donc ce n’est pas la peine d’avoir à apprendre une nouveauté et d’apprendre à tester cette nouveauté en même temps.
Ce cas s’applique lors de l’utilisation d’un nouveau langage ou d’un nouveau framework par exemple.

2. Besoin très simple

Si le besoin est très simple, il est possible que ne pas avoir de tests soit plus rapide car faire les tests à la main ira plus vite (partie gauche de la droite rouge sur le schéma).

Rendements de l'utilisation de tests

Plus le produit est complexe, plus il devient fastidieux de retester toutes les fonctionnalités et le temps passé devient très important (partie droite de la droite rouge sur le schéma). Dans les faits, seules les dernières modifications sont testées et le reste du logiciel est supposé ne pas être influencé par les modifications. C’est vrai la plupart du temps mais cela laisse la porte ouverte à des régressions.

3. Code jetable

Dans le cas d’un programme qui a un usage unique (extraction de données particulières, migration d’un schéma de base de données, traitement d’un cas client particulier), avoir des cas à vérifier peut être une solution plus rapide et aussi fiable que des tests automatisés.

Par contre, si l’on prévoit de garder le script pour le réutiliser plus tard dans des conditions similaires, le même effet va se reproduire : avoir des tests sera plus efficace à long terme.

Le problème n’est pas d’écrire du code jetable, c’est de ne pas le jeter.

Pour tout le reste, il y a probablement des tests qui vous rendraient service.

II. Beaucoup de tests possibles

Il existe de nombreux types de tests (tests unitaires, tests d’intégration, tests de performance, …), chacun d’entre eux répondant à des besoins différents. Certains articles portant sur les tests les listent de manière exhaustive, donnant une impression de travail à fournir gigantesque.

Pourtant, pour bénéficier des avantages des tests automatisés :

  • il n’y a pas besoin de tous les faire ;
  • il n’y a pas besoin de tout tester.

En ayant l’impression que la tâche est énorme, il y a un risque de procrastination. Le coût de mise en place sera considéré comme très élevé et sera continuellement repoussé. Les tests ne seront donc jamais fait.

Il me semble préférable de commencer par des tests faciles (un algorithme de tri par exemple) ou là où le besoin se fait sentir. Sur du code préexistant, il peut être plus simple de commencer par des tests vérifiant un fonctionnement assez général (par exemple, à une requête web, le serveur renvoie une page avec un code 200).
Une fois le mouvement initié, il sera plus aisé d’augmenter la couverture au fur et à mesure. De même, on pourra augmenter la complexité des éléments à tester (comme vérifier les données enregistrées dans une base de données, l’interfaçage avec une API externe, etc.). Il existe des solutions classiques pour ces cas mais ne pas commencer par là permet d’esquiver ces problèmes au début.

Le plus probable est de débuter avec des tests unitaires mais ce n’est pas une obligation. Le plus important étant d’avoir des tests réellement automatisés pour pouvoir bénéficier des avantages qu’ils procurent, ce qui devrait aider à les maintenir et à améliorer la couverture du code.

III. Pré-requis

Évidemment, il faut pouvoir exécuter les tests et l’application hors de la production, au mininum soit sur la machine locale des développeurs, soit dans un environnement d’intégration.

Si les développement sont validés directement en production, la priorité est d’avoir au moins un environnement de développement spécifique, pas d’écrire des tests automatisés. L’écriture des tests est secondaire et sera réalisée par la suite.

IV. Des bogues subsisteront

Les tests ne garantissent pas l’inexistence d’anomalies. Pour cela, il faudrait utiliser des méthodes formelles mais le coût de mise au point est tellement coûteux…

Les tests ne protègent que :

  • des erreurs de syntaxe des fichiers inclus (même si le code lui-même est non testé) ;
  • du non-respect des assertions qu’ils incluent.

Les tests sont donc une solution imparfaite mais :

  • plus la couverture est élevé et plus on est sûr de couvrir de fonctionnalités et de cas particuliers ;
  • c’est une garantie contre les régressions futures ;
  • le fait qu’ils soient automatisés permet d’avoir facilement une idée de l’état du code à n’importe quel moment. Il permet donc de détecter immédiatement lorsque une régression survient.

Avoir des tests automatisés est donc une solution préférable à aucun test. Les gains obtenus par les tests automatisés surpassent les inconvénients dans le cas général.

V. TDD ?

Faire du développement dirigé par les tests (TDD, pour Test Driven Development) est souvent cité en même temps que l’importance de faire des tests. Au-delà des avantages de TTD, les aficionados affirment aussi que c’est plus simple une fois que l’on y on est habitué. Pourquoi ?

1. Processus de développement avec TDD

Le processus de développement est itératif (comme sans TDD). La boucle commence avec le noeud en vert. L’étape d’amélioration du code (noeud refacto dans le schéma) doit être faite si le nécessaire. Les autres étapes sont toutes obligatoires  :

Processus de développement avec TDD

2. Processus de développement sans TDD

Pour obtenir la même garantie, voici le processus à suivre :
Processus de développement sans TDD

L’étape de désactivation du code pour vérifier que le test ne passe plus est nécessaire car sans elle, il n’est pas possible de valider que le test vérifie correctement le code. Cette désactivation est généralement réalisable trivialement en commentant le code ajouté.

Il est possible d’avoir des tests automatisés sans TDD :

  • si le développeur le fait selon le processus présenté ci-dessus ;
  • si ce sont des équipes différentes qui écrivent le code et ceux qui écrivent les tests.

Il me semble regrettable de ne pas en faire, mais il n’est pas nécessaire d’écrire ses premiers tests automatisés avec TDD : si c’est déjà une nouveauté, pas besoin d’en ajouter une deuxième.

Pour ceux qui voudraient un guide pratique en Python (principalement sur les tests unitaires mais les réflexions s’appliquent aux autres langages), en trois parties sur sametmax :
Partie 1, Partie 2, Partie 3

Annexe : Code source des graphiques

Le graphique a été créé avec la bibliothèque python matplotlib v.1.1.1 (paquet python-matplotlib dans Debian). Voici le script qui a permis de le produire :

# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)

ax.plot(range(10), range(10)) #sans test
ax.text(7.5, 7, 'sans tests', style='italic', color="blue")

ax.plot([0, 9], [2, 5]) #avec test
ax.text(7.5, 4.1, 'avec tests', style='italic', color="green")

ax.plot([6, 6], [0, 9]) # début de la rentabilité des tests

ax.annotate(u"le code avec tests devient\nplus rapide à\ndévelopper/maintenir",
    (3, 3),
    xytext=(-110, +50),
    arrowprops={"arrowstyle": '->'},
    textcoords="offset points",
    bbox=dict(boxstyle="round", fc="0.8"),)

ax.annotate(u"début de la\nrentabilité",
    (6, 1.5), 
    xytext=(+40, +25), 
    arrowprops={"arrowstyle": '->', "connectionstyle": "angle,angleA=-90,angleB=180,rad=10"},
    textcoords="offset points",
    bbox=dict(boxstyle="round", fc="0.8"),)

plt.xlabel(u"Complexité")
plt.ylabel(u"Temps passé")

ax.spines["right"].set_visible(False)
ax.spines["top"].set_visible(False)
ax.tick_params(axis='both', which="both",
     bottom=False, top=False, right=False, left=False,
     labelbottom=False, labelleft=False)

plt.savefig("./rendements_tests.png")

Les graphes ont été réalisés avec dot (contenu dans le paquet graphviz dans Debian).
Avec TDD:
$ dot -Tpng tests_avec_tdd.gv -o tests_avec_tdd.png
Contenu du fichier tests_avec_tdd.gv

digraph G {
        "écriture du test" [color=darkgreen, fontcolor=darkgreen, shape=rectangle];
        "(refacto)" [color=darksalmon, style=filled, shape=polygon, sides=7];

        "écriture du test" -> "le test ne passe pas";
        "le test ne passe pas" -> "écriture du code";
        "écriture du code" -> "le test passe";
        "le test passe" -> "(refacto)";
        "(refacto)" -> "écriture du test";
}

Sans TDD:
$ dot -Tpng tests_sans_tdd.gv -o tests_sans_tdd.png
Contenu du fichier tests_sans_tdd.gv

digraph G {
        "écriture du code" [color=darkgreen, fontcolor=darkgreen, shape=rectangle];
        "(refacto)" [color=darksalmon, style=filled, shape=polygon, sides=7];

        "écriture du code" -> "le code fonctionne";
        "le code fonctionne" -> "écriture du test";
        "écriture du test" -> "le test passe";
        "le test passe" -> "désactivation du code";
        "désactivation du code" -> "le test ne passe plus";
        "le test ne passe plus" -> "réactivation du code";
        "réactivation du code" -> "(refacto)";
        "(refacto)" -> "écriture du code";
}

DebConf sur la planète

Cette année, la conférence Debian annuelle aura lieu à Portland, aux États-Unis. Comme l’année dernière, j’y participerai. :)

Participation à la conférence Debian

Cette conférence sera la quinzième du nom. Voici une carte des différentes DebConf (passées en rouge, la prochaine en blanc et celle de l’année prochaine en jaune).

debconf14_planet

Jusqu’ici les conférences ont eu lieu alternativement en Europe et en Amérique (du Nord, centrale ou du Sud). Ce sera aussi le cas en 2015 puisque la conférence aura lieu en Allemagne à Heidelberg.

Réalisation de la carte

La carte diffère légèrement de celle réalisée l’année dernière (pour DebConf13) grâce quelques changements de configuration d’xplanet.

Commande utilisée

xplanet -transpng debconf14_planet.png -geometry 1024x512 -projection peters -config debconf14_planet.conf -num_times 1

Deux paramètres ont été modifiés :

  • La carte utilise une projection de Peters plutôt qu’une projection de Mercator. Pour cela, il suffit de remplacer -projection mercator par -projection peters.
  • Avec cette projection, la taille de la Terre n’est pas la même et la zone vide est rempli par défaut par un ciel étoilé. Il est aussi possible de choisir une couleur unie ou sa propre image de fond. Remplacer le paramètre -output par -transpng pour définir le fichier de sortie permet d’avoir un fond transparent.

Fichier debconf14_planet.conf

[earth]
shade=100
marker_file=coords.txt
marker_fontsize=15
map=night.jpg

L’ajout de map permet de définir l’image à utiliser à la place de l’image par défaut. Ici, on obtient une image de la Terre de nuit (qui provient de /usr/share/xplanet/images/night.jpg).

Fichier coords.txt

+44.80 +0.58 "0&1" #Bordeaux, France
+43.65 -79.38 "2" #Toronto, Canada
+59.92 +10.75 "3" #Oslo, Norway
-29.99 -51.22 "4" #Porto Alegre, Brazil
+60.22 +24.66 "5" #Espoo, Finland
+18.91 -98.97 "6" #Oaxtepec, Mexico
+55.96 -3.19 "7" #Edinburgh, Scotland
-37.96 -57.59 "8" #Mar del Plata, Argentina
+39.60 -6.08 "9" #Extremadura, Spain
+40.74 -74.00 "10" #New York City, USA
+44.78 +17.21 "11" #Banja Luka, Republika Srpska, Bosnia and Herzegovina
+12.14 -86.25 "12" #Managua, Nicaragua
+46.87 +6.75 "13" #Le Camp, Vaumarcus, Switzerland
+45.53 -122.67 "14" color=white #Portland, Oregon, USA
+49.24 +8.42 "15" color=yellow #Heidelberg, Germany

Le fichier a simplement été mis à jour (ajout d’Heidelberg, décalage des couleurs).

À bientôt !

Créer un motif pour cowsay

Cowsay est un de ces outils en ligne de commande prouvant que le génie humain est sans limite. Indispensable lors des grandes étapes d’une vie (déclaration d’amour, divorce, post sur IRC, etc.), il est nécessaire de maîtriser cet outil crucial au point de ne faire qu’un avec lui. À la limite 1,2 si vous n’êtes pas ambitieux.

Installation et utilisation

Cowsay est écrit en Perl et est disponible dans toutes les distributions sérieuses :

apt-get install cowsay  #sous Debian et dérivées

Le paquet fournit `cowsay` et `cowthink`. Une démo valant mille mots, voici deux démos pour deux fois plus d’économies :

$ cowsay "Bonjour lecteur"
 _________________
< Bonjour lecteur >
 -----------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
$ cowthink "Ça c'est fait"
 _______________
( Ça c'est fait )
 ---------------
        o   ^__^
         o  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Il est possible de changer les yeux, et de faire apparaître la langue avec des paramètres facultatifs. Par exemple :

$ cowsay -t "Malaaaade, je suis malaaade"
 _____________________________
< Malaaaade, je suis malaaade >
 -----------------------------
        \   ^__^
         \  (--)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
$ cowsay -d "Malaaaade, j'étais trop malaaade"
 __________________________________
< Malaaaade, j'étais trop malaaade >
 ----------------------------------
        \   ^__^
         \  (xx)\_______
            (__)\       )\/\
             U  ||----w |
                ||     ||

Il est aussi possible de changer la vache par un autre dessin. Plus de 50 motifs sont disponibles dans /usr/share/cowsay/cows/. Cependant, nous ne sommes pas limités à ces motifs car nous pouvons fournir notre propre motif :

$ cowsay -f ./rabbit.cow "Je n'ai pas la myxomatose..."
 ______________________________
< Je n'ai pas la myxomatose... >
 ------------------------------
    \    /|
     \  | |   ____
      \ \ |--/ ___\
       \ ) oo (   `
       =(>(\/)<)=_ --._
         (       /     \_
          \    ,(      / )
          | | / _\   ,'-'
         (_(_/ (______)

Voyons comment réaliser un tel prodige !

Créer son propre motif

Le plus simple est de commencer par une version en ascii satisfaisante. Si on veut avoir la possibilité de modifier les yeux, les deux caractères les représentant doivent être l’un à côté de l’autre. Pour le lapin, on commence avec :

$ cat lapin.txt 
         /|
        | |   ____
        \ |--/ ___\
         ) oo (   `
       =(>(\/)<)=_ --._
         (       /     \_
          \    ,(      / )
          | | / _\   ,'-'
         (_(_/ (______)

Pour en faire un fichier utilisable par `cowsay`, le nouveau fichier devra finir par ".cow" et être du code Perl valide.
Ainsi les caractères « \ » et « @ » doivent être protégé par un premier « \ ». L’emplacement (facultatif) de la langue est marqué par $tongue, celui des yeux par $eyes. Les variables $thoughts seront remplacées automatiquement pour relier le motif (ici le lapin) à la bulle contenant le texte.

La variable $the_cow sera définie avec ce motif.

Au final, on obtient :

$the_cow = <<EOC;
    $thoughts    /|
     $thoughts  | |   ____
      $thoughts \\ |--/ ___\\
       $thoughts ) $eyes (   `
       =(>(\\/)<)=_ --._
         (  $tongue   /     \\_
          \\    ,(      / )
          | | / _\\   ,'-'
         (_(_/ (______)
EOC

Évidemment, le résultat est beaucoup moins lisible mais comme c’est du Perl, les traditions sont respectées.

Le résultat en action :

$ cowsay -d -f ./rabbit.cow "Caramba, encore raté"
 ______________________
< Caramba, encore raté >
 ----------------------
    \    /|
     \  | |   ____
      \ \ |--/ ___\
       \ ) xx (   `
       =(>(\/)<)=_ --._
         (  U    /     \_
          \    ,(      / )
          | | / _\   ,'-'
         (_(_/ (______)

Code source et références

Télécharger rabbit.cow

Pour connaître les possibilités non couvertes par l’article, lisez la page de manuel de `cowsay`. C’est la même page qui sert de manuel pour `cowthink`.

La version utilisée pour cet article est la 3.0.3.

La page historique sur `cowsay` par son créateur : https://web.archive.org/web/20120225123719/http://www.nog.net/~tony/warez/cowsay.shtml

Naviguer entre répertoires avec popd, pushd et cd

Parfois, il est nécessaire de naviguer entre plusieurs répertoires dont le chemin est long. Certes, les interpréteurs complètent les chemins, ce qui rend les manipulations moins douloureuses. Certes, cd - rend bien service lorsque l’on souhaite simplement revenir dans le répertoire précédent. Cependant, des shells comme Bash ou zsh possèdent une pile dans laquelle il est possible d’enregistrer des chemins pour les réutiliser par la suite. Utiliser cette pile est parfois une solution bien plus pratique.

Les commandes popd et push ne sont pas disponibles dans tous les interprètes de commandes. Par exemple, sh ne les a pas.

Quatres commandes sont nécessaires :

  • popd, pour empiler les répertoires ;
  • pushd, pour dépiler les répertoires ;
  • dirs, pour voir la pile ;
  • cd, pour se déplacer entre les répertoires. Oui, la commande que tout le monde connaît. :-)

Première visite

Empiler un répertoire

Partons d’une pile vide :

chaperonrouge@conte:/$ cd /maison
chaperonrouge@conte:/maison$ dirs -v
0 /maison

dirs affiche uniquement le répertoire actuel (donc /maison, à l’index 0), et rien d’autre car la pile ne possède qu’un seul élément. L’option -v permet d’afficher l’index.

déplacement dans un répertoire

Par défaut, pushd prend un répertoire en paramètre. Il empile ce répertoire, qui est donc à l’index 0. L’ancien répertoire à l’index 0 est décalé à l’index 1. Le nouveau répertoire de travail est donc celui qui est en 0. Enfin, pushd affiche l’état de la nouvelle pile.

Par exemple, en utilisant deux fois la commande pushd sur des répertoires différents :

chaperonrouge@conte:/maison$ pushd /chez/mere/grand
/chez/mere/grand /maison
chaperonrouge@conte:/chez/mere/grand$ dirs -v
0 /chez/mere/grand
1 /maison
chaperonrouge@conte:/chez/mere/grand$ pushd /dans/la/foret
/dans/la/foret /chez/mere/grand /maison
chaperonrouge@conte:/dans/la/foret$ dirs -v
0 /dans/la/foret
1 /chez/mere/grand
2 /maison

Pushd, comportement par défaut

Changer de répertoire

Pour se déplacer vers un répertoire enregistré dans la pile, il suffit de préfixer l’index par le caractère tilde (~). La pile reste inchangée.

Par exemple, chaperonrouge veut aller dans le répertoire /chez/mere/grand qui est actuellement à l’index 1 de la pile :

chaperonrouge@conte:/maison$ dirs -v
0 /dans/la/foret
1 /chez/mere/grand
2 /maison
chaperonrouge@conte:/$ cd ~1
chaperonrouge@conte:/chez/mere/grand$

Déplacement dans un répertoire enregistré dans la pile

Supprimer un répertoire

popd supprime un répertoire de la pile puis affiche le nouvel état de la pile.
Par défaut, popd supprime le premier répertoire de la pile et se déplace vers le répertoire qui est passé à l’index 0.

Par exemple, chaperonrouge décide de faire le ménage :

chaperonrouge@conte:/maison$ dirs -v
0 /maison
1 /en/chemin
2 /dans/la/foret
3 /chez/mere/grand
chaperonrouge@conte:/maison$ popd
/en/chemin /dans/la/foret /chez/mere/grand
chaperonrouge@conte:/en/chemin$ dirs -v
0 /en/chemin
1 /dans/la/foret
2 /chez/mere/grand

Popd, comportement par défaut

Il est aussi possible de supprimer un élément quelconque de la pile en précisant l’index de l’élément à supprimer. Dans ce cas, popd ne change pas le répertoire actuellement utilisé (le chemin enregistré à l’index 0 n’a pas changé).

chaperonrouge@conte:/maison$ dirs -v
0 /maison
1 /dans/la/foret
2 /chez/mere/grand
3 /dans/le/ventre/du/loup
chaperonrouge@conte:/maison$ popd +2
/maison /dans/la/foret /dans/le/ventre/du/loup
chaperonrouge@conte:/maison$

Popd (sélection d'un répertoire particulier)

Autres possiblités

L’option -n de pushd permet d’empiler le répertoire passé en paramètre sans se déplacer :

chaperonrouge@conte:/maison$ dirs -v
0 /maison
1 /dans/la/foret
2 /chez/mere/grand
chaperonrouge@conte:/maison$ pushd -n /porte/du/village
0 /maison
1 /porte/du/village
1 /dans/la/foret
2 /chez/mere/grand
/maison /porte/du/village /dans/la/foret /chez/mere/grand
chaperonrouge@conte:/maison$

Pushd, sans déplacement

Mettre le répertoire de travail dans la pile :

chaperonrouge@conte:/maison$ dirs -v
0 /maison
1 /dans/la/foret
2 /chez/mere/grand
chaperonrouge@conte:/maison$ pushd .
/maison /maison /dans/la/foret /chez/mere/grand
chaperonrouge@conte:/maison$

Pushd, empilement du répertoire actuel

L’option -c de dirs vide la pile des répertoires enregistrés :

chaperonrouge@conte:/maison$ dirs -v
0 /maison
1 /dans/la/foret
2 /chez/mere/grand
chaperonrouge@conte:/maison$ dirs -c
chaperonrouge@conte:/maison$ dirs -v
0 /maison

Vider la pile

Il est possible de compter l’index de la pile en partant de la fin (-0 pour le dernier, -1 pour l’avant dernier, etc.). Le comportement est équivalent aux index de liste en Python (et probablement dans beaucoup d’autres langages).

Voici un exemple pour aller dans le dernier répertoire de la pile :

chaperonrouge@conte:/maison$ dirs -v
0 /maison
1 /dans/la/foret
2 /chez/mere/grand
chaperonrouge@conte:/maison$ cd ~-0
chaperonrouge@conte:/chez/mere/grand$

Déplacement dans un répertoire de la pile, comptage par la fin

Les options de ces commandes sont évidemment non exhaustives.

Logiciels utilisés et codes source des schémas

Pour avoir la documentation des commandes, il faut utiliser « man bash » et non « man pushd ».

Le comportement des outils est basé sur Bash, version 4.2.45(1)-release.

Les schémas ont été réalisés avec ditaa, version 0.9. La version utilisée provient du paquet Debian du même nom.
La commande de base à utiliser est :

$ ditaa pushdpopd_exemple1.txt pushdpopd_exemple1.png

Voici le code source des schémas inclus dans l’article :
pushdpopd_exemple1 :

/-------------------\       +-------------+          /-------------------\
| 0 /               |-------: cd /maison  |--------->| 0 /maison         |
\-------------------/       +-------------+          \-------------------/
/-------------------\                                /-------------------\
| cYEL(vide)        |                                | cYEL(vide)        |
\-------------------/                                \-------------------/

pushdpopd_exemple2 :

/-------------------------\      +---------------------------+       /-------------------------\
| 0 /maison               |------: pushd /chez/mere/grand    |------>| 0 /chez/mere/grand      |--+
\-------------------------/      +---------------------------+       \-------------------------/  |
/-------------------------\                                          /-------------------------\  |
| cYEL(vide)              |                                          | cYEL1 /maison           |  |
\-------------------------/                                          \-------------------------/  |
                                                                                                  |
/-------------------------\      +---------------------------+                                    |
| 0 /dans/la/foret        |<-----: pushd /dans/la/foret      |------------------------------------+
\-------------------------/      +---------------------------+
/-------------------------\
| cYEL1 /chez/mere/grand  |
+-------------------------+
| cYEL2 /maison           |
\-------------------------/

pushdpopd_exemple3 :

/-------------------------\       +-------------------------+            /-------------------------\
| 0 /dans/la/foret        |-------: cd ~1                   |----------->| 0 /chez/mere/grand      |
\-------------------------/       +-------------------------+            \-------------------------/
/-------------------------\                                              /-------------------------\
| cYEL1 /chez/mere/grand  |                                              | cYEL1 /chez/mere/grand  |
+-------------------------+                                              +-------------------------+
| cYEL2 /maison           |                                              | cYEL2 /maison           |
\-------------------------/                                              \-------------------------/

pushdpopd_exemple4 :

/-------------------------\       +-------------------------+            /-------------------------\
| 0 /maison               |-------: popd                    |----------->| 0 /en/chemin            |
\-------------------------/       +-------------------------+            \-------------------------/
/-------------------------\                                              /-------------------------\
| cYEL1 /en/chemin        |                                              | cYEL1 /dans/la/foret    |
+-------------------------+                                              +-------------------------+
| cYEL2 /dans/la/foret    |                                              | cYEL2 /chez/mere/grand  |
+-------------------------+                                              \-------------------------/
| cYEL3 /chez/mere/grand  |
\-------------------------/

pushdpopd_exemple5 :

/-------------------------------\       +-----------------------+        /-------------------------------\
| 0 /maison                     |-------: popd  +2              |------->| 0 /maison                     |
\-------------------------------/       +-----------------------+        \-------------------------------/
/-------------------------------\                                        /-------------------------------\
| cYEL1 /dans/la/foret          |                                        | cYEL1 /dans/la/foret          |
+-------------------------------+                                        +-------------------------------+
| cYEL2 /chez/mere/grand        |                                        | cYEL2 /dans/le/ventre/du/loup |
+-------------------------------+                                        \-------------------------------/
| cYEL3 /dans/le/ventre/du/loup |
\-------------------------------/

pushdpopd_exemple6 :

/-------------------------\       +----------------------------+         /-------------------------------\
| 0 /maison               |-------: pushd -n /porte/du/village |-------->| 0 /maison                     |
\-------------------------/       +----------------------------+         \-------------------------------/
/-------------------------\                                              /-------------------------------\
| cYEL1  /dans/la/foret   |                                              | cYEL1 /porte/du/village       |
+-------------------------+                                              +-------------------------------+
| cYEL2 /chez/mere/grand  |                                              | cYEL2 /dans/la/foret          |
\-------------------------/                                              +-------------------------------+
                                                                         | cYEL3 /chez/mere/grand        |
                                                                         \-------------------------------/

pushdpopd_exemple7 :

/-------------------------\       +----------------------------+         /-------------------------------\
| 0 /maison               |-------: pushd .                    |-------->| 0 /maison                     |
\-------------------------/       +----------------------------+         \-------------------------------/
/-------------------------\                                              /-------------------------------\
| cYEL1  /dans/la/foret   |                                              | cYEL1 /maison                 |
+-------------------------+                                              +-------------------------------+
| cYEL2 /chez/mere/grand  |                                              | cYEL2 /dans/la/foret          |
\-------------------------/                                              +-------------------------------+
                                                                         | cYEL3 /chez/mere/grand        |
                                                                         \-------------------------------/

pushdpopd_exemple8 :

/-------------------------\       +----------------------------+         /-------------------------------\
| 0 /maison               |-------: dirs -c                    |-------->| 0 /maison                     |
\-------------------------/       +----------------------------+         \-------------------------------/
/-------------------------\                                              /-------------------------------\
| cYEL1  /dans/la/foret   |                                              | cYEL (vide)                   |
+-------------------------+                                              \-------------------------------/
| cYEL2 /chez/mere/grand  |
\-------------------------/

pushdpopd_exemple9 :

/---------------------------\       +----------------------------+         /-------------------------------\
| 0 /maison              -2 |-------: cd ~-0                     |-------->| 0 /chez/mere/grand            |
\---------------------------/       +----------------------------+         \-------------------------------/
/---------------------------\                                              /-------------------------------\
| cYEL1  /dans/la/foret  -1 |                                              | cYEL1 /dans/la/foret          |
+---------------------------+                                              +-------------------------------+
| cYEL2 /chez/mere/grand -0 |                                              | cYEL2 /chez/mere/grand        |
\---------------------------/                                              \-------------------------------/