Étendez Pandoc avec Lua

Magazine
Marque
GNU/Linux Magazine
Numéro
199
|
Mois de parution
décembre 2016
|
Domaines


Résumé
Il est parfois nécessaire de convertir un fichier d'un langage de balisage vers un autre : de Markdown vers du HTML, d'Org-mode vers LaTeX, etc. Pandoc est un outil permettant ce type de conversion, et il est capable de gérer un nombre impressionnant de formats différents. Que faire si l'on souhaite utiliser un format ésotérique inconnu de Pandoc ? Il est possible de l'étendre en Lua !

Body


L'objectif

L'architecture de Pandoc peut être comparée à celle d'un compilateur : un lecteur lit le fichier d'origine et crée une représentation intermédiaire ; cette représentation est ensuite convertie vers le format d'arrivée par un écrivain.

Implémenter le support complet d'un nouveau format dans Pandoc consiste donc simplement à écrire un lecteur et un écrivain. Cela semble toutefois fastidieux si le format à implémenter et à maintenir est destiné à rester relativement confidentiel, cantonné à un usage personnel. Il est alors beaucoup plus judicieux d'étendre Pandoc en utilisant le mécanisme prévu à cet effet.

Dans cet article, nous montrerons comment convertir une recette de cuisine écrite en Org-mode en un fichier LaTeX utilisant la classe recipe, et ce afin de générer un joli PDF. Le code complet est disponible à l'adresse suivante : https://framagit.org/Steap/GLMF-articles/tree/master/pandoc-lua-extension.

Les outils

- pandoc (1.17.0.3 dans cet article)

- pdflatex (paquet texlive-latex-base dans Debian)

- la classe LaTeX recipe (paquet texlive-latex-extra dans Debian)

- Lua n'est pas nécessaire

- un éditeur de texte

Pourquoi une extension ?

Nous voulons générer notre recette de cuisine depuis le fichier pizza.org écrit dans le format explicite suivant :

# -*- mode: org -*-

#+TITLE: Pizza/bière façon geek

#+AUTHOR: Cyril Roelandt

#+OPTIONS: toc:nil

#+PERSONNES: 6

* Ingrédients

+ 42 bières

+ 1 téléphone

* Préparation

+ Ouvrir une bière (par personne)

+ Se saisir de son téléphone (un seul pour tout le groupe)

+ Appeler son livreur de pizza préféré

+ Commander poliment ses pizzas

+ Attendre le livreur en buvant une autre bière (par personne)

+ Ouvrir au livreur, échanger les pizzas contre des bitcoins

+ Déguster les pizzas en finissant les bières

Voici le fichier pizza.tex que nous souhaiterions obtenir :

\documentclass[a4paper]{recipe}

\usepackage[T1]{fontenc}

\usepackage[utf8]{inputenc}

\usepackage[french]{babel}

\usepackage{enumitem}

\usepackage{amssymb}

\pagenumbering{gobble} % Disable page numbering

