<!--
  canonical-url: https://www.avonture.be/fr/blog/quarto-mustache
  published:     2025-05-15
  This is a static plain-Markdown mirror generated at build time.
  Visit the canonical URL above for the fully rendered page, with images and interactive components.
-->

# Utiliser le templating Mustache avec Quarto

> Maîtrisez le templating Mustache dans Quarto ! Découvrez comment utiliser l'extension quarto-partials pour créer des pages de documentation structurées et répétitives pour votre site statique.

---
![Utiliser le templating Mustache avec Quarto](/img/v2/mustache.webp)

**TL;DR**

Ce tutoriel explique comment utiliser un templating à la Mustache dans Quarto pour créer efficacement une documentation structurée et répétitive destinée à un site statique. Grâce à l'extension `quarto-partials`, vous pouvez définir des blocs de contenu réutilisables (les partials) et les inclure dans plusieurs pages. Le guide couvre la mise en place d'un projet, le passage de variables depuis le frontmatter YAML d'un document vers un partial, et l'utilisation de conditions pour afficher du contenu selon qu'une variable est définie ou non. Une approche idéale pour documenter un grand nombre de fonctionnalités similaires avec une structure cohérente.

J'utilise Quarto pour générer de la documentation ; jusqu'ici, j'ai surtout produit des fichiers `.docx` et `.pdf`.

