Markdown et pandoc

- 7 minutes de lecture

Lors d’une discussion concernant l’Ă©criture de mon livre, j’ai expliquĂ© quels outils j’ai utilisĂ©. Parmi ces outils, j’ai eu un usage de pandoc pour convertir du texte du format markdown en fichier .docx.

Pour le rendu du manuscrit, mon Ă©diteur m’a demandĂ© un rendu au format .docx, avec la contrainte d’un document par chapitre. Les documents devaient suivre une feuille de style particuliĂšre qui Ă©tait fournie. La feuille de style contenait les styles pour les titres, citations, blocs de code, etc.

Je pense que ce format est le plus pratique pour les Ă©quipes de l’Ă©diteur. Mais la perspective de travailler dans Word ne m’enchantait pas (d’ailleurs, j’utilise LibreOffice sur mes postes Linux), je voulais pouvoir archiver mon travail sur Git, et inclure dans mes chapitres du code rĂ©ellement fonctionnel. J’ai donc cherchĂ© un moyen de pouvoir travailler dans un format de type markdown, que je pouvais prĂ©-processer.

pandoc et markdown

pandoc est un outil de conversion de documents. Il gÚre de nombreux formats en entrée et en sortie, dont le markdown et le docx !

AprĂšs avoir installĂ© l’outil, on peut convertir des documents. L’option -o permet de prĂ©ciser le fichier de sortie, le format est dĂ©tectĂ© Ă  partir de l’extension du fichier :

# générer un doc html
$ pandoc file.md -o file.html

# générer un word
$ pandoc file.md -o file.docx

La gĂ©nĂ©ration par dĂ©faut en format docx est tout Ă  fait correcte et prend en compte les diffĂ©rents niveaux de titre, la gestion du gras et de l’italique, le code et les blocs de citation, ainsi que les listes, tableaux et images. Bref, tout ce qu’on peut basiquement faire en markdown.

Le markdown suivant :

# Content

Emphasis, aka italics, with *asterisks* or _underscores_.
Strong emphasis, aka bold, with **asterisks** or __underscores__.
Combined emphasis with **asterisks and _underscores_**.
Strikethrough uses two tildes. ~~Scratch this.~~

