Code training As Code avec Asciidoctor

Nicolas Hodicq
maytheforce.bewizyu
8 min readNov 29, 2017

Contexte

Gérer la documentation d’un projet est une problématique bien connue des équipes de développement. Il existe tout un tas d’outils qui permettent de le faire et de bien le faire (Wiki, Word, ….). La principale difficulté consiste à maintenir la documentation à jour par rapport au code source du projet, des applications. L’approche la plus pertinente, à mon avis, est de faire évoluer la documentation comme on le fait avec le code source.

👉 Documentation dans le gestionnaire de sources (idéalement git 😏)

  • Historique de la documentation
  • Code review sur les pull requests
  • Facile à écrire
  • Format facilement lisible pour les code reviews

Pour répondre à ce besoin, la communauté plébiscite le format Markdown qui a été fortement démocratisé par les projets open source et la plateforme bien connue qu’est Github. Ce format permet, avec une syntaxe simple et lisible, de générer des fichiers HTML. Encore mieux, toutes plateformes de gestion de sources affichent directement les fichiers .md (extension pour les fichiers markdown) en page web (exemple). J’utilise cette solution depuis un moment déjà sur les projets auxquels je participe et cela me suffisait jusqu’à présent.

Au sein de Bewizyu, nous intervenons sur 3 secteurs d’activités (Create, Learn, Make). Une partie de notre activité concerne la création et la maintenance de supports de formation sur différentes technologies. Potentiellement, nous pouvons jouer ces formations aussi bien pour Bewizyu (et oui nous sommes agréés 🍾) que pour pour d’autres organismes de formation en tant que partenaire pédagogique.

👉 Contraintes complémentaires pour nos supports de formation (tout en conservant les mêmes pratiques que pour la documentation dont je vous ai parlée plus haut 👆).

  • Générer des supports de TP au format PDF
  • Générer les supports de présentation (Slides)
  • Personnaliser le thème des supports en fonction de l’organisme de formation pour lequel nous intervenons
  • Changer certains textes en fonction de l’organisme de formation
  • Couvrir les exemples de code, qui servent dans les supports, par des tests unitaires afin de pouvoir détecter plus facilement les breaking changes lors des montées de version des langages ou des frameworks.

Avec ces nouvelles données, le markdown ne suffit plus. En effet, il n’est pas possible d’inclure des fichiers externes (javascript par exemple), ni d’avoir une notion de “paramètres” pour personnaliser des champs textes.

Asciidoctor

J’ai entendu parler pour la première fois d’Asciidoctor sur un article du blog des Ninja Squad. C’est un retour d’expérience sur les processus et les outils qu’ils ont mis en place pour écrire leurs livres sur AngularJS et Angular. J’avais trouvé l’approche très intéressante, notamment le fait de considérer que la création d’un livre est similaire à la création d’une application, le tout avec une conception itérative et agile.

J’avais fait quelques tests mais je n’avais pas eu de use case pour tester grandeur nature, jusqu’à aujourd’hui !!! Même si nous nous cherchons encore un peu (certains points peuvent être améliorés), nous générons aujourd’hui nos supports via Asciidoctor.

Comme le markdown, les fichiers .adoc (extension pour les fichiers Asciidoc) utilisent une syntaxe simple à écrire et à lire. Voici un comparatif entre le markdown et asciidoctor.

Asciidoctor est un outil développé en Ruby (qui remplace AsciiDoc Python) qui permet de lire et de parser des fichiers écrits avec une syntaxe Asciidoc. Asciidoctor ajoute aussi un certain nombre de fonctionnalités qui ne sont pas prises en charge par Asciidoc. Pour avoir un petit comparatif, c’est par ici. Pour être complet, un portage JavaScript est aussi disponible, mais peu utilisé et la génération de PDF, qui est ma cible principale, n’est faisable qu’en Ruby.

Et sinon, on teste quand ?

Alors pour l’occasion, j’ai créé un repository qui contient toutes les sources qui vont me servir dans cet article. Vous trouvez dans le README du projet toutes les informations nécessaires pour créer les PDF’s.

La syntaxe