\newcommand{\bsi}[2]{%

  \fontencoding{T1}\fontfamily{pbs}\fontseries{xl}\fontshape{n}%

  \fontsize{#1}{#2}\selectfont}

\renewcommand{\inghead}{\textbf{Ingrédients (pour 6 personnes)}:\ }

\renewcommand{\rechead}{\centering\bsi{24pt}{30pt}}

\makeatletter

\renewcommand*\l@subsubsection{\@dottedtocline{3}{3em}{0em}}

\makeatother

\setlength\parskip{2ex plus 0.5ex}

\begin{document}

\recipe{Pizza/bière façon geek}

\ingred{42 bières ; 1 téléphone ; }

\begin{enumerate}[label=$\blacktriangleright$]

\item Ouvrir une bière (par personne)

\item Se saisir de son téléphone (un seul pour tout le groupe)

\item Appeler son livreur de pizza préféré

\item Commander poliment ses pizzas

\item Attendre le livreur en buvant une autre bière (par personne)

\item Ouvrir au livreur, échanger les pizzas contre des bitcoins

\item Déguster les pizzas en finissant les bières

\end{enumerate}

\begin{flushright}

Une recette de Cyril Roelandt.

\end{flushright}

\end{document}

Il existe déjà dans pandoc un lecteur capable de transformer le fichier Org-mode en une représentation interne. Pandoc fournit également un écrivain capable de générer du LaTeX, mais le code généré n'utilise évidemment pas la classe recipe : nous n'aurons donc pas le rendu voulu. Intégrer et maintenir un nouvel écrivain dans pandoc serait pénible et sans doute inutile pour la plupart des utilisateurs; il est donc tout à fait logique de préférer écrire une extension.

Phase 1 - Vue d'ensemble

Notre extension nécessite deux fichiers :

- un modèle (recipe.template) qui contiendra la structure de notre document LaTeX à générer ;

- un fichier Lua (recipe.lua) qui convertira le contenu de notre fichier Org-mode.

etendez_pandoc_avec_lua_figure_01

Comme on peut le voir sur la figure 1, pandoc, grâce à notre extension, convertira notre fichier source pizza.org en un fichier LaTeX structuré comme nous le souhaitons (pizza.tex) que nous pourrons ensuite convertir en PDF grâce à pdflatex.

Phase 2 - Écriture du modèle

Voici le modèle (recipe.template) que nous allons utiliser :

\documentclass[a4paper]{recipe}

\usepackage[T1]{fontenc}

\usepackage[utf8]{inputenc}

\usepackage[french]{babel}

\usepackage{enumitem}

\usepackage{amssymb}

% D'autres commandes LaTeX vues plus haut, non reprises ici par souci de

% lisibilité.

\begin{document}

\recipe{$title$}

$body$

$if(author)$

\begin{flushright}

Une recette de $author$.

\end{flushright}

$endif$

\end{document}

Il est facilement compréhensible : il contient la structure de notre fichier LaTeX. La partie réellement intéressante est l'utilisation des variables personnes, title et author, qui correspondent aux pragmas utilisés au début de pizza.org. Il est possible de spécifier autant de variables que nécessaire. La valeur d'une variable peut même être définie dynamiquement en lançant pandoc avec l'option -V :

$ pandoc -Vfoo=bar ...

Phase 3 - Code minimal de l'extension

La commande suivante nous permet de générer le code Lua d'une extension implémentant un écrivain HTML :

pandoc --print-default-data-file sample.lua > recipe.lua

Le fichier recipe.lua ainsi généré contient un grand nombre de fonctions Lua permettant de traiter chaque type de nœud (titre, texte, gras, etc.). Chacune de ces fonctions doit retourner une chaîne de caractères.

Nous conservons une implémentation pour les fonctions suivantes :

function Doc(body, metadata, variables)

  return body

end

function Str(s)

  return s

end

function Space()

  return " "

end

La première nous permettra de retourner le contenu du document (cf. la variable body du modèle). Les deux autres nous permettront respectivement de retourner les chaînes de caractères inchangées, et de conserver les espaces.

Nous pouvons « vider » sans risque le contenu des autres fonctions en leur faisant retourner une chaîne de caractères vide ; nous pourrions tout simplement les supprimer, mais il est pratique de les conserver s'il devient nécessaire de les implémenter par la suite :

-- Le même corps pour toutes les autres fonctions.

function Span(s)

  return ""

end

Phase 4 - Makefile

Nous devrions désormais être capables de générer un fichier PDF depuis notre fichier source. Utilisons le Makefile suivant :

%.pdf: %.tex

 pdflatex $<

%.tex: %.org recipe.lua recipe.template

 pandoc -f org -t recipe.lua --template=recipe.template -o $@ $<

clean:

 rm -f *.log *.aux *.pdf *.tex

Il est notamment possible de déverminer notre extension en lisant le fichier pizza.tex produit. Générons maintenant notre PDF :

$ make pizza.pdf

Le fichier produit est valide, mais un peu vide. C'est normal : il nous reste quelques fonctions à réellement implémenter.

Phase 5 - Le code de l'extension

Notre fichier pizza.org est composé de deux listes : une liste d'ingrédients, et une liste d'opérations à effectuer. En regardant le fichier pizza.tex présenté en début d'article, on voit qu'il faudra convertir la première en une commande \ingred, et la deuxième en une liste LaTeX classique. Implémentons donc la fonction Lua BulletList dans recipe.lua :

function BulletList(items)

  if current_section == 'Ingrédients' then

    ret = "\\ingred{"

    for _, item in pairs(items) do

      ret = ret .. item .. " ; "

    end

    ret = ret .. "}\n"

  elseif current_section == 'Préparation' then

    ret = "\\begin{enumerate}[label=$\\blacktriangleright$]\n"

    for _, item in pairs(items) do

      ret = ret .. "\\item " .. item .. "\n"

    end

    ret = ret .. "\\end{enumerate}"

  end

  return ret

end

Nous retenons dans quelle section nous nous trouvons grâce à la variable current_section déclarée en début de fichier et mise à jour à chaque fois que nous rencontrons un titre :

local current_section = ""

...

function Header(lev, s, attr)

  current_section = s

  return ""

end

Trucs et astuces

Quelle que soit l'extension, la méthodologie reste la même que celle présentée ici :

1) Déterminer la «chaîne de compilation» : suffit-il de lancer pandoc, ou d'autres étapes (comme le lancement d'une commande externe) seront-elles nécessaires ?

