Retour d'expérience de mon utilisation de markdown et pandoc pour rédiger un manuscrit de thèse de doctorat.
Table des matières
J'ai récemment(au final ça commence presque à dater) écrit une thèse. C'est de loin le plus long document que j'ai écrit, environ 250 pages, et qui n'a rien à voir avec la rédaction d'article de recherche, généralement de 6 à 12 pages. Tous les gens passés par là vous le diront, ce n'est pas un exercice facile, la rédaction occupe généralement les 6 derniers mois du doctorat.
La rédaction de ce type de document requière notamment:
- de nombreux retours avec l'encadrement pour relecture, de courts passages surtout,
- beaucoup de déplacements de section d'un chapitre à l'autre,
- beaucoup de schémas, d'image et de modifications "mineures", typiquement des textes, des flèches.
L'objectif de cet article est de décrire un ensemble d'outils qui m'a permis d'avoir l'environnement le plus productif. Batteries not included: je ne fournis que des pistes et des usages, je n'ai pas maintenu de script ou de conteneur docker prêt à l'emploi.
Premier constat
L'école doctorale demande de respecter une page de garde, un résumé en 4ème de couverture et suivant un certain thème. Idéalement il faut donc suivre un template word ou LaTeX.
En fonction du domaine de recherche, informatique pour moi, la production de diagrammes UML, de formules, la gestion de la bibliographie rendent l'utilisation de LaTeX obligatoire.
Second constat
Rédiger en LaTeX impose une lourdeur, la syntaxe même langage distrait du fond. Insérer une image, un bloc résumé, de l'emphase, impose de mélanger le fond de la forme au point de gêner un des points clés: être en mesure de rapidement se déplacer dans le document et localiser du contenu.
Les phases de relecture sont primordiales afin de vérifier la cohérence, les transitions entre sections ou tout simplement les accords au sein d'une phrase.
L'approche que j'ai privilégié est de rédiger en markdown pour retirer le plus de mise en forme possible puis d'utiliser pandoc pour générer du LaTeX reposant sur un template. Divers filtres pandoc sont utilisés afin de combler aux exigences de LaTeX.
Grossièrement, un ensemble de chapitres markdown sont compilés en LaTeX pour produire un fichier content.tex
intégré dans un template à l'aide de la commande \input{content}
.
Je vais détailler ici les éléments de la stack technique, quelques exemples de filtres et de document source et finir par mon ressentis général sur ce process d'écriture.
Stack technique
Étapes de compilation
- compilation dans l'ordre de chaque chapitre en LaTeX, a) intégration des schémas, b) appel de tous les filtres pour produire un document LaTeX compilable,
- compilation réelle du document LaTeX final avec son template,
- optionnellement vérification que l'ensemble d'un fichier de citation est bien utilisé.
Pas de solutions tout intégrées satisfaisantes
Beaucoup de solutions existent pour produire des livrables à partir de sources markdown (gitbook, voire même des CMS tel que grav peuvent correspondre), ces approches produise des sites web statiques mais plus rarement un format 'livre' ou 'rapport' acceptable.
J'ai donc composé moi même, avec mes outils habituels, sur mon archlinux de l'époque tout était disponible dans les repositories ou via l'AUR, sur une Ubuntu les paquets seraient les suivant:
pandoc pandoc-crossref # via cabal, tout n'est pas disponible dans les dépôts officiels
texlive-xetex # pour xelatex
texlive-luatex # pour checkcites
texlive-bibtex-extra # bibtex
texlive-extra-utils # pour pdfcrop
biber
Drawio fournit un éditeur WYSIWYG de diagrammes, sans formalisme particulier mais intégrant une bibliothèque pour gérer la plupart des standards UML. Sur un ordinateur de bureau l'intégration CLI ne pose pas de problème, en revanche en headless (WSL2 ou docker), la tache semble plus compliquée compte tenu du nombre d'issue github mentionnant des erreurs. Une tentative d'image docker semble toutefois fonctionnelle (coucou Erwan).
Le template final utilisé est produit par un doctorant pour correspondre aux attentes de l'école doctorale (https://gitlab.inria.fr/proman/mathstic-thesis-template). C'est une partie rédigée directement en LaTeX parce qu'elle ne correspond pas aux aller-retours de relecture décrits plus haut.
Pandoc
Pandoc permet d'intégrer des filtres lors de la compilation, ici ce qui nous intéresse c'est markdown vers LaTeX. Comme en compilation, le filtre donne accès à l'AST, la structure de représentation interne de tout type de document, le langage pivot en quelque sorte.
Un filtre peut prendre en entrée un paragraphe, peut le modifier et le retourner; chaque filtre fonctionne de manière isolée par rapport aux autres.
Exemples choisis
Pour mon manuscrit, j'ai rédigé 17 filtres pour pandoc, allant du simple remplacement de texte plus lisible en markdown mais incompatible en LaTeX à la compilation de diagramme pour intégrer un export pdf ou png.
Je présente ici quelques uns de ces filtres afin d'illustrer leur intégration au document source et leur complexité.
- n° pour LaTeX
Le filtre le plus simple, le symbole ° n'est pas supporté en unicode, il faut utiliser \no{}
.
Le filtre va donc s'intéresser uniquement à la compilation vers LaTeX, chercher le texte n°
dans tous le contenu de tous les textes écrits et va le remplacer par la méthode LaTeX équivalent.
Le filtre que j'ai utilisé est le suivant:
function Str(ele)
if FORMAT=="latex" then
local w = ele.text
if w:match('n°') then
w = w:gsub('n°', '\\no{}')
return pandoc.RawInline('tex', w)
end
end
end
- Utilisation de metadonnées pour un chapitre
J'ai choisi de structuré le cœur du manuscrit en chapitre en gardant un fichier par chapitre, 1 axe est étudié dans un chapitre, autant que cela se reflète dans le découpage des fichiers. L'intérêt est de pouvoir compiler uniquement un chapitre, l'envoyer par mail pour relecture ou encore intervertir deux chapitres entre eux.
J'ai choisi de mettre le résumé de chapitre en metadata du fichier markdown pour directement générer un bloc de texte intégré à la mise en page des chapitres.
En considérant le début de fichier suivant:
---
title: 'Contexte'
part: État de l'art
abstract: |
La structure actuelle du réseau grille ne permet pas le développement de générateurs d'énergie réinjectant directement sur le réseau de manière massive. La solution est donc de consommer directement depuis la production locale. Cela nécessite l'ajout d'appareils de mesure, et par définition le développement d'une smartgrid. L'ajout de matériel est très vite limité par le besoin de connaissance des activités, de leurs consommations associées et de leur évolution. Le choix du matériel à installer est primordial et peut s'avérer compliqué à cause de la complexité de leur fonctionnement, notamment pour les moyens de stockage telles que les batteries.
---
L'attribut part
est utilisé pour générer un changement de partie, puis le title
et l'abstract
sont repris dans des balises LaTeX séparées pour produire la mise en page souhaitée:
Pour les chapitres nécessitant une mise en page spéciale des attributs différents peuvent être utilisés:
---
title: 'Remerciements'
no_abstract: true
number: false
---
Ici la page de remerciements ne doit pas utiliser la numérotation des chapitres et n'aura pas de bloc résumé.
- Mise en forme des résumés de fin de chapitre
Pour faciliter la compréhension générale, chaque chapitre se termine par un résumé des idées développées dans ce chapitre.
Pour ce filtre il est plus simple de reposer sur la notion de classe déjà présente, et de l'utiliser sur le délimiteur :::
supporté par Pandoc:
:::{.summary}
L'hétérogénéité des capteurs, services tiers et des algorithmes d'optimisation nécessite la mise en place d'un environnement dédié. Une telle architecture peut passer par un middleware en mesure de proposer une modularité afin de séparer le cœur de l'application de cette hétérogénéité.
Cette modularité liée à l'environnement d'un gestionnaire d'énergie doit s'articuler autour des activités réalisées. Nous choisissons une approche dirigée par les modèles pour permettre à un Expert de construire ses modèles à partir d'un langage dédié. Ces modèles sont ensuite chargés et servent de base à l'optimisation énergétique.
:::
local function contains(t, value)
for _,v in ipairs(t) do
if(v==value) then
return true
end
end
return false
end
function Div(el)
if (contains(el.classes,"summary") and FORMAT=="latex" ) then
local t = {}
table.insert(t, pandoc.RawInline("latex", "\\sectionsummary{"))
local content = el.content[1].content
table.insert(t, pandoc.RawInline("latex", "\\textit{"))
for k,v in pairs(content) do
table.insert(t, v)
end
table.insert(t, pandoc.RawInline("latex", "}"))
table.insert(t, pandoc.RawInline("latex", "}"))
return pandoc.Para(t)
end
end
- Intégration de fichiers externes
Vouloir citer un extrait de code source peut polluer le flux d'un paragraphe et polluer le correcteur grammatical.
La fonction peut s'aisément se rajouter dans notre environnement en mappant vers les fonctions de listings
de LaTeX et l'ajout des bons attributs pour la coloration syntaxique et la construction de la table des listings.
Le filtre que j'ai utilisé est le suivant:
local open = io.open
local reguldsl_default_lstset = {}
local function read_file(path)
local file = open(path, "rb") -- r read mode and b binary mode
if not file then return nil end
local content = file:read "*a" -- *a or *all reads the whole file
file:close()
return content
end
function CodeBlock(el)
if el.classes[1] == "include" then
-- look for a file attribute to know whether we need to replace the text
local file = el.attr['attributes']['file']
for k,v in pairs(reguldsl_default_lstset) do
el.attr['attributes'][k] = v
end
el.attr['attributes']['file'] = nil
el.attr['attributes']['label'] = 'lst:' .. file:gsub('/', ''):gsub('_', '')
--el.attr['attributes']['language'] = 'reguldsl'
el.attr['attributes']['caption'] = pandoc.utils.stringify(el.text)
if file then
el.text = read_file(file)
end
return el
end
end
- Intégration de diagrammes drawio
Une des plus grosses pertes de temps que j'ai eu, et qui a motivé la mise en place de ce système de compilation depuis markdown est la manipulation des diagrammes construits dans un éditeur tiers, tel que drawio.
Drawio est un éditeur graphique de «formes libres» (comprendre qu'il ne respecte pas un formalisme en particulier tel que UML), les fichiers de travail sont en .drawio
(basé sur xml) et les exports peuvent être au format pdf ou png.
Cela nécessite de garder un dossier pour les sources, le versionner, et garder un dossier dédié aux exports que le document LaTeX utilisera. Le cas échéant, les sources markdown peuvent pointer vers ces fichiers d'export, pour les intégrer dans la compilation LaTeX.
fichier .drawio -> export en png/pdf -> compilation LaTeX
J'ai donc écrit un filtre pour directement pouvoir citer le fichier source en markdown, compiler à la volé l'export stocké dans un dossier temporaire, y faire référence côté LaTeX pour directement intégrer l'export png dans le fichier pdf final.
- Pour aller plus loin
J'ai utilisé de nombreux autres filtres pour gérer différentes plaies de la mise en page: les tableaux, les légendes d'image, les groupes d'images (Figure 1.a et 1.b par exemple).
Correction orthographique avancée
En plus de la correction intégrée à n'importe quel éditeur, j'ai utilisé Antidote (à partir de 59€/an) selon moi un indispensable pour des documents de cette taille. Les fonctionnalités importantes non disponibles dans un correcteur classique:
- cohérence des typographies: si un mot autorise plusieurs écritures, notamment les lettres 'y' ou 'i' Antidote s'assure que le choix est le même tout au long du document,
- conjugaison: la détection du sujet d'une phrase est plutôt fiable, Antidote questionne certaines utilisations de conditionnels,
- style: les répétitions, les phrases trop longues.
Antidote permet l'intégration aux outils de rédaction classique (LibreOffice, Word), pas adapté à mon utilisation de Vim ou VSCode. Pour permettre la correction dans Antidote j'ai du passer par l'export Pandoc en texte brut pour nettoyer tout le balisage et copier cela dans l'éditeur Antidote.
De plus, j'avais commencé à définir un dictionnaire personnalisé de mots techniques dans vim :spell
qui n'est pas utilisé par Antidote.
J'ai donc du faire des copier coller chapitre par chapitre pour rapporter les corrections dans le document source, vraiment pas idéal.
Retour d'expérience
Je vais pas me le cacher, développer des outils informatiques autour de la rédaction même du manuscrit de ma thèse a rajouté un côté meta (dont ma thèse n'avait pas besoin) qui permet de se changer les idées.
De manière générale, je suis satisfait du workflow d'écriture que j'avais, j'ai gagné du temps là où le «tout LaTeX» aurait été fastidieux: la gestion des diagrammes hors Tikz et la lisibilité du texte.
En revanche, j'ai développé ces filtres et chaine de compilation pour mon poste, à un instant donné, sur une machine tournant sur Archlinux (distribution Linux très fréquemment mise à jour).
Pour garder un outil utilisable il aurait plus commode d'utiliser docker pour:
- figer une version de drawio dont l'export pdf en ligne de commande semble casser une release sur deux,
- mieux gérer les dépendances pour l'environnement LaTeX.
Pour conclure, je pense que Pandoc à de l'avenir, au moins pour ces milieux «de niche». Je vais continuer à l'utiliser, peut être pour m'amuser avec un export ebook pour ce manuscrit puisque pandoc gère le .epub
ou plus simplement pour de la rédaction technique (markdown pour wiki gitlab et export pdf pour communication externe).