La syntaxe a un certain nombre de points en commun avec le markdown, on s’habitue très rapidement. Je ne vais pas m’attarder dessus, mais plutôt vous diriger vers la documentation officielle qui se suffit à elle même.

Dans le repository, vous trouverez un exemple qui provient des samples de asciidcotor-pdf. Il reprend une partie importante des syntaxes que vous utiliserez bientôt fréquemment, j’espère 😏.

https://github.com/nartawak/asciidoctor-blog/blob/master/src/content/main.adoc

Intégrer des fichiers externes

C’est un des avantages comparé au markdown, il nous est possible d’inclure du code source via des fichiers externes.

  • Voici un exemple de code que je vais inclure dans mes supports
math.js
  • Je souhaite que tous les exemples de code de mes supports soient couverts par des tests unitaires.
math.specs.js
  • Ensuite, je peux inclure mon fichier math dans mon document (j’ai dû mettre mon exemple en commentaire car gist essaie de l’interpréter 😡)

L’exemple précédent inclu un fichier complet dans le support. Or souvent, le besoin que nous avons est plus fin que cela, nous ne souhaitons pas inclure le fichier complet mais une portion seulement.

👉 C’est tout à fait possible avec les tags !!

Un tag se définit par une instruction de début (tag::) et de fin (end::) et un nom dans des commentaires, comme dans l’exemple ci-dessous:

math.specs.js

Pour inclure la portion de code (tag) d’un fichier externe, il faut préciser entre les [], le tag à inclure. La fonctionnalité est assez puissante notamment avec la possibilité de grouper, filtrer et d’exclure des tags. Toutes les informations complémentaires sont disponibles ici.

Exemple en commentaire car gist essaie de l’interpréter 😡

Pour être complet, il est aussi possible d’insérer uniquement certaines lignes d’un fichier externe. Personnellement, je n’utilise pas cette approche. Le fichier source évoluera dans le temps et de fait, l’inclusion dans le support sera “cassée”. C’est donc laborieux à maintenir.

Exemple en commentaire car gist essaie de l’interpréter 😡

Attribute Entries

Autre avantage comparé au markdown, il est possible de définir des attribute entries. C’est un système de clé / valeur déclaré sur une ligne qui permet, dans la suite du document, de récupérer la valeur par la clé. C’est très utile pour factoriser tout ce que vous allez répéter souvent.

Pour déclarer un attribut, ‘:NOM_ATTRIBUT: valeur’ et pour l’utiliser, {NOM_ATTRIBUT}, comme dans l’exemple suivant:

Exemple en commentaire car gist essaie de l’interpréter 😡

Tous les attributs, que vous allez créer, mais aussi ceux mis à disposition par Asciidoctor peuvent être surchargés. Pour ce faire, il y a deux possibilités qui, bien sûr, peuvent être combinées:

  • Redéfinir une nouvelle valeur à l’attribut dans le document.
  • Passer une nouvelle valeur en ligne de commande lors de la génération (en HTLM, PDF, what else ..). L’exemple suivant surcharge l’attribut path-node-samples avec la valeur Test
asciidoctor -a path-node-samples=Test src/nartawak.adoc

Comme je l’ai mentionné plus haut, je souhaite générer mes documents en plusieurs exemplaires. Le contenu doit être identique mais le style et certains éléments (nom des auteurs, organisme de formation, ..) changent.

Je trouve un peu verbeux de passer tous les attributs en ligne de commande. En pratique, je préfère définir un fichier par client qui définit les différents attributs spécifiques. Dans mon repository exemple, je souhaite générer le même document pour une organisation Nartawak et Bewizyu. Dans chaque document, je définis l’auteur (prénom et nom de famille) et le nom de l’organisation. Je peux ensuite les utiliser dans le document main.

Générer des pdf’s

Pour générer des pdf’s, il faut ajouter un gem Ruby supplémentaire 👉 asciidoctor-pdf. Une fois installé, vous pourrez directement utiliser le binaire en ligne de commande pour générer le pdf.

generate-pdf.sh

Personnaliser le style

Un style par défaut est défini par asciidoctor-pdf, vous pouvez si vous en avez le besoin le redéfinir complètement. Enfin complètement, c’est pas aussi simple que cela. Le style / thème est défini par un fichier yaml, il est possible de configurer les clés mises à disposition par asciidoctor-pdf.