Récemment, j'ai eu besoin de générer un site statique (HTML) permettant à mes collègues de consulter la documentation du dernier logiciel que j'ai écrit. Je devais décrire près de 80 fonctions : autant de pages web à écrire, chacune avec la même structure (un chapitre description, un chapitre comment l'exécuter, un chapitre comment le configurer, etc.). *Ce besoin a fini par se transformer en le pipeline complet décrit dans [Quarto - How I Built a Self-Documenting Ecosystem for 50+ Projects](/blog/quarto-industrialisation).*

Et forcément, présenté comme ça, on pense à un CMS (content management system) comme Joomla par exemple : pouvoir définir une page standard (un template) qui contiendra les chapitres et, dans chaque chapitre, injecter du contenu sous forme de variables. Ce serait bête d'écrire chaque page à la main, non ?

C'est là qu'intervient l'idée de [Mustache](https://mustache.github.io/). Mustache se définit comme un framework de *Logic-less templates*.

Et comme j'utilise Quarto pour ma documentation, il me faut une extension pour utiliser Mustache : c'est [Quarto-partials](https://github.com/gadenbuie/quarto-partials/tree/main) de Garrick Aden-Buie — une des extensions que je mentionne dans [mes extensions Quarto préférées](/blog/quarto-extensions), mais elle mérite qu'on creuse bien plus loin.

Voici l'idée en une image : une page Markdown dont le chapitre « How to run » est injecté depuis un template partagé, avec une variable remplie depuis le frontmatter de la page elle-même.

![Rendu de la fonctionnalité Contextual Canvas](./images/render_canvas.webp)

Le titre « Contextual Canvas » et sa description sont écrits directement dans la page ; le paragraphe « How to run » en dessous provient d'un template partagé par toutes les pages de fonctionnalités, avec `{{ command }}` remplacé par la valeur déclarée dans le frontmatter de cette page. Construisons ça.

## Créer quelques fichiers {#create-some-files}

Comme toujours, créons quelques fichiers.

Créez un dossier temporaire et placez-vous dedans :

```bash
$ mkdir /tmp/partials && cd $_
```

Créez un fichier `_quarto.yml` avec ce contenu :

**\_quarto.yml**

```yaml
project:
  type: website
  output-dir: .

lang: en

website:
  title: "Playing with Quarto partials"
  page-navigation: true
  bread-crumbs: true
  sidebar:
    style: "docked"
    toc-depth: 4
    contents: auto

<!-- highlight-next-line -->
filters:
  <!-- highlight-next-line -->
  - partials
```

Ce fichier dit à Quarto que nous allons créer un site web : en lançant `quarto render` plus tard, nos pages écrites en Markdown seront converties en pages HTML.

Les deux dernières lignes doivent être présentes pour charger une extension externe appelée `partials` ; installons-la.

Comme nous utilisons Docker, la commande est plutôt longue : `docker run -it --rm -v .:/public -w /public -u $(id -u):$(id -g) ghcr.io/quarto-dev/quarto:latest quarto add gadenbuie/quarto-partials`.

Cela va créer un nouveau dossier `_extensions` contenant partials.

## Découvrons les bases {#lets-discover-the-basics}

L'idée derrière Quarto-partials est de permettre d'écrire une page comme celle ci-dessous : je décris ma première fonctionnalité fictive et, dans le chapitre `How to run`, j'injecte le contenu d'une autre page :

**documentation/canvas.md**

```markdown
---
title: Contextual Canvas
---

## Description

Imagine you're working on a complex project – perhaps writing a research paper, designing a website, or planning a marketing campaign. You have various documents open, browser tabs scattered, and maybe even some handwritten notes nearby. It can feel like juggling multiple realities at once.

*Contextual Canvas* aims to streamline this by creating a dynamic, visual workspace directly within your primary application. Instead of just seeing a static file or interface, you can summon the Canvas, which intelligently pulls in relevant information based on what you're currently working on

## How to run

{{< partial ../_partials/run.md >}}
```

Comme vous le voyez, j'écris la description de ma fonctionnalité (appelée ici « Contextual Canvas ») puis j'ai un chapitre « How to run ». Au lieu d'écrire le mode d'emploi ici, j'inclus un fichier externe (un *template*) appelé `../_partials/run.md`.

Celui-ci utilisera l'extension Quarto-partials : je peux donc mettre dans `../_partials/run.md` des fonctionnalités Mustache, comme l'injection d'une variable.

Voici le template :

**\_partials/run.md**

```markdown
In order to run this action, please run `{{ command }}`.

Existing flags:

  * `--debug`: output debug information,
  * `--no-color`: disable ANSI colors and
  * `--verbose`: enable verbose mode; showing more information on screen
```

Et voilà Mustache en action : comme vous le voyez sur la première ligne, la syntaxe `{{ command }}` affichera le contenu d'une variable appelée `command`. Bien sûr, je dois la déclarer. Revenons à ma documentation pour ajouter ce que Quarto appelle un bloc YAML *frontmatter* :

**documentation/canvas.md**

```markdown
---
title: Contextual Canvas
<!-- highlight-next-line -->
partial-data:
  <!-- highlight-next-line -->
  command: "make canvas"
---

## Description

Imagine you're working on a complex project – perhaps writing a research paper, designing a website, or planning a marketing campaign. You have various documents open, browser tabs scattered, and maybe even some handwritten notes nearby. It can feel like juggling multiple realities at once.

*Contextual Canvas* aims to streamline this by creating a dynamic, visual workspace directly within your primary application. Instead of just seeing a static file or interface, you can summon the Canvas, which intelligently pulls in relevant information based on what you're currently working on

## How to run

{{< partial ../_partials/run.md >}}
```

Il est temps de créer notre site web. Lancez `docker run -it --rm -v .:/public -w /public -u $(id -u):$(id -g) ghcr.io/quarto-dev/quarto:latest quarto render` — le rendu déjà aperçu en haut de cet article.

Cela va appeler la commande `render` de Quarto. Quarto va traiter le fichier `_quarto.yml`, voir que nous voulons produire un site web et lancer le rendu HTML. Ça ne prend qu'une ou deux secondes.

Maintenant, en lançant `docker run -d --name partials -p 8080:80 -v .:/usr/local/apache2/htdocs/ httpd:alpine`, nous créons un container Apache accessible sur le port 8080.

Enfin, rendez-vous sur `http://localhost:8080/documentation/canvas.html` et nous verrons notre site.

Et maintenant, nous sommes prêts à copier/coller le fichier `documentation/canvas.md` vers `documentation/feature_2.md` et à simplement mettre à jour la variable command. Et à répéter l'opération jusqu'à avoir documenté toutes les fonctionnalités.

> Screenshot — http\://localhost:8080/documentation/canvas.html
>
> Notre fonctionnalité Canvas

Prouvons que le template est réutilisable. Créons une nouvelle fonctionnalité :

**documentation/builder.md**

```markdown
---
title: Intent-Driven Interface Builder
partial-data:
  command: "make builder"
---

## Description

Imagine the process of building user interfaces. Developers and designers often spend significant time translating conceptual designs and user stories into the specific widgets, layouts, and interactions of a software application. *Intent-Driven Interface Builder* aims to revolutionize this by allowing users to describe the intent of a UI element or section, rather than manually configuring every pixel and property.

The *Intent-Driven Interface Builder* would then leverage AI and a vast library of pre-built, adaptable UI components and design patterns to automatically generate the corresponding interface elements. It would intelligently choose appropriate widgets, arrange them in a logical layout, and even suggest relevant styling based on the application's overall design language and best practices.

## How to run

{{< partial ../_partials/run.md >}}
```

et générons à nouveau notre site en relançant `docker run -it --rm -v .:/public -w /public -u $(id -u):$(id -g) ghcr.io/quarto-dev/quarto:latest quarto render`.

> Screenshot — http\://localhost:8080/documentation/feature\_2.html
>
> Notre deuxième fonctionnalité

### Ce que nous venons de faire {#what-have-we-just-done}

Nous avons créé un template réutilisable. Notre document comporte deux parties : une section YAML (le frontmatter) où nous fournissons des variables. La seconde partie, celle en Markdown, est notre template de base. Nous pouvons copier celui-ci pour toutes nos fonctionnalités, par exemple.

```markdown
---
partial-data:
  command: "run canvas"
---

## How to run

{{< partial ../_partials/run.md >}}
```

## Mon cas d'usage {#my-use-case}

Dans mon cas, ma documentation ressemble à ceci :

**/documentation/php\_lint.md**

```markdown
---
title: PHP linter
categories: [linting, php]
order: 1
partial-data:
  command: "lint-php"
  config_file: ".phplint.yml"
  config_url: "https://github.com/tengattack/phplint/blob/master/.phplint.yml"
  constant: "PHPLINT"
  type: "PHP"
---

<!-- cspell:ignore phplint -->

## Description

This is a brief description of the functionality. This text is hardcoded because it is different each time, from one feature to another.

## For what type of project

{{< partial ../_partials/project_type.md >}}

## How to run

{{< partial ../_partials/run.md >}}

## How to configure

{{< partial ../_partials/configure/file.md >}}

## Attention points

None.

## Remarks

None.
```

## Tester si une variable est définie ou non (facultatif — à ignorer si vos templates n'ont pas besoin de conditions) {#testing-if-a-variable-is-defined-or-not-optional--skip-if-your-templates-dont-need-conditionals}

Jetons un œil au fichier `is_for.md`.

**/\_partials/project\_type.md**

```markdown
<!-- #type means defined and thus has been set to something like "PHP" or "PYTHON" -->
{{#type}}

This job only for **{{ type }}** project.

{{/type}}

<!-- ^type means empty/missing so, in this case, the command is for all type of projects -->
{{^type}}

This stage is for **all** type of projects.

{{/type}}
```

Deux syntaxes sont utilisées ici : `{{#` et `{{^`.

La première vérifie la présence d'une variable appelée `type` et, si celle-ci est définie, le bloc est traité.

La seconde s'appelle une *Inverted section* et vérifie l'absence de la variable : si `type` n'est pas définie, ce bloc sera interprété.

Si vous regardez mon fichier `php_lint.md`, j'ai bien une variable `type` définie dans ma section `partial-data` (celle utilisée par Quarto-partials).

**/documentation/php\_lint.md**

```markdown
---
title: PHP linter
categories: [linting, php]
order: 1
<!-- highlight-next-line -->
partial-data:
  command: "lint-php"
  config_file: ".phplint.yml"
  config_url: "https://github.com/tengattack/phplint/blob/master/.phplint.yml"
  name: "PHP Linter"
  <!-- highlight-next-line -->
  type: "PHP"
---

<!-- cspell:ignore phplint -->

## Description

This is a brief description of the functionality. This text is hardcoded because it is different each time, from one feature to another.

## For what type of project

{{< partial ../_partials/project_type/is_for.md >}}

## How to run

{{< partial ../_partials/run.md >}}

## How to configure

{{< partial ../_partials/configure/file.md >}}

## Attention points

None.

## Remarks

None.
```

Si je génère mon fichier avec la ligne de commande `quarto render`, je verrai alors `This job only for **PHP** project.` dans ma documentation.

Si ma fonctionnalité concernait tous les types de projets, il me suffit de supprimer la ligne `type: "PHP"` de mon frontmatter. Dans ce cas, je verrai `This stage is for **all** type of projects.`.

Jetons un œil au fichier inclus suivant :

**../\_partials/configure/file.md**

```markdown
<!-- #config_file means defined and thus there is a configuration file -->
{{#config_file}}

{{ name }} uses a settings file named .config/{{ config_file }} ([learn more]({{ config_url }})).

{{/config_file}}

<!-- ^config_file means empty/missing so no configuration file -->
{{^config_file}}

There is no configuration file.

{{/config_file}}
```

Même idée. Si une clé `config_file` est définie dans le frontmatter de la documentation, nous obtiendrons `PHP Linter uses a settings file named .config/.phplint.yml ([Learn more](https://github.com/tengattack/phplint/blob/master/.phplint.yml)).`. Si nous supprimons la ligne `config_file`, nous obtiendrons `There is no configuration file.`.

## Contenu brut (facultatif — à ignorer sauf si une valeur affichée semble mal échappée) {#raw-contents-optional--skip-unless-a-rendered-value-looks-wrongly-escaped}

Lors de l'affichage d'une variable, il faut parfois désactiver le mode d'affichage normal et utiliser ce que Mustache appelle le `raw content`.

Dans l'exemple ci-dessous, j'utilise un caractère `/` qui sera échappé par Mustache.

```yaml
partial-data:
  output: ".output/coverage"
```

```markdown
{{#output}}

The report will be created into your project's directory in a folder called `{{ output }}`.

{{/output}}
```

Le rendu HTML donnera ici `.output&#x2F;coverage` et non `.output/coverage` comme attendu. Comme on le voit, Mustache a échappé notre slash. Il faut donc utiliser le mode contenu brut et la syntaxe `{{& output }}` à la place.

```markdown
{{#output}}

The report will be created into your project's directory in a folder called `{{& output }}`.

{{/output}}
```

## Conclusion {#conclusion}

Une ligne d'extension dans `_quarto.yml`, deux ou trois templates partials, et près de 80 pages de documentation quasi identiques cessent d'être 80 fichiers Markdown copiés-collés : elles deviennent un template plus un petit bloc de frontmatter par page. Voyez [Quarto - How I Built a Self-Documenting Ecosystem for 50+ Projects](/blog/quarto-industrialisation) pour découvrir comment tout cela est devenu un pipeline complet, et [mes extensions Quarto préférées](/blog/quarto-extensions) pour d'autres briques du même genre.