2) Construire l'extension minimale permettant de lancer toute cette chaîne sans erreur, même si le résultat ne correspond pas à nos attentes.

3) Implémenter les fonctions Lua nécessaires une par une jusqu'à avoir un résultat satisfaisant.

Il est également utile de pouvoir visualiser l'arbre syntaxique généré par le lecteur afin de déverminer une extension. Pandoc fournit cette fonctionnalité et peut afficher l'arbre en JSON :

$ pandoc -f org -t json pizza.org | json_pp

Conclusion

Nous avons vu comment développer une extension Lua implémentant un écrivain simple pour pandoc. La méthode reste la même pour des écrivains plus complexes : cet article a été rédigé en Org-mode puis converti vers le format ODT grâce à une extension pandoc au code un peu plus volumineux.


Sur le même sujet

Pilotez de manière optimale vos afficheurs LED

Magazine
Marque
Hackable
Numéro
34
|
Mois de parution
juillet 2020
|
Domaines
Résumé

Trop souvent, dans les forums et/ou sur les sites web, certains ont tendance à conseiller l'approche « facile » plutôt que l'approche « efficace ». Qui n'a jamais vu un jour quelqu'un répondre « mais utilises donc xxx(), ça marche et c'est plus simple » en réponse à une problématique précise ? C'est là, généralement, le fait de personnes qui n'ont que peu d'expérience ou ne comprennent simplement pas la motivation du demandeur. Voici une petite réalisation destinée à mettre en évidence non seulement l'intérêt de « creuser un peu », mais également une bonne raison de mitiger la valeur des conseils trop rapidement prodigués.

Godot pour coder des jeux, mais pas seulement !

Magazine
Marque
GNU/Linux Magazine
HS n°
Numéro
109
|
Mois de parution
juillet 2020
|
Domaines
Résumé

Godot peut être employé pour développer des jeux en 2D ou en 3D, mais il est également possible de réaliser des interfaces pour des applications plus professionnelles. Le « petit » plus sera de ne développer qu'une seule fois pour de multiples systèmes...

Créez une fake webcam pour modifier l'image de vos visioconférences

Magazine
Marque
GNU/Linux Magazine
Numéro
239
|
Mois de parution
juillet 2020
|
Domaines
Résumé