Pour ce faire vous devez copier le fichier yml dans votre projet et ensuite surcharger les attributs qui définissent l’emplacement du dossier de style.

👉 :pdf-stylesdir: src/themes (dans mon repository)

Si vous regardez dans ce dossier, vous y trouverez notamment deux fichiers, nartawak-theme.yml et bewizyu-theme.yml. Pour l’instant, nous précisons où se situe le dossier contenant les thèmes mais nous n’indiquons pas quel thème choisir. Encore une fois, un attribut doit être défini.

👉 :pdf-style: nartawak

Asciidoctor-pdf concatènera le nom du style que nous définissons avec -theme.yml . Dans cet exemple cela donnera nartawak-theme.yml .

Dans le projet sample, un style est défini par organisation, l’attribut pdf-style est surchargé dans chaque organisation au même titre que les attributs personnalisés. Les headers et footers des supports changent en fonction de l’organisation.

👉 PDF Nartawak organisation

👉 PDF Bewizyu organisation

N’hésitez pas à vous référer au theming guide

Intégration continue

Comme je l’ai mentionné précédemment, l’idée de concevoir mes supports comme une application me séduit. Quand je travaille sur une application, je travaille avec des pull requests. À chaque pull request:

  • un ou plusieurs membres de l’équipe font une revue de code, et on va itérer jusqu’au moment où tous les relecteurs donnent le GO.
  • Les outils des tests unitaires, de contrôle de qualité et le build doivent être au vert.

Ces éléments sont des prérequis pour qu’une pull request soit fusionnée dans la banche stable de développement ou de release.

Dans le projet sample, j’ai utilisé travis comme outil d’intégration mais bien sur toutes les solutions classiques ou cloud font très bien le travail, ce n’est pas dépendant de la plateforme de build.

Sur chacune des pull request, les dépendances nécessaires au projets sont installées, celles pour construire les supports en Ruby et celles pour les projets contenant les codes sources.

Dans mon cas, mes exemples de code sources étant en JavaScript avec Node.js, les dépendances Node.js sont installés dans tous mes projets via Lerna. Cet outil sera peut être un jour le sujet d’un autre article, je ne vais pas rentrer dans le détail, sachez juste qu’il permet d’installer des dépendances et de lancer des tâches (npm run …) sur plusieurs projets node.js en une seule ligne de commande.

A chacune des pull requests, ESlint et les tests unitaires sont joués pour chacun des projets contenant du code sources, les supports (narawak et bewizyu sont construits).

A chaque commit sur la branche de master (cela ne peut arriver que via une pull request dans mon projet), une release est créée et les pdfs générés sont ajoutés à la release. Ils sont ainsi téléchargeables depuis la page des releases de mon projet sur github. La granularité des releases serait différente sur un réel projet, mais pour mon exemple cela illustre bien ce qu’il est possible de mettre en place.

Conclusion

Comme pour tout nouvel outil, il faut un temps d’adaptation. Je suis toutefois très séduit par celui-ci qui vient combler un manque dans la stack que j’utilisais précédemment. Après quelques démos aux collègues, nous avons vite décider de le généraliser pour le support de nos formations. Je me suis concentré dans ce trop long article sur la génération des supports au format pdf mais, il est bien sur possible de générer des slides avec asciidoctor-deck.js et asciidoctor-reveal.js.

Le vrai point négatif concerne l’industrialisation. Asciidoctor ne retourne pas de code d’erreur différent de 0 lorsqu’une erreur se produit dans le cycle de génération. En bash, le code de retour 0 signifie que tout s’est bien passé, que le process est en succès. Asciidoctor n’affiche donc que des warning dans la console. Les outils d’intégration continue considèrent donc que le build est valide. Vous pouvez trouver un exemple sur le projet, les statuts checks ne devraient pas être valides (logs). D’après mes recherches des issues sont ouvertes (44, 2003, 2346) et certaines depuis très longtemps, pas sur que cela évolue rapidement.

“Remember - BEWIZYU, always”…

--

--

Nicolas Hodicq
maytheforce.bewizyu

Consultant & trainer @Zenika / Full stack web & mobile developer