[I'm an inline-style link](https://www.google.com)

![cover.jpg](cover.jpg)

# Lists

1. First ordered list item
2. Another item
    * Unordered sub-list.
1. Actual numbers don't matter, just that it's a number
    1. Ordered sub-list
4. And another item.

# Code and Syntax Highlighting

Inline `code` has `back-ticks around` it.

Use 3 backticks to create a code block

```shell
#!/bin/sh
echo "Hello World"
```

# Blockquotes

> Blockquotes are very handy in email to emulate reply text.
> This line is part of the same quote.

# Tables

| First Header  | Second Header |
| ------------- | ------------- |
| Content Cell  | Content Cell  |
| Content Cell  | Content Cell  |

est transformé en docx avec la commande pandoc sample.md -o sample.docx

Le rĂ©sultat n’est pas des plus stylĂ©s, mais est dĂ©jĂ  plutĂŽt pas mal, on voit bien que markdown est bien supportĂ© :

sample.png sample.docx

Rendre le document stylé

Lorsqu’il gĂ©nĂšre le fichier docx, pandoc peut utiliser un document qui lui servira de rĂ©fĂ©rence pour les styles. La premiĂšre Ă©tape consiste alors Ă  gĂ©nĂ©rer le document de rĂ©fĂ©rence en utilisant la commande pandoc -o custom-reference.docx --print-default-data-file reference.docx. Cette commande gĂ©nĂšre alors le document contenant les styles par dĂ©faut.

Le contenu de ce document importe peu, il contient quelques éléments pouvant servir de test, seuls seront utilisés les styles.

Les styles utilisés par pandoc sont décrits dans sa documentation.

À noter que le code source inline est reprĂ©sentĂ© par le style de caractĂšres Verbatim Char, et le code source en blocs prendra le style de paragraphe Source Code (qui n’est pas créé par dĂ©faut dans le document de rĂ©fĂ©rence).

Pour redimensionner les images, il est aussi possible de préciser leur taille en attribut :

![cover.jpg](cover.jpg){ width=50% }

Une fois ce document personnalisĂ©, on gĂ©nĂ©re les fichiers docx en utilisant l’option --reference-doc=custom-reference.docx :

pandoc sample.md -o sample-with-style.docx --reference-doc=custom-reference.docx

sample-with-style.png sample-with-style.docx

Pré-processing

L’utilisation du format texte markdown Ă©tant donc possible, je souhaitais pouvoir prĂ©-processer mes fichiers markdown pour :

  • inclure du code source rĂ©el dans le document
  • gĂ©nĂ©rer mes diagrammes au format drawio en png et les inclure dans le document gĂ©nĂ©rĂ©

J’ai donc Ă©crit un peu de script qui fait tout ça.

Inclure le code depuis des fichiers externes

Il est possible avec pandoc d’exĂ©cuter des filtres pendant l’exĂ©cution de la transformation. L’utilisation des filtres n’est cependant pas trĂšs bien documentĂ©e, et s’appuie sur la manipulation d’un AST au format JSON. Cette approche, bien que plutĂŽt propre, me semblait quand mĂȘme compliquĂ©e pour simplement inclure du code dans mes fichiers.

J’ai donc trouvĂ© une autre solution plus simple : dans mes fichiers markdown, j’utilise une syntaxe de Commentaires pour recrĂ©er une espĂšce de macro, que je fais prĂ©-processer par un script shell.

La macro d’inclusion de code que j’ai utilisĂ©e est celle-ci :

[//]: # (INCLUDECODE FICHIER_A_INCLURE)

La syntaxe [//]: # () correspond Ă  l’Ă©quivalent d’un commentaire markdown (cf ce stackoverflow). J’ai alors simplement utilisĂ© un commentaire INCLUDECODE devant ĂȘtre suivi du nom du fichier Ă  inclure.

Pour prĂ©-processer ces macros, j’utilise la puissance du shell et le fait que pandoc accepte le contenu du document Ă  transformer sur STDIN. Les 2 commandes suivantes ont un comportement identique (au delta de l’exĂ©cution d’un cat en plus) :

# direct
$ pandoc sample.md -o sample.docx

# pipes
$ cat sample.md | pandoc -o sample.docx

Avec ce fonctionnement, il est facile de glisser un script shell entre deux :

$ cat sample.md |
 ./include-code-macro.sh |
 pandoc -o sample.docx

Le script shell intercepte les lignes correspondant à la macro et insÚre le contenu du fichier référencé :

#!/bin/bash

regex='\[\/\/\]: # \(INCLUDECODE (.*)\)'

# Process input line by line
while IFS= read -r line; do
  # Extract the filename using regex
  if [[ "$line" =~ $regex ]]; then
    filename="${BASH_REMATCH[1]}"

    # Find the file
    if [[ $? -eq 0 ]]; then
      # Output the file content wrapped in code blocks
      echo '```'
      cat "$filename"
      echo '```'
    fi
  else
    # Output the line as is
    echo "$line"
  fi
done

exit 0

Ce petit script (trĂšs moche) fait permet donc d’inclure facilement mes fichiers externes. Cela me permet de les modifier sur le cĂŽtĂ©, et de regĂ©nĂ©rer mes documents d’un coup.

Générer des diagrammes drawio et les inclure

Pour gĂ©rer des diagrammes drawio Ă  la volĂ©e, j’ai simplement suivi le mĂȘme principe : une syntaxe de type macro et un script pour la traiter.

La macro pour extraire un diagramme est du mĂȘme type :

[//]: # (DIAGRAM FICHIER_A_INCLURE)

Et le script est aussi similaire, il utilise le CLI de drawio pour exporter le diagramme dans un .png, qui est ensuite inclus dans le markdown :

regex='\[\/\/\]: # \(DIAGRAM (.*)\)'

# Process input line by line
while IFS= read -r line; do
  # Extract the filename using regex
  if [[ "$line" =~ $regex ]]; then
    filename="${BASH_REMATCH[1]}"

    # Find the file
    if [[ $? -eq 0 ]]; then
      # export drawio diagram
      drawio -x -f png --width 1024 -t "$filename" >/dev/null 2>&1
      exported_file=$(basename "$filename" .drawio)
      # add markdown for exported diagram
      echo "![]($exported_file.png)"
    fi
  else
    # Output the line as is
    echo "$line"
  fi
done

exit 0

J’aurais aussi pu utiliser directement une directive d’image de cette maniĂšre :

![](mon_diagramme.drawio)

mais j’ai prĂ©fĂ©rĂ© rester avec mon format de macro. Cela me laissait la possibilitĂ© de factoriser mes scripts et d’introduire d’autres macros si besoin (besoin que je n’ai pas eu Ă©videmment, YAGNI comme on dit).

Je m’Ă©tais aussi laissĂ© la possibilitĂ© d’affiner les scripts pour redimensionner prĂ©cisĂ©ment les diagrammes, ou inclure du code entre 2 lignes, mais je n’en ai pas eu le besoin non plus.

La commande définitive

En cumulant tout ce qu’on a vu, la commande suivante fait l’inclusion du code, la gĂ©nĂ©ration des diagrammes Ă  la volĂ©e, et la conversion en .docx avec un style fourni :

$ cat sample.md |
  ./include-code-macro.sh |
  ./include-diagram-macro.sh |
  pandoc -o sample-final.docx --reference-doc=custom-reference.docx

Le document généré par cette commande est maintenant complet :

sample-final.png sample-final.docx

Conclusion

pandoc permet de générer des documents Word assez propres de mon point de vue. Avec un peu de travail sur la feuille de style, il est assez facile de personnaliser le rendu des documents.

C’est d’ailleurs cette façon de gĂ©nĂ©rer les documents que j’ai utilisĂ©e pour Ă©crire (et compiler) mon livre !

En utilisant la puissance du shell, il est relativement facile de scripter l’inclusion d’Ă©lĂ©ments externes comme du code source, des diagrammes ou d’autres fichiers.

Liens et références

Photo de couverture par Arisa Chattasa sur Unsplash