Avec le confinement, le nombre de visioconférences a augmenté de manière exponentielle. Malheureusement, seul Zoom propose de modifier l'arrière-plan de ce que filme votre caméra, masquant ainsi votre capharnaüm. Je vous propose donc d'utiliser Python et OpenCV pour créer un faux périphérique de webcam utilisable avec n'importe quelle application et sur lequel nous pourrons modifier l'image...

Premiers pas avec GDScript et Godot

Magazine
Marque
GNU/Linux Magazine
HS n°
Numéro
109
|
Mois de parution
juillet 2020
|
Domaines
Résumé

Le langage de scripting par défaut de Godot est le GDScript. Plutôt que de se lancer dans du C# ou encore du Python comme cela est possible, autant utiliser le langage natif du logiciel (surtout quand il s'inspire de Python…).

Écran e-paper NFC : une histoire d'exploration et de code

Magazine
Marque
Hackable
Numéro
34
|
Mois de parution
juillet 2020
|
Domaines
Résumé

Il est difficile de trouver un titre adéquat pour le contenu qui va suivre, car le matériel dont il sera question est tout aussi atypique qu'absolument captivant en termes de fonctionnement. Pire encore, ce n'est pas tant le matériel qui importe que l'approche nécessaire pour obtenir exactement le comportement attendu. Il sera donc ici question de NFC, de papier électronique (e-paper), de C et de la maxime voulant « qu'à cœur vaillant, rien d'impossible ». Sans attendre, embarquez avec moi dans une petite aventure qui, je l'espère, vous apprendra autant qu'elle m'a appris...

À nous, Markdown !

Magazine
Marque
GNU/Linux Magazine
HS n°
Numéro
109
|
Mois de parution
juillet 2020
|
Domaines
Résumé

Le Markdown est devenu incontournable. Il est facilement manipulable, stockable, versionnable et partageable. Un moteur de rendu automatique peut transformer toute la substantifique moelle de l’auteur en un ouvrage broché digne de la Bible de Gutenberg. Alors, pourquoi ne pas appliquer ce modèle à l’écriture de nouveaux articles pour notre cher magazine ?

Par le même auteur

Empaquetez (facilement) votre projet avec upt

Magazine
Marque
GNU/Linux Magazine
Numéro
222
|
Mois de parution
janvier 2019
|
Domaines
Résumé
Les logiciels que nous utilisons sur nos distributions GNU/Linux et *BSD proviennent généralement des dépôts associés à ces dernières. Ils y sont présents, car les mainteneurs de notre OS préféré les ont empaquetés : cela nous évite de devoir recompiler les sources nous-mêmes, de résoudre les problèmes de dépendances ou de parfaire l'intégration avec le reste de notre système. Nous verrons dans cet article comment les empaqueteurs ont réussi à automatiser une partie de leur travail. Nous proposerons ensuite une solution logicielle permettant à toutes les distributions d'empaqueter facilement le code disponible sur des plateformes telles que PyPI, RubyGems ou CPAN.

Faut s’démener au FOSDEM !

Magazine
Marque
GNU/Linux Magazine
Numéro
213
|
Mois de parution
mars 2018
|
Résumé
Le FOSDEM (Free and OpenSource Developers European Meeting) est tellement incontournable qu’une part non négligeable des auteurs de GLMF s’y rend chaque année. De nombreuses mains et points de vue ont donc participé à ce compte-rendu, pour vous faire part du foisonnement de ce week-end intense.

Étendez Pandoc avec Lua

Magazine
Marque
GNU/Linux Magazine
Numéro
199
|
Mois de parution
décembre 2016
|
Domaines
Résumé
Il est parfois nécessaire de convertir un fichier d'un langage de balisage vers un autre : de Markdown vers du HTML, d'Org-mode vers LaTeX, etc. Pandoc est un outil permettant ce type de conversion, et il est capable de gérer un nombre impressionnant de formats différents. Que faire si l'on souhaite utiliser un format ésotérique inconnu de Pandoc ? Il est possible de l'étendre en Lua !