# Discovering Quarto A series of 12 article(s), in reading order. # Exécuter Quarto Markdown dans Docker > Exécutez Quarto Markdown dans Docker pour générer facilement de la documentation et des slideshows. Apprenez à construire votre propre image Docker et à rendre du Markdown en PDF, HTML et Reveal.js. --- ![Exécuter Quarto Markdown dans Docker](/img/v2/quarto.webp) **TL;DR** Cet article montre comment exécuter Quarto (un outil basé sur Pandoc qui convertit du Markdown en PDF, HTML, Word, ePub ou slideshows reveal.js) via Docker, soit en construisant une image personnalisée à partir d'un `Dockerfile`, soit en utilisant l'image prête à l'emploi `ghcr.io/quarto-dev/quarto`. Il détaille le rendu d'un même fichier Markdown en PDF, en HTML et en présentation reveal.js multi-slides avec `quarto render test.md --to `. [Quarto](https://quarto.org/) est un outil pour produire des PDF, des documents Word, des pages web HTML, des fichiers ePub, des slideshows et bien d'autres sorties encore à partir d'un fichier Markdown. Avec Quarto, vous pouvez transformer n'importe quel contenu markdown en un nouveau PDF par exemple. Quarto propose énormément de fonctionnalités, auxquelles s'ajoutent les extensions de sa communauté, ce qui en fait un outil vraiment pratique pour quiconque souhaite produire de la documentation. Personnellement, je n'ai plus utilisé de traitement de texte du genre Word depuis plusieurs années ; ni PowerPoint — je ne sais même plus quand c'était la dernière fois. Et pourtant, je produis beaucoup de documentation et de slideshows. J'écris tout en Markdown et je génère des PDF ou des slideshows depuis le même contenu. Jusqu'il y a peu, j'utilisais [pandoc](https://pandoc.org/) mais, après avoir pris le temps d'explorer Quarto, c'est nettement plus puissant. Comme toujours sur ce blog, vous n'allez pas installer Quarto à l'ancienne. Vous allez l'exécuter depuis une image Docker — et vous n'avez même pas besoin de construire cette image vous-même. ## Générez votre premier PDF, sans rien installer {#render-your-first-pdf-without-installing-anything} Créez un dossier temporaire (`mkdir -p /tmp/docker-quarto && cd $_`), déposez-y un fichier Markdown nommé `test.md` et lancez : ```bash $ docker run -it --rm -v .:/input -w /input -u $(id -u):$(id -g) ghcr.io/quarto-dev/quarto:latest quarto render test.md --to pdf ``` ```bash $ docker run -it --rm -v .:/input -w /input -u $(id -u):$(id -g) cavo789/quarto quarto render test.md --to pdf pandoc to: latex output-file: test.tex standalone: true pdf-engine: xelatex variables: graphics: true tables: true default-image-extension: pdf metadata documentclass: scrartcl classoption: - DIV=11 - numbers=noendperiod papersize: letter header-includes: - \KOMAoption\{captions}\{tableheading} block-headings: true Rendering PDF running xelatex - 1 This is XeTeX, Version 3.141592653-2.6-0.999996 (TeX Live 2024) (preloaded format=xelatex) restricted \write18 enabled. entering extended mode running xelatex - 2 This is XeTeX, Version 3.141592653-2.6-0.999996 (TeX Live 2024) (preloaded format=xelatex) restricted \write18 enabled. entering extended mode Output created: test.pdf ``` Un fichier `test.pdf` se trouve maintenant à côté de votre Markdown, vous en êtes le propriétaire, et il ressemble à ceci : ![Votre fichier PDF](./images/pdf_version.webp) Cette image est l'image officielle de Quarto — rien à construire, rien d'installé sur votre machine, et le container se supprime tout seul dès que le rendu est terminé. > **Rappel sur la CLI Docker:** > > Pour rappel, les options de la commande Docker run utilisées sont (presque toujours les mêmes) : > > * `-it` pour démarrer Docker en mode interactif, ce qui permet au script exécuté dans le container de vous poser des questions par exemple, > * `--rm` pour demander à Docker de tuer et supprimer le container dès que le script a été exécuté (sinon vous vous retrouverez avec une longue liste de containers Docker arrêtés mais non supprimés ; vous pouvez le vérifier en n'utilisant pas le flag `--rm` puis en lançant `docker container list` dans la console), > * `-v .:/input` pour partager votre dossier courant avec un dossier appelé `/input` dans le container Docker, > * `-w /input` pour indiquer à Docker que le répertoire courant, dans le container, sera le dossier `/input`, > * `-u $(id -u):$(id -g)` demande à Docker de réutiliser vos identifiants locaux : ainsi, quand un fichier est modifié ou créé dans le container, il vous appartiendra, > * ensuite le nom de l'image Docker de Quarto, et, enfin, > * `quarto render test.md --to pdf` c'est-à-dire la ligne de commande à lancer dans le container. > **Masquer les informations non essentielles:** > > Ajoutez l'argument CLI `--log-level warning` à Quarto pour ne lui faire afficher que les avertissements (et les erreurs). La sortie non essentielle sera masquée et vous garderez une console propre. ## Le fichier source {#the-source-file} Voici le `test.md` utilisé ci-dessus : **/tmp/docker-quarto/test.md** ```markdown # What is Quarto? Explain like I'm five Imagine you want to write a story or a report, but instead of using a fancy computer program, you use plain text. That's kind of like Markdown, a simple language that lets you format your text without getting too complicated. Now, Quarto is like a super-powered writing tool that understands Markdown and can also help you write code in different languages, like R or Python. It's like having a helper in your writing process, making things easier and more fun. So, if you want to create documents, presentations, or even books, Quarto and Markdown can be your friends. They'll help you organize your thoughts, add cool features, and even share your work with the world. ``` **Référez-vous à la documentation officielle de [Quarto](https://quarto.org/) pour tout savoir en détail sur le Markdown qu'il accepte.** La seule chose qui change d'un format de sortie à l'autre, c'est l'argument `--to`. ## Le même fichier, en HTML et en slideshow {#the-same-file-as-html-and-as-a-slideshow} Modifiez simplement l'argument `--to` et remplacez `pdf` par `html` : `docker run -it --rm -v .:/input -w /input -u $(id -u):$(id -g) ghcr.io/quarto-dev/quarto:latest quarto render test.md --to html --log-level warning` Vous avez maintenant un fichier `test.html` dans votre répertoire. Pour un slideshow, l'argument `--to` doit valoir `revealjs` : `docker run -it --rm -v .:/input -w /input -u $(id -u):$(id -g) ghcr.io/quarto-dev/quarto:latest quarto render test.md --to revealjs --log-level warning` Ouvrez le fichier `test.html` et vous obtiendrez ceci : ![Revealjs - un slide](./images/revealjs_version1.webp) Ok, vous n'avez qu'un seul slide pour l'instant. Réouvrez le fichier `test.md` et insérez des *sauts de slide*. Cela se fait avec la syntaxe `----` : **/tmp/docker-quarto/test.md** ```markdown # What is Quarto? Explain like I'm five Imagine you want to write a story or a report, but instead of using a fancy computer program, you use plain text. That's kind of like Markdown, a simple language that lets you format your text without getting too complicated. ---- Now, Quarto is like a super-powered writing tool that understands Markdown and can also help you write code in different languages, like R or Python. It's like having a helper in your writing process, making things easier and more fun. ---- So, if you want to create documents, presentations, or even books, Quarto and Markdown can be your friends. They'll help you organize your thoughts, add cool features, and even share your work with the world. ``` Relancez la commande `--to revealjs` pour régénérer le slideshow en tant que fichier `test.html`. > **Information:** > > Lancez simplement `docker run -d --name static-site -p 8080:80 -v .:/usr/local/apache2/htdocs/ httpd:alpine` puis rendez-vous sur `http://127.0.0.1:8080/test.html` pour voir votre slideshow. Votre slideshow comporte maintenant trois slides (appuyez sur `espace` ou sur les flèches pour naviguer) : ![Revealjs - slide 1](./images/revealjs_slide1.webp) ![Revealjs - slide 2](./images/revealjs_slide2.webp) ![Revealjs - slide 3](./images/revealjs_slide3.webp) > **Déployez simplement votre slideshow en ligne:** > > Le plus sympa, c'est que votre slideshow est prêt à être déployé sur votre serveur distant. Copiez le fichier html et le dossier associé (dans notre cas ici, le fichier `test.html` et le dossier `test_files`) sur votre serveur FTP par exemple et votre site sera accessible publiquement. Pas mal, non ? ## Construire votre propre image (facultatif — passez si l'image officielle suffit) {#build-your-own-image-optional--skip-it-if-the-official-one-is-enough} Il existe un certain nombre d'images prêtes à l'emploi sur Internet pour répondre à vos besoins. Vous les trouverez sur . L'image dite `Tier 0` — celle utilisée ci-dessus, `ghcr.io/quarto-dev/quarto:latest` — convient pour générer des sorties html / revealjs. Vous voudrez votre propre image le jour où vous aurez besoin de quelque chose qu'elle ne fournit pas : un package LaTeX pour une mise en page PDF particulière, R ou Python pour des cellules de code exécutables, une police, une extension Quarto intégrée. Créez un nouveau fichier appelé `Dockerfile` (il n'y a pas d'extension) avec ce contenu : **Dockerfile** ```docker # cspell:disable # Version number of Quarto to download and use # See https://github.com/quarto-dev/quarto-cli/pkgs/container/quarto for existing versions ARG QUARTO_VERSION=1.10.18 ARG OS_USERNAME=quarto ARG UID=1000 ARG GID=1000 FROM ghcr.io/quarto-dev/quarto:${QUARTO_VERSION} # librsvg2-bin is to allow SVG conversion when rendering a PDF file # (will install the rsvg-view binary) RUN --mount=type=cache,target=/var/cache/apt,rw \ set -e -x && \ apt-get update -y \ && apt-get install -y --no-install-recommends \ ca-certificates \ git \ pandoc \ pandoc-citeproc \ curl \ gdebi-core \ librsvg2-bin \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* USER root # The input folder will be the one where we'll put our files before the conversion. # We should do this as root since it's a folder under / RUN set -e -x && \ mkdir -p /input # Should be done for the user; won't work if done for root # (quarto will say that "tinytex is not installed") ARG OS_USERNAME ARG UID ARG GID RUN set -e -x && \ groupadd -g ${GID} -o "${OS_USERNAME}" && \ useradd -l -m -u ${UID} -g ${GID} -o -s /bin/bash "${OS_USERNAME}" USER "${OS_USERNAME}" WORKDIR / # Install tools like tinytex to allow conversion to PDF RUN set -e -x && \ quarto install tool tinytex --update-path && \ ~/.TinyTeX/bin/x86_64-linux/tlmgr update --self --all && \ ~/.TinyTeX/bin/x86_64-linux/fmtutil-sys --all && \ # See https://github.com/rstudio/tinytex/issues/426 for explanation # Run tlmgr install for a few tinyText packages (needed for PDF conversion) ~/.TinyTeX/bin/x86_64-linux/tlmgr install fvextra footnotebackref pagecolor sourcesanspro sourcecodepro titling ``` Ceci fait, lancez `docker build -t cavo789/quarto .` et après environ trois minutes la première fois, vous obtiendrez votre propre image Docker : ```bash $ docker build -t cavo789/quarto . [+] Building 183.3s (9/9) FINISHED docker:default => [internal] load build definition from Dockerfile 0.1s => => transferring dockerfile: 1.80kB 0.0s => [internal] load metadata for ghcr.io/quarto-dev/quarto:1.6.36 0.7s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [stage-0 1/6] FROM ghcr.io/quarto-dev/quarto:1.6.36@sha256:7c800f23602e532de8d2e35de8 19.6s => => resolve ghcr.io/quarto-dev/quarto:1.6.36@sha256:7c800f23602e532de8d2e35de838e9f5331 0.0s => => sha256:7c800f23602e532de8d2e35de838e9f53310af381daa34cf3d733c05fd7dec72 955B / 955B 0.0s => => sha256:a9220a0c1eb7e69d971f705b026870f03e511a0c6a95ae0d1140d45089c2 1.73kB / 1.73kB 0.0s => => sha256:7478e0ac0f23f94b2f27848fbcdf804a670fbf8d4bab26df842d40a10c 30.44MB / 30.44MB 3.2s => => sha256:14a397168681603ca2ef7ec883ab375f79cc39ed6abfc602de3d16d 126.00MB / 126.00MB 13.0s => => sha256:682f5bc1f33ef91c217708dbd0f9c287f896d05632b47cdeb1ced02 130.07MB / 130.07MB 16.1s => => extracting sha256:7478e0ac0f23f94b2f27848fbcdf804a670fbf8d4bab26df842d40a10cd33059 1.8s => => extracting sha256:14a397168681603ca2ef7ec883ab375f79cc39ed6abfc602de3d16dc40197b37 0.7s => => extracting sha256:682f5bc1f33ef91c217708dbd0f9c287f896d05632b47cdeb1ced02166e3aa8d 3.2s => [stage-0 2/6] RUN --mount=type=cache,target=/var/cache/apt,rw set -e -x && ap 79.5s => [stage-0 3/6] RUN set -e -x && mkdir -p /input 0.4s => [stage-0 4/6] RUN set -e -x && groupadd -g 1000 -o "quarto" && useradd -l -m - 0.6s => [stage-0 5/6] RUN set -e -x && quarto install tool tinytex --update-path && ~ 80.4s => exporting to image 1.9s => => exporting layers 1.9s => => writing image sha256:f7222fdd5856beb8fb209a609fc9271a0f8f3a586449bc5d90a6b338e54453 0.0s => => naming to docker.io/cavo789/quarto 0.0s ``` > **Choisissez votre propre nom:** > > L'instruction précédente `docker build -t cavo789/quarto .` a créé une image appelée `cavo789/quarto`. Vous pouvez évidemment choisir un autre nom sans le moindre impact sur l'image. À partir de là, toutes les commandes de cet article fonctionnent de la même façon : remplacez juste `ghcr.io/quarto-dev/quarto:latest` par `cavo789/quarto`. Vous pouvez vérifier rapidement la taille de votre image ; elle est plutôt énorme mais, sauf si vous êtes vraiment à court de mémoire ou d'espace disque, ce n'est pas un problème. ```bash $ docker image list | grep quarto cavo789/quarto latest fe1d20bd71a6 1 minute ago 1.55GB ``` ## Pour aller plus loin {#going-further} Un seul fichier Markdown, trois valeurs de `--to`, trois livrables complètement différents — et pas une seule application installée sur votre machine. C'est ce que je trouve encore remarquable : la source de mon PDF, de ma page web et de mon slideshow est le même fichier texte, donc ils ne peuvent jamais diverger. Une fois à l'aise avec Quarto dans un simple container Docker, deux étapes suivantes s'imposent naturellement : transformer cette configuration en un vrai [devcontainer](/blog/quarto-devcontainer) VSCode (vous ouvrez le projet, tout est préinstallé et le hot-reload fonctionne directement), et parcourir [mes extensions Quarto préférées](/blog/quarto-extensions) pour enrichir votre documentation. Pour le volet slideshow en profondeur, voyez [Level Up Your Presentations with Quarto, reveal.js, Decktape, Docker and DevContainers](/blog/running-revealjs-with-docker). --- # Affichage conditionnel avec Quarto > Apprenez à utiliser les fonctionnalités d'affichage conditionnel de Quarto (content-visible, content-hidden) pour contrôler quel contenu apparaît dans les différents formats de sortie (HTML, PDF, Word, slides) à partir d'un seul fichier Markdown. --- ![Affichage conditionnel avec Quarto](/img/v2/quarto.webp) **TL;DR** Cet article présente les blocs conditionnels `content-visible`/`content-hidden` de Quarto (`::: {.content-visible when-format="html"}`). Ils permettent à une seule source Markdown de produire un contenu différent selon le format de sortie — pratique pour masquer les tableaux trop larges du PDF, retirer les longs listings de code d'un diaporama, ou ajouter une slide de clôture « Merci / Des questions ? » uniquement à la sortie reveal.js. Comme moi, vous avez sûrement abandonné Microsoft Word, PowerPoint et compagnie pour tout faire en Markdown. Comme moi, vous utilisez le même fichier markdown pour générer votre documentation en `html`, `pdf`, `docx`, `pptx`, `revealjs`, ... selon les besoins, au cas par cas. Mon cas d'usage personnel est le suivant. J'ai un seul fichier `readme.md`, parfois volumineux, pour la documentation d'un outil que j'ai créé. Dans ce document, je peux afficher par exemple des tableaux comportant plusieurs colonnes, parfois très larges. Aucun problème si j'affiche la documentation en HTML : le navigateur propose une barre de défilement horizontale. Mais quand je génère un PDF, le tableau *explose* la largeur de ma page et le résultat est affreux. Même idée : si ma sortie est une page HTML, je peux facilement afficher un listing de quelques dizaines de lignes de mon code source, par exemple pour illustrer une fonctionnalité. En revanche, dans un diaporama, ce serait contre-productif : la slide serait énorme et peu efficace. Et si mon objectif est de générer un diaporama, la dernière partie de ma documentation sera un chapitre *Merci de votre attention ! Avez-vous des questions ?*. Celui-là ne doit certainement pas apparaître dans la sortie `docx` / `pdf` / `html`. Juste pour l'affichage [`revealjs`](/blog/quarto-revealjs-tips) (ou [PowerPoint](/blog/quarto-powerpoint), si votre public y tient). > **Image Docker avec Quarto:** > > Si vous n'avez pas encore d'image Docker avec Quarto, lisez cet article [Running Quarto Markdown in Docker](/blog/docker-quarto). ## content-visible when {#content-visible-when} La syntaxe est très simple : il suffit d'utiliser le tag spécial `:::` pour délimiter une zone dans le fichier, puis d'utiliser un filtre spécial appelé `content-visible`. Pour la syntaxe en détail, voyez la [documentation officielle](https://quarto.org/docs/authoring/conditional.html) Voyons ça en action. Créez un simple fichier markdown (`conditional-display.md`) sur votre disque et appelez quarto avec `quarto render conditional-display.md --to xxx` en remplaçant `xxx` par, par exemple, `docx`, `pdf`, `html`, `revealjs`, ... **conditional-display.md** ```markdown # Conditional display ::: {.content-visible when-format="html"} This paragraph will only appear in HTML. ::: ::: {.content-visible when-format="docx"} And this one only when the output format is Word ::: ::: {.content-visible when-format="revealjs"} I'm part of the slideshow ::: ``` En rendant l'exemple markdown ci-dessus vers Word, nous obtenons le titre et le paragraphe `when-format="docx"` : ![Affichage conditionnel avec Quarto - docx](./images/docx.webp) Pour `when-format="html"` aussi, seulement le titre et un paragraphe : ![Affichage conditionnel avec Quarto - html](./images/html.webp) Pour `when-format="revealjs"`, c'est un peu plus malin puisque revealjs est un document HTML : ![Affichage conditionnel avec Quarto - revealjs](./images/revealjs.webp) Et comme nous n'avons aucun paragraphe non conditionnel, la conversion en PDF ne donne que le titre. ![Affichage conditionnel avec Quarto - pdf](./images/pdf.webp) Le `when-format="xxx"` fonctionne donc exactement comme prévu. > **`.content-hidden`:** > > L'exact opposé est possible avec `.content-hidden`, voyez la [documentation](https://quarto.org/docs/authoring/conditional.html#content-hidden). --- # Utiliser Quarto pour créer un diaporama PowerPoint > Découvrez comment convertir votre documentation Markdown en diaporama PowerPoint (.pptx) ou en présentation reveal.js en ligne avec Quarto. --- ![Utiliser Quarto pour créer un diaporama PowerPoint](/img/v2/quarto.webp) **TL;DR** Cet article montre comment convertir un fichier Markdown en présentation PowerPoint `.pptx` avec `quarto render slides.md --to pptx` : les titres de niveau 2 deviennent des titres de slide et une ligne `---` démarre une nouvelle section. Et la même source peut aussi être rendue en diaporama reveal.js en ligne avec `--to revealjs`. Quarto peut convertir un fichier markdown en [un diaporama HTML revealjs](/blog/quarto-revealjs-tips) mais peut aussi créer un fichier `pptx` que vous pouvez ouvrir et lancer dans Microsoft PowerPoint. Dans cet article, nous allons créer un fichier `pptx` à partir de notre documentation markdown. ## Du Markdown vers PowerPoint en une commande {#from-markdown-to-powerpoint-in-one-command} Prenez un simple fichier Markdown nommé `slides.md` et lancez : ```bash $ quarto render slides.md --to pptx ``` Ouvrez le `.pptx` obtenu, et voici votre première slide : ![PowerPoint - Slide 1](./images/pptx_slide_1.webp) La règle mérite d'être retenue parce qu'elle résume toute la syntaxe : **chaque titre `##` devient un titre de slide, et une ligne `---` démarre une nouvelle slide**. La structure de votre document *est* la structure de votre diaporama. ## Le fichier source {#the-source-file} Voici le `slides.md` qui a produit la présentation — le contenu est du remplissage, c'est la structure qui compte : **slides.md** ```markdown --- title: "My thesis in Latin" --- ## Chapter 1 Voluptatem minus labore architecto sed voluptas molestiae perferendis. Expedita magni facere. Ullam non non sint qui provident. ## Chapter 2 Iure repudiandae perferendis maiores dolorem consequuntur exercitationem suscipit. --- ### Chapter 2.1 Quis voluptate est quis in ea veniam qui incididunt ad cillum nostrud. ``` Et les trois slides restantes qu'il génère : ![PowerPoint - Slide 2](./images/pptx_slide_2.webp) ![PowerPoint - Slide 3](./images/pptx_slide_3.webp) ![PowerPoint - Slide 4](./images/pptx_slide_4.webp) Vous trouverez beaucoup d'astuces sur la [page de documentation officielle](https://quarto.org/docs/presentations/powerpoint.html). > **Image Docker avec Quarto:** > > Si vous n'avez pas encore d'image Docker avec Quarto, lisez cet article [Running Quarto Markdown in Docker](/blog/docker-quarto). ## Conclusion {#conclusion} Ce qui est agréable ici, ce n'est pas que Quarto produise des fichiers PowerPoint — c'est que votre documentation et votre diaporama deviennent le même fichier. Mettez un chapitre à jour, relancez le rendu, et la présentation est à jour. Pas de copier/coller dans un éditeur de slides, pas deux versions du même contenu qui divergent. Et ce même `slides.md` peut devenir un diaporama en ligne : lancez `quarto render slides.md --to revealjs` à la place, mettez le résultat sur un serveur web, et tout le monde peut consulter votre travail depuis un navigateur. Voyez [Quarto - revealjs tips](/blog/quarto-revealjs-tips) pour découvrir ce que vous pouvez en faire. --- # Utiliser des variables depuis un fichier externe dans votre projet Quarto > Arrêtez le hardcoding ! Apprenez à gérer et réutiliser facilement vos variables dans votre documentation Quarto grâce à des fichiers YAML externes et des variables d'environnement. --- ![Utiliser des variables depuis un fichier externe dans votre projet Quarto](/img/v2/quarto.webp) **TL;DR** Cet article montre comment éviter de coder en dur des valeurs récurrentes (IP, chemins, emails) dans une documentation Quarto : on les stocke dans un fichier `_variables.yml` et on y fait référence avec les short codes `{{< var xxx >}}`, ou via `{{< meta xxx >}}` pour les métadonnées du document. Il couvre aussi la lecture de valeurs depuis un fichier `.env` avec `{{< env xxx >}}`, chargé avec `--env-file` lors du rendu de Quarto dans Docker. Mon cas d'usage est le suivant : je dois rédiger une longue documentation technique où je dois fournir des informations comme les adresses IP des serveurs utilisés, des chemins vers l'application, des dossiers de configuration, ... La *manière normale de faire* consiste à mettre ces informations directement dans la documentation et à veiller à mettre à jour chaque occurrence en cas de changement durant le cycle de vie de l'application. La meilleure approche est probablement d'utiliser un fichier externe où l'information est stockée sous forme clé-valeur et, pendant le processus de rendu de Quarto, de remplacer les short codes par les valeurs. ## Ce que les variables Quarto vous apportent {#what-quarto-variables-do-for-you} Vous écrivez ceci dans votre source Markdown : ```markdown Version {{< var version >}} is a minor upgrade. Please contact us at {{< var email.info >}}. ``` Et voici ce que Quarto affiche : ![Utiliser des variables avec Quarto](./images/variables.webp) Le numéro de version et l'adresse email vivent désormais dans un seul fichier. Changez-les là, relancez le rendu, et chaque page de la documentation est à jour — y compris les vingt endroits que vous auriez oubliés. Il suffit de : un `_variables.yml` contenant vos clés-valeurs, un `_quarto.yml` (qui peut être vide), et les short codes `{{< var >}}` / `{{< meta >}}` dans votre texte. ## Mise en place {#setting-it-up} > **Image Docker avec Quarto:** > > Si vous n'avez pas encore d'image Docker avec Quarto, lisez cet article [Running Quarto Markdown in Docker](/blog/docker-quarto). Vous trouverez la documentation officielle sur . Voici comment procéder : 1. D'abord, c'est obligatoire, vous devez avoir un fichier nommé `_quarto.yml` dans le même répertoire que le fichier (disons `documentation.md`) que vous allez convertir avec Quarto. *Note : ce fichier peut être vide (voir pour plus d'informations).* 2. Ensuite, vous devez créer un fichier nommé `_variables.yml` avec vos clés-valeurs et, enfin, 3. Il vous faut votre fichier markdown. Donc, `_quarto.yml` peut rester vide. Sa présence sert juste à dire à Quarto que le fichier markdown fait partie d'un projet. Voici un exemple de ce que peut contenir un `_variables.yml` : **\_variables.yml** ```yaml version: 1.2 email: info: info@example.com support: support@example.com engine: jupyter: "[Jupyter](https://jupyter.org)" knitr: "[Knitr]()" ``` Et voici un exemple de markdown (fichier `documentation.md`) : **documentation.md** ```markdown --- title: Testing of variables short code. --- {{< meta title >}} Version {{< var version >}} is a minor upgrade. Please contact us at {{< var email.info >}}. Quarto includes {{< var engine.jupyter >}} and {{< var engine.knitr >}} computation engines. ``` Comme vous pouvez le voir, le short code ressemble à `{{< meta xxx >}}` ou `{{< var xxx >}}`. `meta` sert aux métadonnées du document, comme son titre, et `var` à récupérer des informations depuis `_variables.yml`. Lancez le rendu avec `quarto preview documentation.md --to html` et vous obtenez la page montrée au début de cet article. ## Variables d'environnement {#environment-variables} Vous pouvez aussi récupérer des variables d'environnement avec `{{< env xxx >}}` mais, là, vous devez d'abord charger ces variables si nécessaire. Par exemple, vous pouvez avoir un fichier `.env` comme celui-ci : **.env** ``` APPLICATION_NAME=My application name VERSION_NUMBER=1.2 ``` Ensuite, avant d'appeler le processus de rendu de Quarto, vous devez charger le fichier. Comme j'utilise Docker, je fais ceci : ```bash $ docker run --rm -it -v .:/input -w /input --env-file .env cavo789/quarto quarto preview documentation.md --to html ``` Voici le contenu de `documentation.md` : **documentation.md** ```markdown --- title: Testing of variables and env short codes. --- :::{.callout-tip} ## Environment variables {{< env APPLICATION_NAME >}} v.{{< env VERSION_NUMBER >}} ::: {{< meta title >}} Version {{< var version >}} is a minor upgrade. Please contact us at {{< var email.info >}}. Quarto includes {{< var engine.jupyter >}} and {{< var engine.knitr >}} computation engines. ``` J'utilise donc `--env-file .env` dans l'instruction `docker run` pour que Docker charge mes variables et les rende disponibles dans le container. Quarto peut alors y accéder. ![Utiliser des variables d'environnement](./images/environment.webp) Cette solution est encore meilleure si vous avez une application comme Laravel, et donc déjà un fichier .env de ce type. Résultat : vous réutilisez les mêmes valeurs pour l'application et pour la documentation. ## Conclusion {#conclusion} Les variables sont le bon outil quand ce qui se répète est une *valeur* : un numéro de version, une IP, une adresse de support, un chemin. Elles coûtent deux fichiers et sont rentabilisées dès le premier serveur renommé. Quand ce qui se répète est une structure de page entière plutôt qu'une valeur, les variables ne suffisent plus — et c'est là que [Using Mustache templating with Quarto](/blog/quarto-mustache) prend le relais. --- # Quelques astuces pour Quarto lors du rendu en diaporama reveal.js > Améliorez vos présentations Quarto ! Découvrez les astuces essentielles pour produire de superbes diaporamas reveal.js : slides de titre personnalisées, positionnement du logo, personnalisation CSS/JS et mises en page avancées. --- ![Quelques astuces pour Quarto lors du rendu en diaporama reveal.js](/img/v2/quarto.webp) **TL;DR** Cet article est un pot-pourri d'astuces Quarto pour reveal.js : slides de titre, positionnement personnalisé du logo avec CSS/JS, images de fond et leur comportement `background-size`, contrôle du `slide-level`, emoji, classes `r-fit-text` et `r-stretch`, apparitions basées sur les fragments, mises en page en colonnes et en quadrants, short codes personnalisés, CSS inline et externe, masquage des légendes d'images, style des callouts, navigation verticale/en grille, slugs de slides personnalisés, numéros de slides et notes du présentateur. La [documentation reveal.js de Quarto](https://quarto.org/docs/reference/formats/presentations/revealjs.html) est le meilleur endroit pour trouver les instructions permettant de créer des présentations reveal.js impeccables. Dans cet article, nous allons passer rapidement en revue quelques astuces pour rendre vos présentations reveal.js encore meilleures. *Pour le workflow complet — écrire, prévisualiser et exporter un diaporama reveal.js en HTML et en PDF depuis un devcontainer — voir [Level Up Your Presentations with Quarto, reveal.js, Decktape, Docker and DevContainers](/blog/running-revealjs-with-docker).* > **Image Docker avec Quarto:** > > Si vous n'avez pas encore d'image Docker avec Quarto, lisez cet article [Running Quarto Markdown in Docker](/blog/docker-quarto). ## Travailler avec les slides {#working-with-slides} ### Créer une slide de titre {#create-a-title-slide} La [slide de titre](https://quarto.org/docs/presentations/revealjs/advanced.html#title-slide) est la première de votre présentation. Vous pouvez ajouter une telle slide avec quelques lignes de yaml : **slides.md** ```markdown --- title: "My Slide Show" title-slide-attributes: data-background-image: ./img/background.jpg data-background-size: cover data-background-opacity: "0.9" --- # Slide 1 # Slide 2 # Slide 3 ``` Le diaporama comportera quatre slides, la première sera la title-slide. ![Slide de titre](./images/title-slide.webp) ### Afficher un logo en haut à gauche sur la première slide, puis en bas à droite {#display-a-logo-top-left-on-the-first-slide-then-bottom-right} L'exemple ci-dessous illustre le besoin : sur votre première slide, vous voulez rendre votre logo bien plus visible que sur les autres slides. Pour y arriver, sur la première slide affichée, votre logo sera affiché en haut à gauche avec une certaine taille, alors qu'au changement de slide, le logo sera positionné en bas à droite et sera bien plus petit. Voici un exemple visuel : ![Logo en haut à gauche](./images/logo-top-left.webp) ![Logo en bas à droite](./images/logo-bottom-right.webp) Pour cela, nous allons devoir ajouter un JavaScript personnalisé et un CSS personnalisé. Créez un nouveau dossier appelé `assets` et, dans ce dossier, un fichier appelé `style.css` et un second appelé `custom.js`. **Project: assets** **style.css** ```css .reveal .slide-logo-bottom-right { bottom: 0 !important; left: unset !important; height: 100% !important; max-height: 2.2rem !important; right: 12px !important; } .slide-logo-max-size { bottom: unset !important; left: 12px; height: 100px !important; max-height: unset !important; right: unset !important; top: 5px; } .reveal .slide-logo { display: block; max-width: unset !important; position: fixed; width: 100x !important; } ``` **custom.js** ```javascript // The script below will ensure the logo is displayed top left // in his full size (probably on the first slide; title-slide) // then as soon as a different slide is displayed, the logo will // be displayed bottom right with a smaller size. function updateLogoSizePosition(event) { if (event.currentSlide.matches('#title-slide')) { var elements = document.querySelectorAll(".slide-logo"); [].forEach.call(elements, function(elem) { elem.classList.remove("slide-logo-bottom-right"); elem.classList.add("slide-logo-max-size"); }); } else { var elements = document.querySelectorAll(".slide-logo"); [].forEach.call(elements, function(elem) { elem.classList.add("slide-logo-bottom-right"); elem.classList.remove("slide-logo-max-size"); }); } }; window.addEventListener("load", (event) => { // Make sure the logo has his full size when the slideshow // is loaded (i.e. apply the slide-logo-max-size css class) var elements = document.querySelectorAll(".slide-logo"); [].forEach.call(elements, function(elem) { elem.classList.remove("slide-logo-bottom-right"); elem.classList.add("slide-logo-max-size"); }); Reveal.on("slidechanged", function(event) { updateLogoSizePosition(event); }); }); ``` Maintenant, utilisez-les dans votre `slides.md` comme ceci : **slides.md** ```markdown --- title: "My Slide Show" subtitle: "Playing with logo" author: "Christophe Avonture" date: last-modified lang: "en" format: revealjs: logo: images/my-logo.svg css: assets/style.css resources: images header-includes: | --- ``` Comme vous le voyez ci-dessus, le code JavaScript est injecté dans votre présentation via la directive `header-includes`. Pour la feuille de style, c'est la directive `css` qui est utilisée (*il n'y a malheureusement pas de directive `js:`*). *Pour exécuter cet exemple, lancez `quarto render slides.md --to revealjs`.* ### Ajouter une image de fond à une slide {#adding-a-background-image-to-a-slide} Un fond, un titre et un peu de style : **slides.md** ```markdown # Merry Christmas {background-image="images/winter.jpg" style="text-align: center; background-color: #cfb0f399; color: #210753;"} ``` *Pour exécuter cet exemple, lancez `quarto render slides.md --to revealjs`.* ![Ajouter une image de fond à une slide](./images/background-title-style.webp) #### Background-size contain ou cover {#background-size-contain-or-cover} Parfois, l'affichage de l'image n'est pas celui que vous souhaitez ; c'est le cas quand l'image est trop grande et/ou la slide trop étroite. L'illustration ci-dessous le montre : la photo du paresseux est beaucoup trop grande et vous ne pouvez pas la voir en entier. Cela peut être corrigé en utilisant l'attribut `background-size`. Quand il n'est pas mentionné (comme pour la première slide ci-dessous), la valeur par défaut est `cover` (voir pour plus d'informations). Vous pouvez demander au navigateur de redimensionner l'image pour qu'elle soit affichée en entier. C'est le comportement quand `background-size` vaut `contain`. **slides.md** ```markdown ## {background-image="images/2024.jpg"} ## {background-image="images/2024.jpg" background-size="contain"} ``` *Pour exécuter cet exemple, lancez `quarto render slides.md --to revealjs`.* La slide ci-dessous utilise `cover` et, non, ce n'est pas vraiment attrayant. ![Background-size réglé sur cover](./images/background-cover.webp) La seconde slide utilise `contain` et, au moins, on voit ce que le paresseux essayait de nous dire. ![Background-size réglé sur contain](./images/background-contain.webp) ### Slide-level {#slide-level} Le niveau de slide est un élément de configuration important ([documentation officielle](https://pandoc.org/MANUAL.html#structuring-the-slide-show)). Si nous regardons le markdown ci-dessous, combien de slides aurons-nous ? Deux ou trois ? La première pour le titre, la seconde pour `Technologies and tools`, mais `Apache` sera-t-il une slide séparée ou non ? **slides.md** ```markdown --- title: "Playing with slide-level" --- ## Technologies and tools ### Apache Our web applications are running under [Apache](https://httpd.apache.org/). We don't need to have Apache locally since we're using it as a Docker image. ``` En fait, cela dépend du réglage `slide-level`. Par défaut, `slide-level` vaut `2` et le résultat sera alors : ![Slide level 2](./images/slide-level-2.webp) Nous avons deux slides. Sur la seconde, on voit notre titre de niveau 3 `Apache` comme du texte dans `Technologies and tools`. Si vous voulez qu'une slide soit créée pour chaque titre de niveau 2 et de niveau 3 (ou plus), vous devez le définir avec slide-level. **slides.md** ```markdown --- title: "Playing with slide-level" # highlight-next-line format: # highlight-next-line revealjs: # highlight-next-line slide-level: 3 --- ## Technologies and tools ### Apache Our web applications are running under [Apache](https://httpd.apache.org/). We don't need to have Apache locally since we're using it as a Docker image. ``` Et le résultat donnera : ![Slide level 3](./images/slide-level-3.webp) ### Utiliser des emoji {#using-emoji} C'est simple, ajoutez juste l'entrée suivante dans votre front matter YAML. ```yaml --- from: markdown+emoji --- ``` Vous pouvez ensuite ajouter des emoji en tapant simplement le code comme `:wave:` ou `:raised_hands:`. Vous trouverez une liste complète sur . ### Texte en grand {#big-text} La classe `r-fit-text` ([doc officielle](https://quarto.org/docs/presentations/revealjs/advanced.html#fit-text)) donnera la taille maximale à votre contenu, c'est-à-dire : **slides.md** ```markdown # ::: {.r-fit-text} Big Text ::: ``` ![Texte en grand](./images/big-text.webp) ### Stretch {#stretch} La classe `r-stretch` ([doc officielle](https://quarto.org/docs/presentations/revealjs/advanced.html#stretch)) est ma préférée. Prenez l'exemple ci-dessous : **slides.md** ```markdown # Slide 1 Here is an image: ![Quarto slide with stretched image](image.webp){.r-stretch} Some text after the image. ``` Cela génèrera cette slide : ![Stretch 1](./images/stretch-1.webp) Ajouter beaucoup plus de texte / de paragraphes donnera cette nouvelle slide : ![Stretch 2](./images/stretch-2.webp) La hauteur de l'image sera redimensionnée automatiquement pour que le texte et les images tiennent sur la même slide. Si vous avez moins de texte, la hauteur de l'image sera plus grande, plus petite sinon. Sans devoir redimensionner l'image manuellement sur le disque. Très utile. ### Afficher une image à la pression d'une touche {#show-an-image-on-key-press} En utilisant `::: {.fragment .fade-up}` pour définir une zone de contenu, vous demandez à reveal.js de n'afficher son contenu qu'à la séquence de touches suivante. **slides.md** ```markdown # It's a candy dog ::: {style="font-size: 2em; color: #75AADB;"} Would you like to see a candy dog? ::: ::: {.fragment .fade-up} ![Candy dog reveal.js example](./images/dog.webp) ::: ``` *Pour exécuter cet exemple, lancez `quarto render slides.md --to revealjs`.* ![C'est un chien en bonbon](./images/candy-dog.gif) ### Créer des colonnes {#creating-columns} Vous pouvez utiliser `columns` pour diviser votre slide : **slides.md** ```markdown ## ::: columns ::: {.column width="70%"} ![Left column: slide example image](./images/image_1.webp) ::: ::: {.column width="30%"} ![Right column: top image](./images/image_2.webp) ![Right column: bottom image](./images/image_3.webp) ::: ::: ``` *Pour exécuter cet exemple, lancez `quarto render slides.md --to revealjs`.* ![Colonnes](./images/columns.webp) #### Quatre quadrants {#four-quadrants} L'exemple ci-dessous vient de , un diaporama reveal.js réalisé avec Quarto. L'idée est de découper les slides en quatre parties et d'afficher le contenu dans le sens horaire, en commençant par le cadran en haut à gauche. **slides.md** ```markdown ::: columns ::: {.column width="5%"} ::: ::: {.column width="40%"} ::: bulletbox ::: {.fragment .fade-in-then-semi-out} ![Top-left quadrant example](./images/top-left.webp){width="450px"} ::: ::: ::: ::: {.column width="5%"} ::: ::: {.column width="40%"} ::: bulletbox ::: {.fragment .fade-in-then-semi-out} ![Top-right quadrant example](./images/top-right.webp){width="450px"} ::: ::: ::: ::: {.column width="5%"} ::: ::: ::: columns ::: {.column width="5%"} ::: ::: {.column width="40%"} ::: bulletbox ::: {.fragment .fade-in-then-semi-out} ![Bottom-left quadrant example](./images/bottom-left.webp){width="450px"} ::: ::: ::: ::: {.column width="5%"} ::: ::: {.column width="40%"} ::: bulletbox ::: {.fragment .fade-in-then-semi-out} ![Bottom-right quadrant example](./images/bottom-right.webp){width="450px"} ::: ::: ::: ::: {.column width="5%"} ::: ::: ``` *Pour exécuter cet exemple, lancez `quarto render slides.md --to revealjs`.* ![Quatre quadrants](./images/four-quadrants.gif) ### Créer notre propre short code, facilement {#creating-our-own-short-code-easily} J'aime pouvoir écrire `==Important text==` et, pour une présentation revealjs, transformer automatiquement ce texte en `Important text` afin de pouvoir facilement lui appliquer un CSS personnalisé. D'abord, créez un `assets/custom.js` s'il n'existe pas encore et copiez/collez le code suivant : **assets/custom.js** ```javascript window.addEventListener("load", (event) => { // This function will search for short code like "==IpsoLorem==" // i.e. a portion of text between two equal sign and will replace it // to "IpsoLorem" so we can then use CSS to highlight // that portion. Reveal.on("slidechanged", function (event) { var re = /==([^=]*)==/; event.currentSlide.innerHTML = event.currentSlide.innerHTML.replace(new RegExp(re, "g"), "$1"); } ); }); ``` Dans votre front matter YAML, ajoutez ceci : **slides.md** ```markdown --- format: revealjs: header-includes: | --- ``` Et maintenant, quand le diaporama sera joué, un code JavaScript personnalisé remplacera à la volée chaque fragment `==xxxx==` par `xxx`. ## CSS {#css} ### Utiliser du css inline {#using-inline-css} Vous pouvez appliquer des styles à du texte inline en créant des spans : `[]` pour entourer le texte à styler et `{}` pour définir le style à appliquer. **slides.md** ```markdown # Inline style To draw attention to a specific part of the text, you might want to make it [red]{style="color: red;"} with a [yellow background]{style="background-color: yellow;"}; [like this]{style="color: red; background-color: yellow;"}. ``` *Pour exécuter cet exemple, lancez `quarto render slides.md --to revealjs`.* Le rendu est correct en HTML : ![html](./images/html.webp) ### Utiliser un css personnalisé {#use-a-custom-css} Utilisez le bloc d'en-tête YAML pour cela. **slides.md** ```markdown --- pagetitle: "Using my custom css" format: revealjs: theme: custom.css --- ## ![Mimikyu example image](./images/mimikyu.webp){style="width:400px;"} ``` *Pour exécuter cet exemple, lancez `quarto render slides.md --to revealjs`.* Maintenant, créez le fichier `custom.css` dans le même dossier que votre fichier markdown et, par exemple, mettez le fond de la slide en jaune : **custom.css** ```css /*-- scss:defaults --*/ .reveal { background-color: rgb(224, 226, 98); } ``` ![Css personnalisé](./images/custom-css.webp) ### Masquer la légende de l'image {#hide-image-caption} Par défaut, revealjs affiche la légende de l'image en dessous de celle-ci. Vous pouvez la masquer avec cette feuille de style : **custom.css** ```css .reveal p.caption { display: none; } ``` ### Les callouts peuvent être stylés {#callout-can-be-stylized} Un callout est une div spéciale comme ci-dessous : **slides.md** ```markdown ## Customize your terminal :::{.callout-note} ## Notice Skip this part if you do not want to customize your terminal right now ::: As you can see on the image below, we can customize the terminal: ``` Dans revealjs, personnellement, je trouve que le rendu prend trop de place par rapport au contenu de ma slide. J'utilise donc du css pour masquer le titre : ![Titre du callout](./images/callout-title.webp) **custom.css** ```css .reveal .callout-title { display: none; } ``` ## Navigation {#navigation} ### Slides verticales {#vertical-slides} Il existe trois types de navigation : `linear` (celle par défaut), `vertical` ou `grid` ([doc officielle](https://quarto.org/docs/presentations/revealjs/advanced.html#vertical-slides)). Si vous utilisez la navigation vertical ou grid, vous devriez structurer vos slides avec des titres de niveau 1 pour l'axe horizontal et des titres de niveau 2 pour l'axe vertical. Dans une navigation `linear` normale, revealjs affichera chaque slide, quelle que soit la touche pressée par l'utilisateur. Dans une navigation `linear`, en tant qu'auteur, vous êtes sûr que votre visiteur verra chacune d'elles. Dans une navigation `vertical` ou `grid`, c'est comme si vous montriez le titre du chapitre : si l'utilisateur presse la touche `down` ou `space`, il entrera dans le chapitre (et ce *verticalement*). Mais s'il presse la touche `right`, il le sautera et passera au chapitre suivant. Le `navigation-mode` dans l'en-tête yaml vous permet d'activer le mode `vertical`, voici un exemple : **slides.md** ```markdown --- title: "Presentation" format: revealjs: navigation-mode: vertical --- # Slide 1 ## Slide 1.1 # Slide 2 ## Slide 2.1 ## Slide 2.2 ## Slide 2.3 ``` ![Navigation verticale](./images/vertical.webp) > **Pressez la touche esc:** > > La touche `esc` vous permet de voir la structure de votre diaporama revealjs. ### Définir un nom pour votre slide {#define-a-name-for-your-slide} Par défaut, reveal.js génèrera un slug depuis le titre pour que vous puissiez référencer la slide dans une URL. Prenez l'exemple suivant : **slides.md** ```markdown ## Elit ad fugiat proident culpa sint Elit ad fugiat proident culpa sint qui id pariatur nostrud ullamco velit irure tempor culpa. Laborum occaecat ea tempor voluptate duis pariatur proident ea. Aute adipisicing ut ullamco exercitation nisi nulla sint. Reprehenderit tempor quis non cupidatat. ## Cillum do et commodo minim ullamco elit culpa Occaecat minim eu veniam laborumanim. Nostrud duis eiusmod ut amet velit commodo. Aliqua laborum cillum officia culpa quis duis enim cillum esse duis excepteur cillum do qui. Veniam dolore enim qui labore proident. Minim ipsum nisi Lorem cillum proident labore cupidatat aliqua pariatur. Nisi id magna incididunt et culpa. ``` *Pour exécuter cet exemple, lancez `quarto render slides.md --to revealjs`.* L'URL générée pour la première slide sera `http://[...]index.html#/elit-ad-fugiat-proident-culpa-sint` et, pour la seconde slide, `http://[...]index.html#/cillum-do-et-commodo-minim-ullamco-elit-culpa`. Pour définir le nom vous-même, ajoutez juste `{#}` suivi du slug que vous souhaitez. **slides.md** ```markdown ## Elit ad fugiat proident culpa sint {#intro} Elit ad fugiat proident culpa sint qui id pariatur nostrud ullamco velit irure tempor culpa. Laborum occaecat ea tempor voluptate duis pariatur proident ea. Aute adipisicing ut ullamco exercitation nisi nulla sint. Reprehenderit tempor quis non cupidatat. ## Cillum do et commodo minim ullamco elit culpa {#chapter1} Occaecat minim eu veniam laborumanim. Nostrud duis eiusmod ut amet velit commodo. Aliqua laborum cillum officia culpa quis duis enim cillum esse duis excepteur cillum do qui. Veniam dolore enim qui labore proident. Minim ipsum nisi Lorem cillum proident labore cupidatat aliqua pariatur. Nisi id magna incididunt et culpa. ``` ![Définir un nom pour votre slide](./images/set-slide-slug.webp) Mais attention au menu des slides (en bas à gauche) : il est peut-être bon d'y définir aussi un nom adapté. ![Définir aussi le nom dans le menu](./images/set-name-in-menu.webp) Pour cela, définissez l'attribut `data-menu-title` ; p.ex. : **slides.md** ```markdown ## Elit ad fugiat proident culpa sint {#intro data-menu-title="Introduction"} ## Cillum do et commodo minim ullamco elit culpa {#chapter1 data-menu-title="Chapter 1"} ``` ### Afficher le numéro de slide {#show-slide-number} Pour n'afficher que le numéro de la slide courante, vous pouvez utiliser `slide-number: true` mais, si vous voulez aussi le nombre total de slides, vous devriez utiliser `slide-number: c/t` : **slides.md** ```markdown format: revealjs: slide-number: c/t ``` ![Slide / Total des slides](./images/slides_c_t.webp) Vous trouverez plus d'informations dans la [documentation Quarto sur les numéros de slides](https://quarto.org/docs/presentations/revealjs/presenting.html#slide-numbers). ## Divers {#misc} ### Utiliser les notes du présentateur {#using-speaker-notes} Vous pouvez écrire dans votre présentation des messages qui ne seront pas affichés lors de la lecture du diaporama, mais seulement sur un second écran quand le présentateur le souhaite. L'exemple ci-dessous l'illustre. La façon d'insérer de telles notes de présentateur est d'utiliser le bloc `::: notes`. **slides.md** ````markdown ## ::: columns ::: {.column width="70%"} ![Left column: speaker notes example image](./images/image_1.webp) ::: ::: {.column width="30%"} ![Right column: speaker notes top image](./images/image_2.webp) ![Right column: speaker notes bottom image](./images/image_3.webp) ::: ::: ::: notes On the left side, you have ... and, on the right side, the first image is .... The second image has been ... ::: ## ![Example slide image 4](./images/image_4.webp) ``` ```` *Pour exécuter cet exemple, lancez `quarto render slides.md --to revealjs`.* Donc, dans l'exemple ci-dessus, nous aurons deux slides. En pressant la touche `s` du clavier (s pour speakers), une nouvelle fenêtre s'affichera. Quand vous avez deux écrans, sur le premier vous affichez votre présentation sans les notes (écran de gauche ci-dessous) et sur le second vous affichez les notes (écran de droite ci-dessous). Si vous partagez votre écran via un outil comme Teams ou Zoom, même chose : vous partagez l'écran où la présentation est affichée (le premier) et gardez les notes pour vous seul. ![Notes du présentateur](./images/speaker-notes.webp) Comme vous le voyez sur l'image ci-dessus, les notes du présentateur affichent des informations utiles comme le temps écoulé depuis le début de la présentation, l'heure actuelle et la slide suivante pour faciliter votre transition. --- # Les blocs Callout de Quarto > Découvrez la syntaxe des blocs Callout de Quarto pour créer des encadrés qui attirent l'attention (Tips, Warnings, Cautions) dans vos rapports. Inclut un snippet VSCode bien pratique. --- ![Les blocs Callout de Quarto](/img/v2/quarto.webp) **TL;DR** Cet article présente la syntaxe des blocs callout de Quarto (`:::{.callout-caution}`, `:::{.callout-tip}`, etc.) pour créer des encadrés mis en évidence dans les sorties PDF, HTML et revealJS — l'équivalent Quarto de l'`` de Docusaurus — et fournit un snippet VSCode qui vous demande de façon interactive le type de callout, le titre et le contenu, pour ne plus jamais avoir à retenir la syntaxe exacte. Un callout (appelé *admonition* par [Docusaurus](https://docusaurus.io/docs/markdown-features/admonitions)) est une syntaxe spéciale utilisée pour mettre un paragraphe en évidence, par exemple un encadré *Faites attention à...* ou *Astuce : saviez-vous que...*. *Du côté de Docusaurus, j'ai abordé cette syntaxe dans [Some tips and tricks when written articles for Docusaurus](/blog/docusaurus-articles-tips).* Sur ce blog propulsé par Docusaurus, la syntaxe d'une *admonition* est ```markdown Never give your bank card code to a stranger. ``` et le rendu est le suivant > **Faites attention à...:** > > Ne donnez jamais le code de votre carte bancaire à un inconnu. Et maintenant une petite astuce : > **Saviez-vous que...:** > > En vous couchant plus tôt, vous aurez un sommeil de meilleure qualité. Quarto implémente cela un peu différemment, voyons donc comment... > **Image Docker avec Quarto:** > > Si vous n'avez pas encore d'image Docker avec Quarto, lisez cet article : [Running Quarto Markdown in Docker](/blog/docker-quarto). ## Syntaxe {#syntax} Malheureusement, cette syntaxe ne fait pas partie du langage Markdown standard : chaque outil a donc la sienne. Pour Quarto, la syntaxe est : ```markdown :::{.callout-caution} ## Pay attention to... Never give your bank card code to a stranger. ::: ``` Et ce paragraphe est converti comme ceci en PDF : ![Callout Caution en PDF](./images/caution-pdf.webp) La syntaxe Quarto pour une astuce est : ```markdown :::{.callout-tip} ## Did you know that... By going to bed earlier, you'll get better quality sleep. ::: ``` ![Callout Tip en PDF](./images/tip-pdf.webp) Les callouts sont supportés en PDF, HTML, revealJS et d'autres formats. Retrouvez la syntaxe en détail dans la [documentation officielle](https://quarto.org/docs/authoring/callouts.html). ## Snippet VSCode {#vscode-snippet} Si vous utilisez VSCode, ne faites pas l'effort de retenir la syntaxe exacte : utilisez la fonctionnalité *Snippet* de VSCode. Appuyez sur `CTRL`-`SHIFT`-`P` pour accéder à la **Command Palette**. Commencez à taper `Configure User Snippets`, validez et sélectionnez `markdown.json` puisque nos snippets ne doivent être disponibles que lors de l'écriture de contenu Markdown. ![Création d'un snippet](./images/create-snippets.webp) S'il s'agit de votre premier snippet, le fichier JSON sera vide. Copiez/collez le texte ci-dessous. Sinon, copiez simplement le nœud `callout` et collez-le au bon endroit dans votre fichier. **markdown.json** ```json { "callout": { "prefix": "quarto-callout", "body": [ ":::{.callout-${1|note,tip,warning,caution,important|}}", "## ${2:title}", "${3:body}", ":::" ], "description": "Add a callout paragraph to your markdown file" } } ``` Enregistrez et fermez le fichier `markdown.json`, puis retournez dans n'importe quel fichier markdown (ou créez-en un nouveau). Maintenant, en tapant `quarto` puis en appuyant sur `CTRL`-`space`, VSCode affichera la liste des possibilités pour ce mot et vous y retrouverez votre snippet. Sélectionnez-le et appuyez sur `Enter` pour valider. ![Appel du snippet](./images/call-snippets.webp) Et, comme vous pouvez le voir sur l'image ci-dessous, VSCode va vous demander trois choses : 1. D'abord le type de callout, avec une liste de valeurs possibles ; facile, non ? 2. Ensuite, après avoir appuyé sur la touche `tab`, il vous sera demandé de saisir un titre et, enfin, 3. Le contenu de votre paragraphe. ![Utilisation du snippet](./images/using-snippet.webp) VSCode vous montre le snippet de façon interactive et vous pouvez ainsi le créer facilement, sans avoir à retenir la syntaxe exacte. ```markdown :::{.callout-caution} ## Pay attention to... Never give your bank card code to a stranger. ::: ``` --- # Le short code includes de Quarto > Découvrez comment utiliser le short code includes de Quarto pour découper de longs documents en plusieurs fichiers organisés et simplifier votre écriture. --- ![Le short code includes de Quarto](/img/v2/quarto.webp) **TL;DR** Cet article présente le short code `{{< include >}}` de Quarto, qui permet de découper un long document en plusieurs petits fichiers Markdown et de les fusionner au rendu en référençant leurs chemins relatifs depuis un fichier `.qmd` principal. Avec le short code `includes` de Quarto, l'écriture d'un long document peut facilement être découpée en plusieurs documents plus petits. *`includes` découpe un document en morceaux fixes. Quand la partie répétée est la même *structure* remplie avec des valeurs différentes, [Using Mustache templating with Quarto](/blog/quarto-mustache) et [Using variables from external file in your Quarto project](/blog/quarto-project-variables) sont les bons outils.* Imaginez le fichier `main.qmd` suivant : **main.qmd** ```qmd # My thesis in Latin {{< include chapter1.md >}} {{< include chapter2.md >}} ``` Quarto va alors fusionner les trois fichiers quand vous faites le rendu de `main.qmd`. > **Image Docker avec Quarto:** > > Si vous n'avez pas encore d'image Docker avec Quarto, lisez cet article [Running Quarto Markdown in Docker](/blog/docker-quarto). ## Le tag includes {#includes-tag} Le tag est simple, il suffit d'indiquer le chemin relatif du fichier à inclure. Pour la syntaxe détaillée, voyez la [documentation officielle](https://quarto.org/docs/authoring/includes.html). ## Résultat {#result} Lancer `quarto render main.qmd --to html` sur le fichier `main.qmd` montré ci-dessus fusionne les fichiers inclus en un seul document : ![Quarto includes](./images/includes.webp) Voici ce que contient réellement chaque fichier inclus. Voici un contenu bidon pour `chapter1.md` : **chapter1.md** ```markdown ## Chapter 1 Voluptatem minus labore architecto sed voluptas molestiae perferendis. Sed voluptatem ut amet at blanditiis sunt et exercitationem quidem. Vel id impedit dicta omnis repudiandae iure. Expedita magni facere. Ullam non non sint qui provident. Ea beatae voluptatem pariatur. Et beatae error ea aut neque omnis pariatur rerum ut. Ad aut nobis magni aut est dicta adipisci est. Quo reiciendis eum aut rem. Dolores sit magni non. ``` Et pour `chapter2.md` : **chapter2.md** ```markdown ## Chapter 2 Iure repudiandae perferendis maiores dolorem consequuntur exercitationem suscipit. Animi voluptatem est quia. Quia id optio. Architecto ut ipsa voluptas minima voluptate accusamus architecto. Consequatur debitis et sunt eos quod qui unde aut. ``` Maintenant, en lançant par exemple `quarto render main.qmd --to html`, Quarto va fusionner ces deux fichiers (et `main.qmd`) dans la page rendue montrée ci-dessus. > **Qu'est-ce qu'un fichier `.qmd` ?:** > > Pour Quarto, l'extension n'a pas beaucoup d'importance : que ce soit `.qmd` ou `.md`, le traitement est le même. > > Par contre, il est sans doute plus intéressant pour nous d'utiliser l'extension `.qmd` quand on emploie une syntaxe propre à Quarto, comme c'est le cas ici. --- # Style inline avec Quarto > Appliquez rapidement des styles CSS inline dans Quarto pour vos documents HTML avec la syntaxe []{}. Mettez un texte en évidence instantanément, sans écrire une classe CSS complète. --- ![Style inline avec Quarto](/img/v2/quarto.webp) **TL;DR** Cet article présente la syntaxe de span CSS inline de Quarto `[text]{style="..."}`, pratique pour styler rapidement un bout de texte (couleur, fond, etc.) sans définir de classe CSS — sachant qu'elle ne fonctionne qu'en sortie HTML et qu'elle est ignorée silencieusement en Word ou en PDF. Pas envie de perdre du temps à créer une classe CSS et à comprendre comment l'intégrer dans Quarto juste pour mettre en évidence une partie de votre paragraphe ? Quarto supporte une syntaxe de *style CSS inline* pour produire des **documents HTML** (cela ne fonctionne pas pour, par exemple, le pdf ou le docx). *Si c'est le contenu lui-même, et pas seulement son style, qui doit varier d'un format à l'autre, voyez [Quarto conditional display](/blog/quarto-conditional-display).* > **Syntaxe:** > > Vous pouvez appliquer des styles à du texte inline en créant des spans : `[]` entoure le texte à styler et `{}` définit le style à appliquer. Donc, pour afficher un texte comme *rouge* en rouge, la syntaxe Quarto est `[red]{style="color: red;"}`. Le texte entre crochets recevra le style CSS inline spécifié. ```markdown # Inline style To draw attention to a specific part of the text, you might want to make it [red]{style="color: red;"} with a [yellow background]{style="background-color: yellow;"}; [like this]{style="color: red; background-color: yellow;"}. ``` Le rendu est correct en HTML : ![html](./images/html.webp) > [source](https://mine-cetinkaya-rundel.github.io/quarto-tip-a-day/posts/11-spans/). > **Image Docker avec Quarto:** > > Si vous n'avez pas encore d'image Docker avec Quarto, lisez cet article [Running Quarto Markdown in Docker](/blog/docker-quarto). Mais c'est tout simplement ignoré en Word ou en PDF : ![docx](./images/docx.webp) ![pdf](./images/pdf.webp) --- # Mes extensions Quarto préférées > Mes extensions Quarto préférées - Découvrez les filtres Quarto essentiels pour le templating de contenu partiel, les icônes Font Awesome, l'inclusion de fichiers de code externes et de simples macros de recherche-remplacement pour améliorer votre workflow de documentation. --- ![Mes extensions Quarto préférées](/img/v2/quarto.webp) **TL;DR** Cet article partage une liste choisie des extensions Quarto préférées de l'auteur pour améliorer votre workflow de documentation. Découvrez des filtres utiles pour le templating de contenu avec `quarto-partials`, l'intégration d'icônes avec `fontawesome`, l'inclusion de snippets de code externes, la création de simples macros de remplacement de texte avec `search-replace` et l'amélioration de la lisibilité des blocs de code dans les présentations reveal.js avec `code-fullscreen`. J'utilise Quarto depuis 18 mois pour générer ma documentation : j'écris des fichiers Markdown (`.md`) et je les convertis en document Word, en PDF, en site HTML statique ou en [diaporama revealjs](/blog/quarto-revealjs-tips). *Si Quarto est nouveau pour vous, [Running Quarto Markdown in Docker](/blog/docker-quarto) vous donne une installation fonctionnelle en quelques minutes.* Quarto prend en charge des extensions externes (aussi appelées `plugins` ailleurs). Voici une liste de quelques-unes que j'utilise. ## gadenbuie/quarto-partials : du contenu partiel réutilisable {#gadenbuiequarto-partials} Voir Quand j'en suis arrivé à cette extension, mon besoin était le suivant : j'avais codé plus de 60 fonctionnalités et je devais les documenter. Chacune aura le même look\&feel, avec un chapitre *Description*, un *Comment l'exécuter ?*, *Comment la configurer ?*, *Remarques* et ainsi de suite. Parfois le texte sera exactement le même (comme un texte générique) et, dans certains cas, presque le même : le chapitre *Comment la configurer ?* aura les mêmes phrases, sauf que je dois remplacer quelques placeholders (comme le nom de la fonctionnalité, le nom du fichier de configuration, ...) Autrement dit : je dois pouvoir écrire une sorte de *template de page* et y *injecter* du contenu spécifique. Je veux que mes 60 pages de fonctionnalités se ressemblent et, si je dois changer un texte global, le faire à un seul endroit central. [Partial content](https://github.com/gadenbuie/quarto-partials/tree/main) est parfait ici. D'après leur documentation, c'est similaire à [Mustache](https://mustache.github.io/). ### Exemple {#example} Créons un fichier appelé `_hello.md` avec ce contenu : **\_hello.md** ```markdown Hello, {{ name }}! ``` Ensuite, dans n'importe quel fichier, nous pouvons faire : **test.md** ```markdown {{< partial _hello.md name="John" >}} ``` Pensez à ajouter les lignes ci-dessous dans votre front matter YAML : **test.md** ```markdown filters: - quarto-partials ``` Nous pouvons passer des variables comme ci-dessus mais, bien plus puissant, nous pouvons déclarer une clé `partial-data` dans notre frontmatter YAML. Si vous voulez aller beaucoup plus loin avec cette extension (blocs conditionnels, contenu brut, un cas d'usage réel de documentation), j'ai écrit un article dédié : [Using Mustache templating with Quarto](/blog/quarto-mustache). ## quarto-ext/fontawesome : des icônes Font Awesome {#quarto-extfontawesome} Voir Utilisez les icônes Font Awesome dans vos documents HTML et PDF. Par exemple, si vous voulez une icône GitHub, il suffit de mettre le code `{{< fa brands github size=5x >}}` dans votre fichier. Plutôt facile. Pensez à ajouter les lignes ci-dessous dans votre front matter YAML : **test.md** ```markdown filters: - fontawesome ``` ## quarto-ext/include-code-files : inclure des fichiers externes {#quarto-extinclude-code-files} > **Est-ce encore nécessaire ?:** > > Je ne suis pas sûr que cet addon soit encore nécessaire : dans une de mes documentations, je l'ai utilisé comme ceci : > > ````markdown > ```{.yaml include="files/.gitlab-ci.yml"} > ``` > ```` > > Ça fonctionnait comme prévu mais, lors de la génération d'un fichier `.docx` (MS Word), le fichier inclus était absent. > > J'ai alors utilisé le shortcode natif comme ci-dessous et, maintenant, ça marche pour `.html` comme pour `.docx`. > > ````markdown > ```yaml > {{< include files/.gitlab-ci.yml >}} > ``` > ```` Voir L'objectif est de pouvoir inclure un fichier externe en utilisant la syntaxe ci-dessous : ````markdown ```{.bash include="documentation/chapter_1/install.sh"} ``` ```` Pensez à ajouter les lignes ci-dessous dans votre front matter YAML : **test.md** ```markdown filters: - include-code-files ``` ## quarto-ext/search-replace : des macros de remplacement {#quarto-extsearch-replace} Voir Une extension de filtre Quarto pour de simples macros de recherche-remplacement. Cette extension vous permet de rechercher et remplacer lors du rendu des documents. Par exemple, en mettant le code `+quarto` dans le frontmatter de l'article ou, mieux, dans le fichier global `_quarto.yaml`, nous pouvons simplement écrire `+quarto` (une constante) dans notre document et laisser le remplacement se faire pendant le rendu de la documentation. **\_quarto.yaml** ```yaml search-replace: +quarto: "[Quarto](https://quarto.org)" ``` Pensez à ajouter les lignes ci-dessous dans votre front matter YAML : ```yaml filters: - search-replace ``` ### Exemple {#example-1} Voici comment l'utiliser. C'est simple ; écrivez juste le nom de votre constante et, pendant le rendu de votre document, la constante sera remplacée par la valeur prédéfinie. Facile ! ```markdown I'm using +quarto and I love it ``` ## shafayetShafee/code-fullscreen : du code en plein écran {#shafayetshafeecode-fullscreen} Voir Cette extension concerne les diaporamas revealjs. Si vous avez un très long bloc de code, revealjs n'affichera pas toutes les lignes et le code sera donc tronqué (seules les 25 premières lignes seront affichées, par exemple). En ajoutant le filtre ci-dessous à votre frontmatter YAML, un petit bouton *Full screen* sera affiché en haut à droite du bloc de code. **test.md** ```markdown filters: - code-fullscreen ``` --- # 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 filters: - 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 partial-data: 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" --- ## 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}} This job only for **{{ type }}** project. {{/type}} {{^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 partial-data: command: "lint-php" config_file: ".phplint.yml" config_url: "https://github.com/tengattack/phplint/blob/master/.phplint.yml" name: "PHP Linter" type: "PHP" --- ## 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}} {{ name }} uses a settings file named .config/{{ config_file }} ([learn more]({{ config_url }})). {{/config_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/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. --- # Rendez votre projet Quarto prêt pour les Devcontainers — fini les galères de configuration > Convertissez n'importe quel projet Quarto en devcontainer totalement portable et piloté par VSCode, en quelques minutes. --- ![Rendez votre projet Quarto prêt pour les Devcontainers — fini les galères de configuration](/img/v2/devcontainer_quarto.webp) **TL;DR** Cet article explique comment convertir n'importe quel projet Quarto en devcontainer portable piloté par VSCode, pour éliminer les galères de configuration. En ajoutant trois fichiers de configuration précis (`compose.yaml`, `Dockerfile`, `devcontainer.json`), vous obtenez un environnement de développement complet. Cette configuration inclut Quarto préinstallé, des extensions VSCode configurées, ainsi que des fonctionnalités comme la prévisualisation automatique et des pre-commit hooks optionnels, ce qui simplifie le workflow de documentation. J'adore utiliser Quarto et j'ai des tonnes de projets de *documentation* sur mon disque. J'écris de la documentation utilisateur, de la documentation développeur et aussi des guides d'installation pour quasiment chaque projet que je code. Si vous n'avez jamais lancé Quarto via Docker, [Running Quarto Markdown in Docker](/blog/docker-quarto) est un bon point de départ ; cet article part de cette idée pour vous offrir un devcontainer VSCode complet. L'idée m'est vite venue de créer une image Docker pour pouvoir développer ma documentation tranquillement, ainsi qu'une configuration devcontainer pour VSCode. Maintenant, quand je veux écrire ma documentation, en moins d'une minute, je peux créer un environnement entièrement dédié à la rédaction de documentation. Entre autres, j'installe Quarto, j'installe et configure plusieurs extensions pour VSCode, et je lance Quarto en mode preview : dès que j'enregistre une modification, Quarto régénère la documentation tout seul. Voyons comment faire ; vous allez voir, c'est vraiment simple. *Le même devcontainer, spécialisé pour les diaporamas, est utilisé dans [Level Up Your Presentations with Quarto, reveal.js, Decktape, Docker and DevContainers](/blog/running-revealjs-with-docker) ; et poussé à l'échelle industrielle dans [Quarto - How I Built a Self-Documenting Ecosystem for 50+ Projects](/blog/quarto-industrialisation).* **Quick Jump:** [Commençons par un projet Quarto simple](#lets-get-a-simple-quarto-project-first) | [Nous allons devoir créer trois fichiers](#well-need-to-create-three-files) ## Commençons par un projet Quarto simple {#lets-get-a-simple-quarto-project-first} Bien sûr, si vous en avez déjà un, allez directement dans votre projet. Ici, pour ce billet, nous allons simplement réutiliser un projet existant. En lançant les commandes ci-dessous dans une console, vous téléchargerez uniquement le dossier `brand` du projet `quarto-examples` : ```bash $ cd /tmp git clone --filter=blob:none --sparse https://github.com/quarto-dev/quarto-examples cd quarto-examples git sparse-checkout set brand cd brand/brand-simple ``` Lancez `code .` pour ouvrir le projet dans VSCode et vous obtiendrez ceci : ![Ouverture du projet brand-simple dans VSCode](./images/opening-in-vscode.webp) Voici où nous allons : ouvrir ce même projet comme devcontainer, lancer `quarto preview .`, et le site tourne déjà — sans aucune installation de Quarto sur votre host. ![Lancement de quarto preview](./images/running_in_preview_mode.webp) ![Le site tourne déjà](./images/preview.webp) Ridiculement simple, non ? Construisons ça, en commençant par trois fichiers. ## Nous allons devoir créer trois fichiers {#well-need-to-create-three-files} Pour obtenir les meilleures performances en termes de vitesse de build de l'image Docker et d'utilisation du mécanisme de cache de Docker, nous allons devoir créer trois fichiers : Le fichier `.devcontainer/compose.yaml` est nécessaire pour dire à VSCode de créer notre image Docker si elle n'existe pas encore, ou de la réutiliser si elle existe. Ce fichier est donc essentiel côté performances. Le fichier `.devcontainer/Dockerfile` définit notre image Docker : les binaires dont nous avons besoin, la configuration utilisateur, etc. Le fichier `.devcontainer/devcontainer.json` est utilisé par VSCode pour comprendre et construire notre environnement de travail. **Project: /tmp/quarto-examples/brand/brand-simple** **.devcontainer/compose.yaml** ```yaml --- # cspell:ignore tinytex services: quarto-doc-base: image: quarto-devcontainer:latest build: context: . dockerfile: Dockerfile args: # We use default values here. The actual UID/GID mapping is handled # by the VS Code Dev Container CLI at runtime, overriding these defaults # when the container starts via the user: vscode directive in the Dockerfile. USER_UID: 1000 USER_GID: 1000 # Flags you'll need to adjust depending on your project INSTALL_CHROMIUM: "true" INSTALL_CSPELL: "false" INSTALL_GRAPHVIZ: "true" INSTALL_PRECOMMIT_HOOKS: "false" user: vscode working_dir: /workspace volumes: - ..:/workspace:cached - ./history/.bash_history:/home/vscode/.bash_history:rw - type: bind source: ${HOME}/.quarto target: /home/vscode/.quarto - type: bind source: ${HOME}/.ssh/id_ed25519 target: /home/vscode/.ssh/id_ed25519 read_only: true # Base command to keep the container running indefinitely command: /bin/sh -c "while sleep 1000; do :; done" ``` **.devcontainer/Dockerfile** ```docker # syntax=docker/dockerfile:1.4 # cspell:disable FROM ghcr.io/quarto-dev/quarto:1.8.25 AS base ARG USERNAME=vscode ARG USER_UID=1000 ARG USER_GID=1000 ARG INSTALL_CHROMIUM=false ARG INSTALL_CSPELL=false ARG INSTALL_GRAPHVIZ=false ARG INSTALL_PRECOMMIT_HOOKS=false # Yaml tooling ARG YQ_VERSION=v4.42.1 # ---------- Stage 1: Install system dependencies ---------- # # openssh-client : used by git when pushing/pulling using git@ protocol # sudo : to easily install additional dependencies if needed # texlive-* : to render as PDF using "quarto render . --to pdf" # yamllint : to validate YAML frontmatter in .qmd files RUN --mount=type=cache,target=/var/cache/apt \ --mount=type=cache,target=/var/lib/apt \ set -eux; \ apt-get update && \ apt-get install -y --no-install-recommends \ ca-certificates \ curl \ git \ libnss3 libfontconfig1 libgbm1 \ libsecret-1-dev \ libx11-dev \ libxkbfile-dev \ openssh-client \ texlive-xetex texlive-fonts-recommended texlive-plain-generic \ unzip \ sudo \ yamllint \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # Allow our user to run sudo without password RUN echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers # Install yq (YAML validator) for validating YAML frontmatter RUN --mount=type=cache,target=/var/cache/yq \ set -eux; \ curl -L --fail --silent --show-error \ https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64 \ -o /var/cache/yq/yq_linux_amd64; \ install -m 0755 /var/cache/yq/yq_linux_amd64 /usr/local/bin/yq # ---------- Stage 2: Extras - pre-commit, Node.js + cspell ---------- FROM base AS extras ARG INSTALL_CSPELL ARG INSTALL_GRAPHVIZ ARG INSTALL_PRECOMMIT_HOOKS ARG USERNAME # Install pre-commit (without installing hooks at build time) RUN --mount=type=cache,target=/root/.cache/pip \ if [ "$INSTALL_PRECOMMIT_HOOKS" = "true" ]; then \ set -eux; \ echo "Installing Python and pre-commit..." && \ apt-get update && \ apt-get install -y --no-install-recommends python3-pip && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ pip3 install --no-cache-dir --upgrade pip && \ pip3 install --no-cache-dir pre-commit && \ pip cache purge || true; \ fi # Install Node.js + cspell (optional) RUN --mount=type=cache,target=/home/${USERNAME}/.npm \ if [ "${INSTALL_CSPELL}" = "true" ]; then \ set -eux; \ curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ apt-get install -y --no-install-recommends nodejs && \ npm config set cache /home/${USERNAME}/.npm && \ npm install -g cspell && \ npm cache clean --force && \ apt-get clean && \ rm -rf /var/lib/apt/lists/*; \ fi # Install Graphviz for Quarto (dot diagrams support) RUN --mount=type=cache,target=/var/cache/apt \ --mount=type=cache,target=/var/lib/apt \ if [ "${INSTALL_GRAPHVIZ}" = "true" ]; then \ set -eux; \ apt-get update \ && apt-get install -y --no-install-recommends \ libcairo2 \ libpango-1.0-0 \ graphviz \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*; \ fi # ---------- Stage 3: Finalization ---------- FROM extras AS final ARG USERNAME ARG USER_UID ARG USER_GID ARG INSTALL_CHROMIUM # ---------- User and permissions -------------------------------------------- RUN set -eux && \ USER_UID=${USER_UID:-1000} && \ USER_GID=${USER_GID:-1000} && \ groupadd -g "${USER_GID}" "${USERNAME}" && \ useradd -m -u "${USER_UID}" -g "${USER_GID}" -s /bin/bash "${USERNAME}" && \ chown -R "${USERNAME}":"${USERNAME}" "/home/${USERNAME}" # Create .ssh directory and set permissions for the non-root user RUN set -eux && \ mkdir -p /home/${USERNAME}/.ssh && \ chown -R "${USERNAME}":"${USERNAME}" "/home/${USERNAME}/.ssh" && \ chmod 700 /home/${USERNAME}/.ssh # Switch to non-root user for Chromium and Quarto tools USER ${USERNAME} # Install Quarto Chromium tool (optional) RUN --mount=type=cache,target=/home/${USERNAME}/.quarto/tools \ if [ "${INSTALL_CHROMIUM}" = "true" ]; then \ set -eux; \ echo "Installing Quarto's Chromium tool..." && \ quarto install tool chromium; \ fi # ---------- Shell and history setup ----------------------------------------- # Configure bash to use this file RUN echo "export HISTFILE=/home/${USERNAME}/.bash_history" >> "/home/${USERNAME}/.bashrc" && \ echo 'export HISTSIZE=10000' >> "/home/${USERNAME}/.bashrc" && \ echo 'export HISTFILESIZE=10000' >> "/home/${USERNAME}/.bashrc" && \ echo 'shopt -s histappend' >> "/home/${USERNAME}/.bashrc" && \ echo "alias render_graphviz='find . -name \"flow.dot\" -exec sh -c '\''for f; do dir=\$(dirname \"\$f\"); dot -Tpng \"\$f\" -o \"\$dir/flow.png\"; done'\'' sh {} +'" >> "/home/${USERNAME}/.bashrc" # Set up the shell prompt RUN set -eux && \ echo "PS1='\n\e[0;33m🐳 \e[0;36m\$(whoami)\e[0m \w # '" >> "/home/${USERNAME}/.bashrc" # Welcome message and tips RUN set -eux && \ cat <> "/home/${USERNAME}/.bashrc" echo -e "\n📘 Welcome to the Quarto Docs Dev Container!" echo -e "" echo -e "Below are some useful commands:" echo -e "" echo -e " \e[1mQuarto Commands\e[0m" echo -e " ───────────────" echo -e " ✨ \e[32mquarto preview .\e[0m Start a hot-reloading preview in your browser." echo -e " ✨ \e[32mquarto render .\e[0m Build the documentation (based on _quarto.yml)." echo -e " ✨ \e[32mquarto render . --profile docx --to docx\e[0m Build a .docx file (requires _quarto-docx.yml)." EOF # Append conditional commands depending on optional tools RUN set -eux && \ if [ "${INSTALL_CSPELL}" = "true" ] || [ "${INSTALL_PRECOMMIT_HOOKS}" = "true" ]; then \ echo -e 'echo -e ""' >> "/home/${USERNAME}/.bashrc"; \ echo -e 'echo -e " \e[1mData quality checks\e[0m"' >> "/home/${USERNAME}/.bashrc"; \ echo -e 'echo -e " ───────────────────"' >> "/home/${USERNAME}/.bashrc"; \ fi && \ if [ "${INSTALL_CSPELL}" = "true" ]; then \ echo 'echo -e " ✍️ \e[32mcspell lint . --config .vscode/cspell.json\e[0m Run spell checks.\n"' >> "/home/${USERNAME}/.bashrc"; \ fi && \ if [ "${INSTALL_PRECOMMIT_HOOKS}" = "true" ]; then \ echo 'echo -e " ⚙️ \e[32mpre-commit run --all-files\e[0m Run code quality checks on all files.\n"' >> "/home/${USERNAME}/.bashrc"; \ fi # Tools RUN set -eux && \ if [ "${INSTALL_GRAPHVIZ}" = "true" ]; then \ echo 'echo -e ""' >> "/home/${USERNAME}/.bashrc"; \ echo 'echo -e " \e[1mTools\e[0m"' >> "/home/${USERNAME}/.bashrc"; \ echo 'echo -e " ─────"' >> "/home/${USERNAME}/.bashrc"; \ echo 'echo -e " 📊 \e[32mrender_graphviz\e[0m Scan your documentation, process .dot files and generate .png ones.\n"' >> "/home/${USERNAME}/.bashrc"; \ fi ``` **.devcontainer/devcontainer.json** ```json { "name": "Quarto Docs Project", "dockerComposeFile": "compose.yaml", "service": "quarto-doc-base", "workspaceFolder": "/workspace", "remoteUser": "vscode", "containerEnv": { "GIT_SSH_COMMAND": "ssh -i /home/vscode/.ssh/id_ed25519 -o 'UserKnownHostsFile=/dev/null'" }, "initializeCommand": "mkdir -p .devcontainer/history && touch .devcontainer/history/.bash_history && echo '.bash_history' > .devcontainer/history/.gitignore", "postAttachCommand": "if which pre-commit >/dev/null 2>&1; then pre-commit install || true; fi", "forwardPorts": [ 8000 ], "customizations": { "vscode": { "settings": { "[dockerfile]": { "files.eol": "\n", "editor.defaultFormatter": "ms-azuretools.vscode-docker" }, "[json]": { "editor.defaultFormatter": "vscode.json-language-features" }, "[jsonc]": { "editor.defaultFormatter": "vscode.json-language-features", "editor.wordWrap": "wordWrapColumn", "editor.wordWrapColumn": 80, "editor.wrappingIndent": "indent" }, "[markdown]": { "editor.defaultFormatter": "yzhang.markdown-all-in-one", "editor.wordWrap": "wordWrapColumn", "editor.wordWrapColumn": 80, "editor.wrappingIndent": "indent" }, "[yaml]": { "editor.defaultFormatter": "redhat.vscode-yaml" }, "cSpell.language": "en,fr,nl", "cSpell.words": [ "Quarto", "devcontainer", "frontmatter", "Pandoc", "Gitlens" ], "editor.formatOnSave": true, "editor.guides.bracketPairs": "active", "editor.guides.bracketPairsHorizontal": "active", "editor.guides.highlightActiveIndentation": true, "editor.guides.indentation": true, "editor.multiCursorModifier": "ctrlCmd", "editor.renderWhitespace": "all", "editor.rulers": [ 120, 70 ], "editor.stickyScroll.enabled": true, "editor.tabCompletion": "on", "editor.tabSize": 4, "editor.wordBasedSuggestionsMode": "allDocuments", "editor.wordWrapColumn": 120, "explorer.compactFolders": false, "explorer.confirmDelete": true, "explorer.confirmDragAndDrop": true, "extensions.autoCheckUpdates": false, "files.associations": { "resources/*.json": "jsonc" }, "files.autoSave": "onFocusChange", "files.defaultLanguage": "${activeEditorLanguage}", "files.eol": "\n", "files.exclude": { "**/.cache": true, "**/.git": true, "**/.quarto": true, "**/*.html": true, "**/readme_files/**": true, "**/site_libs/**": true }, "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, "maptz.regionfolder": { "[dockerfile]": { "foldEnd": "[\\S]*\\#[\\s]*endregion", "foldEndRegex": "[\\S]*\\#[\\s]*endregion[\\s]*(.*)$", "foldStart": "[\\S]*\\#[\\s]*region [NAME]", "foldStartRegex": "[\\S]*\\#[\\s]*region[\\s]*(.*)$" } }, "markdown.extension.toc.levels": "2..6", "markdown.extension.tableFormatter.enabled": true, "markdownlint.config": { "MD033": false, "MD036": false }, "quarto.preview.renderOnSave": true, "telemetry.telemetryLevel": "off", "terminal.integrated.profiles.linux": { "bash": { "path": "/bin/bash", "icon": "terminal-bash" } }, "terminal.integrated.defaultProfile.linux": "bash", "terminal.integrated.fontFamily": "MesloLGS NF", "terminal.integrated.tabs.defaultLocation": "editorGroup", "yaml.schemas": { "https://json.schemastore.org/json-schema-v7.json": [ "_quarto.yml", "_quarto-*.yml" ], "https://json.schemastore.org/github-workflow.json": ".github/workflows/*.yml" }, "window.title": "${dirty}${rootName} ${separator} [DevContainer]", "workbench.colorTheme": "Visual Studio Light" }, "extensions": [ "DavidAnson.vscode-markdownlint", "esbenp.prettier-vscode", "gitkraken.gitlens", "maptz.regionfolder", "mde.select-highlight-minimap", "mikestead.dotenv", "ms-vscode.makefile-tools", "quarto.quarto", "redhat.vscode-yaml", "sirtori.indenticator", "streetsidesoftware.code-spell-checker-dutch", "streetsidesoftware.code-spell-checker-french", "streetsidesoftware.code-spell-checker", "yzhang.markdown-all-in-one" ] } } } ``` > **Information:** > > À strictement parler, nous n'avons pas besoin du fichier `.devcontainer/compose.yaml`, mais c'est le seul moyen de construire l'image Docker **une seule fois** et de la réutiliser sur plusieurs projets. > > En ouvrant un Devcontainer, même si le `Dockerfile` est strictement identique dans tous vos projets de documentation, le contexte sera différent (**project1**, **project2**, ...) et VSCode reconstruira l'image pour ce contexte. Pour éviter ça, il faut construire l'image et **lui donner un nom** ; ce n'est possible qu'avec un fichier `.devcontainer/compose.yaml`. ## Informations supplémentaires (optionnel — passez si les valeurs par défaut vous conviennent) {#extra-information-optional--skip-if-the-defaults-work-for-you} ### Installation de sudo {#installation-of-sudo} Pendant la création de l'image Docker, nous installons aussi la commande `sudo` et autorisons notre utilisateur `vscode` à lancer `sudo su root` dans le devcontainer sans devoir saisir de mot de passe. La raison : parfois, Quarto se plaint d'une dépendance manquante (par exemple lors d'un export en PDF, quand une librairie manque). Pour permettre au développeur d'ajouter rapidement la dépendance et de faire quelques tests sans devoir reconstruire le container encore et encore, `sudo` est installé. ## Arguments de build (optionnel — ne lisez que ceux qui vous concernent) {#build-arguments-optional--read-only-the-ones-you-need} ### Installation de Chromium {#installation-of-chromium} Si vous regardez le fichier `.devcontainer/compose.yaml`, vous verrez un argument appelé `INSTALL_CHROMIUM`. Selon votre projet, vous en aurez besoin ou pas. En effet, dans certaines situations, lors du rendu de votre documentation en Word (c'est-à-dire en lançant par exemple `quarto render . --profile docx --to docx`), Quarto peut vous demander d'installer Chromium. Pour éviter de le faire chaque fois, ouvrez simplement le fichier `.devcontainer/devcontainer.json`, cherchez `INSTALL_CHROMIUM` et mettez-le à `true`. Note : si vous modifiez le code du `.devcontainer/Dockerfile` ou le fichier `.devcontainer/devcontainer.json`, vous devrez reconstruire le container comme expliqué ci-dessous. > **Assurez-vous d'en avoir besoin:** > > Avant d'installer Chromium, assurez-vous d'en avoir besoin : faites d'abord le rendu de votre documentation sans lui et voyez si Quarto se plaint de Chromium. Chromium demande beaucoup de dépendances et fera considérablement grossir la taille de votre image Docker. ### Installation de Code Spell Checker {#installation-of-code-spell-checker} De la même manière, vous avez une variable appelée `INSTALL_CSPELL`. Mettez-la à `true` si vous voulez installer l'outil Code-spell check (nécessite Node.js). ### Installation de GraphViz {#installation-of-graphviz} De la même manière, vous avez une variable appelée `INSTALL_GRAPHVIZ`. Mettez-la à `true` si vous voulez installer le convertisseur GraphViz. C'est nécessaire quand, dans votre fichier `.qmd`, vous avez un graphe comme celui-ci : ```markdown digraph flow { rankdir = LR; A [shape=box, label="Source"]; B [shape=ellipse, label="Processor"]; C [shape=box, label="Destination"]; A -> B; B -> C; } ``` > **GraphViz:** > > Si vous avez un graphe Graphviz et que vous n'avez pas installé GraphViz, vous obtiendrez une erreur comme celle-ci : `ERROR: AssertionError: Error occurred during cleanup: TypeError: Child process has already terminated`. ### Installation des pre-commit-hooks {#installation-of-pre-commit-hooks} La troisième variable est `INSTALL_PRECOMMIT_HOOKS` et, si votre documentation a son propre dossier `.git`, ce sera une bonne idée de mettre la variable à `true` : ainsi, lors du commit de vos modifications, quelques contrôles de qualité des données et outils de formatage seront appliqués. ## Ouvrir notre projet comme Devcontainer {#opening-our-project-as-a-devcontainer} Maintenant, appuyez juste sur `F1` (ou `CTRL`+`SHIFT`+`P`) et sélectionnez **Dev Containers: Rebuild and Reopen in Container**. Si vous n'avez pas cette commande, vérifiez que l'extension `ms-vscode-remote.remote-containers` de Microsoft est bien installée. > **Le remote container de Microsoft:** > > Vous pouvez installer l'extension depuis la console en lançant `code --install-extension ms-vscode-remote.remote-containers` ou en ouvrant cette page : [Marketplace - Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers). Donc, une fois la commande **Dev Containers: Rebuild and Reopen in Container** lancée, VSCode fermera votre session courante et en ouvrira une nouvelle. La toute première fois, VSCode devra faire quelques initialisations (comme construire l'image Docker et télécharger les extensions, ce sera donc plus lent). Après quelques secondes, vous obtiendrez un écran comme celui-ci : ![Le Devcontainer a été ouvert](./images/devcontainer.webp) 1. En bas à gauche, dans la barre de statut, vous avez le texte "Dev Container: xxx" (suivi du nom de notre container tel que configuré dans le fichier `devcontainer.json`). 2. Dans la **fenêtre Terminal** (si vous ne la voyez pas, appuyez sur `CTRL`+`ù`), vous verrez un pense-bête avec les commandes principales, comme `quarto preview .` pour lancer le site avec le hot reload. Donc, toujours dans le Terminal, tapez `quarto preview .` puis appuyez sur `Enter` — vous obtiendrez la même sortie que celle déjà montrée en haut de cet article. En théorie, votre navigateur sera lancé automatiquement et le site ouvert, affichant le même résultat que celui déjà montré plus haut. Sinon, suivez simplement le lien en appuyant sur `ALT` et en cliquant dessus. Ridiculement simple, non ? ## Qu'avons-nous fait ? {#what-have-we-done} À ce stade, qu'avons-nous fait ? Nous avons récupéré un projet de site Quarto et nous nous sommes assurés de le « dockeriser ». Regardons le fichier `_quarto.yml` du projet que nous venons de cloner (vous trouverez plus d'informations sur le site officiel de Quarto : [Creating a website](https://quarto.org/docs/websites/)) : **\_quarto.yml** ```yaml project: type: website website: title: "Simple Brand Example" navbar: title: "Demo CO" tools: - icon: github href: https://github.com/quarto-dev/quarto-examples/tree/main/brand/brand-simple ``` Nous avons ajouté trois fichiers au projet, dans un dossier spécial appelé `.devcontainer`. Ce dossier indique à VSCode que nous souhaitons utiliser un container Docker quand nous travaillons sur ce projet. Le container sera basé sur une image Docker personnalisée (telle que codée dans `.devcontainer/Dockerfile`). L'image réutilisera l'image officielle de Quarto. Ainsi, sans devoir installer Quarto sur notre machine, nous pourrons utiliser toutes les fonctionnalités de Quarto. Enfin, nous devons *basculer vers le devcontainer* : juste après avoir ouvert VSCode, nous devons lancer une commande spéciale pour rouvrir le projet comme devcontainer. Cette commande est, la première fois, **Dev Containers: Rebuild and Reopen in Container** mais, dès que l'image personnalisée a été créée une fois, nous pouvons simplement utiliser **Dev Containers: Reopen in Container**. ## Rédiger notre documentation {#writing-our-documentation} Jusqu'ici, nous avons déjà lancé `quarto preview .` dans un terminal. La documentation a été générée et notre site est déjà affiché (sur `http://localhost:7519` ou tout autre port). Testons le hot reload : éditez le fichier `index.qmd` que vous trouverez dans le dossier racine et changez quelque chose. Par exemple, remplacez le mot `Overview` par `Preface` et enregistrez. ![La synchronisation est déjà activée](./images/synchronization-enabled.webp) Comme vous le voyez sur l'image ci-dessus, il me suffit de faire une modification, d'enregistrer mon fichier et d'attendre moins d'une seconde pour que le site soit mis à jour automatiquement. Rien à faire (je n'ai pas besoin de rafraîchir la page). Simple, non ? ## Utiliser le terminal {#using-the-terminal} Allez dans la **fenêtre Terminal** (si vous ne la voyez pas, appuyez sur `CTRL`+`ù`). Regardez maintenant la partie droite : il y a un terminal `Configuring...` mais aussi un `bash workspace` (le nom de notre projet). Si vous ne le voyez pas, regardez juste au-dessus de `Configuring...` et cliquez sur le bouton `+`. ![Le terminal](./images/terminal.webp) Comme vous le voyez, vous avez quelques *instructions*. Ces instructions ont été codées dans la dernière partie du fichier `.devcontainer/Dockerfile`. Elles sont là juste pour aider l'utilisateur la première fois. Donc, si votre objectif est de générer la documentation finale, vous pouvez toujours vous dire *Ah oui, il me suffit de lancer `quarto render .`* ## Qu'est-ce que pre-commit ? {#what-is-pre-commit} Dans ce projet, j'ai prévu l'installation de [pre-commit](https://pre-commit.com/). C'est un outil optionnel qui lancera quelques contrôles sur votre projet avant que vous ne le poussiez vers votre système de versioning (comme GitHub ou GitLab). *Je lui ai consacré un article complet : [Git - pre-commit-hooks](/blog/git-precommit).* En ajoutant `pre-commit` dans le devcontainer, vous aurez le privilège de ne plus jamais commiter de fichiers avec des défauts comme, par exemple, des problèmes de formatage dans votre contenu Markdown. Si cette idée vous plaît, créez le fichier `.pre-commit-config.yaml` dans le dossier racine de votre projet (donc pas dans le dossier `.devcontainer` mais dans son dossier parent). Copiez/collez simplement le contenu ci-dessous : **.pre-commit-config.yaml** ```yaml # Pre-commit configuration for Quarto projects exclude: "^.build/|^.git/|^.quarto/" repos: # --- Universal hooks for clean files --- - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: check-added-large-files - id: check-case-conflict - id: check-json - id: check-merge-conflict - id: check-yaml - id: detect-private-key - id: end-of-file-fixer - id: trailing-whitespace # Markdown linter - repo: https://github.com/igorshubovych/markdownlint-cli rev: v0.45.0 hooks: - id: markdownlint args: - "--config=.config/.markdownlint.yaml" # Checks all of the hyperlinks in a markdown text to determine if they are alive or dead - repo: https://github.com/tcort/markdown-link-check rev: v3.13.7 hooks: - id: markdown-link-check name: Check Markdown Links entry: markdown-link-check language: node types: [markdown] # The hook below hook extracts the YAML front matter from each .qmd # file and validates its syntax using yq. # It helps catch formatting errors (e.g. missing colons, invalid keys) # before committing. # Only the YAML block between the first pair of --- lines is checked. - repo: local hooks: - id: validate-qmd-yaml name: Validate YAML front matter in .qmd entry: bash -c 'awk "/^---/{f=!f;next}f" "$1" | yq eval "." - > /dev/null' language: system pass_filenames: true args: ["{}"] files: \.qmd$ # --- Spelling --- # Requires to install cspell in the Docker file as a binary. This implies to install NPM tool # The .vscode/cspell.json file is shared with VSCode - repo: local hooks: - id: cspell name: Spell check with cspell entry: cspell language: system args: - "--config=.vscode/cspell.json" - "--no-progress" - "--no-summary" exclude: ^.*_extensions/.* ``` Une fois ce fichier en place, quand vous lancerez la commande `git commit` depuis le terminal (`CTRL`+`ù`), `git` lancera d'abord `pre-commit` pour exécuter les *hooks*. Dans le fichier `.pre-commit-config.yaml` ci-dessus, nous avons défini deux hooks : le premier pour faire des vérifications génériques sur les fichiers, puis un second pour s'assurer qu'il n'y a pas d'erreurs de linting dans les fichiers `.md` (consultez le repo officiel pour plus d'infos). Ces hooks sont déclenchés chaque fois que vous commitez des fichiers (et uniquement sur les fichiers commités ; pas sur tout le projet). Vous pouvez aussi forcer l'exécution des hooks sans commiter, en lançant `pre-commit run --all-files` dans la console. Cette commande vérifiera tous les fichiers. `pre-commit` est un outil précieux pour garantir le respect des standards de qualité établis dans l'industrie. ## Conclusion {#conclusion} Avec seulement trois fichiers (`.devcontainer/compose.yaml`, `.devcontainer/Dockerfile` et `.devcontainer/devcontainer.json`), nous avons converti un projet simple en Devcontainer où Quarto est préinstallé, les extensions installées et configurées, et où des fonctionnalités comme la prévisualisation automatique tournent. Cool, non ? --- # Quarto - Comment j'ai construit un écosystème auto-documenté pour plus de 50 projets > Plongée dans la conception d'un écosystème de documentation entièrement automatisé avec Quarto et Docker, transformant la documentation en un pipeline de build performant. --- ![Quarto - Comment j'ai construit un écosystème auto-documenté pour plus de 50 projets](/img/v2/quarto-industrialisation.webp) **TL;DR** Cet article explique comment j'ai construit un écosystème auto-documenté pour plus de 50 projets avec Quarto et Docker : la documentation est automatisée et reste alignée avec le code. En tant que développeur, j'aime écrire du code et de la documentation, mais soyons honnêtes : garder une documentation à jour est difficile. À la seconde où vous l'écrivez, elle devient obsolète. C'est un problème que j'ai rencontré encore et encore au cours de ma carrière. Je voulais trouver un moyen d'automatiser la documentation, pour qu'elle reflète toujours l'état réel du code. Vous construisez une belle API, vous écrivez un manuel, puis… vous modifiez votre code. La structure de la réponse n'est plus la même, le message d'erreur a changé, vous avez publié une v1.1 alors que votre documentation parlait encore de la v1.0, et ainsi de suite. Que faire pour résoudre ce problème ? Peut-on automatiser la documentation ? Peut-on la rendre auto-actualisante ? Peut-on faire en sorte que ma documentation change quand mon code change ? Réponse courte : oui. Et c'est exactement ce que j'ai fait avec [Quarto](https://quarto.org/). Voyons comment industrialiser notre documentation avec Quarto et Docker, en créant un écosystème auto-documenté qui passe à l'échelle sur plusieurs projets. **Quick Jump:** [Le moment « Wow »](#-the-wow-moment) | [Installation — Le labo « Wow »](#installation--the-wow-lab-build-your-first-doc-engine-in-5-minutes) ## 🚀 Le moment « Wow » {#-the-wow-moment} Une fois le labo en route (voir la section Installation plus bas), ouvrez le dossier dans VS Code, cliquez sur **"Reopen in Container"**, puis tapez dans le terminal : `./scripts/preview.sh`. Vous obtiendrez quelque chose comme ceci : ![Script de prévisualisation](./images/devcontainer.webp) Faites un `Ctrl + Click` sur l'URL (`http://localhost:`4242) : votre navigateur s'ouvre avec le document Quarto rendu. Vous devriez voir les diagrammes Mermaid générés par nos scripts Python. > Screenshot — http\://localhost:4242 > > ![Carte d'infrastructure automatisée](./images/automated_infrastructure_map.webp) Sympa, non ? Mais d'où vient ce diagramme ? Il est généré par la fonction `render_project_arch()` de notre script `project_viz.py`. Ce script lit notre `compose.yaml` et notre `Dockerfile` pour comprendre la structure du projet, puis génère un diagramme Mermaid à partir de ça. Regardons la sortie de notre script `file_stats.py`. Il analyse les fichiers du projet et produit un rapport rendu directement dans le document Quarto. ![Statistiques de fichiers](./images/file_stats.webp) Et c'est en temps réel. Ajoutez un nouveau fichier au projet, sauvegardez, rafraîchissez le navigateur : le fichier apparaît dans les stats et le diagramme d'architecture se met à jour. Le script `my_feature.py` génère un diagramme Mermaid simple à partir d'une liste d'éléments. Modifiez la liste dans le script, sauvegardez, et voyez le diagramme se mettre à jour en direct. ![My Feature](./images/my_feature.webp) Enfin, le script `call_api.py` permet d'appeler une API en direct pendant le rendu. Vous pouvez donc récupérer de vraies données de votre environnement de production ou de staging et les inclure dans votre documentation, tout en gardant vos identifiants à l'abri grâce au Secrets Resolver. ![Appel d'API](./images/call_api.webp) ## Pourquoi ça fonctionne {#why-it-works} Beaucoup pensent que le Doc-as-Code, c'est juste « mettre du Markdown dans Git ». Faux. Il s'agit d'**exécution au runtime**. * Un document Quarto n'est pas du Markdown statique : un bloc de code `{python}` intégré au document **s'exécute pendant le rendu**, pas avant. * `output: asis` dit à Quarto de traiter ce que la fonction Python affiche comme du Markdown brut (ou du Mermaid) plutôt que comme du texte littéral — une fonction qui retourne un diagramme devient donc un diagramme rendu, pas un listing de code. * Comme le diagramme est généré à partir du `compose.yaml` et du `Dockerfile` *réels*, modifier l'infrastructure puis relancer le rendu met à jour le diagramme automatiquement — plus jamais de redessin manuel. ## Installation — Le labo « Wow » : construisez votre premier moteur de doc en 5 minutes {#installation--the-wow-lab-build-your-first-doc-engine-in-5-minutes} Voici une configuration concrète, prête à l'emploi. Ce Dockerfile crée un environnement où Quarto peut « discuter » avec des scripts Python personnalisés pour générer des diagrammes Mermaid à la volée. *La partie Python-vers-Mermaid est détaillée à part dans [Documentation as Code - Transform Your Infrastructure into Beautiful Diagrams with Python and Mermaid](/blog/docker-python-mermaid) ; ici, on la branche sur Quarto.* ### 1. L'orchestrateur (compose.yaml) {#1-the-orchestrator-composeyaml} **compose.yaml** ```yaml services: docs: build: . volumes: - .:/workspace ports: - "4242:4242" environment: - PYTHONPATH=/workspace/scripts command: > bash -c "chmod +x /workspace/scripts/preview.sh && sleep infinity" ``` ### 2. Le moteur (`Dockerfile`) {#2-the-engine-dockerfile} On installe Quarto et on configure le `PYTHONPATH` pour que notre document puisse importer notre bibliothèque « Feature » maison depuis n'importe où. **Dockerfile** ```docker # Use a clean Ubuntu base FROM ubuntu:22.04 # Install Dependencies & Quarto RUN apt-get update && apt-get install -y --no-install-recommends curl python3 python3-pip && \ rm -rf /var/lib/apt/lists/* RUN curl -L -o quarto.tar.gz https://github.com/quarto-dev/quarto-cli/releases/download/v1.4.550/quarto-1.4.550-linux-amd64.tar.gz \ && mkdir -p /opt/quarto && tar -xzf quarto.tar.gz -C /opt/quarto --strip-components=1 \ && ln -s /opt/quarto/bin/quarto /usr/local/bin/quarto WORKDIR /workspace # Disable Python bytecode generation to keep image layers lean ENV PYTHONDONTWRITEBYTECODE=1 # Force Python stdout and stderr streams to be unbuffered for immediate logging ENV PYTHONUNBUFFERED=1 # Install Python requirements for Quarto (Jupyter & YAML) RUN pip3 install --no-cache-dir jupyter PyYAML ``` ### 3. L'expérience (.devcontainer/devcontainer.json) {#3-the-experience-devcontainerdevcontainerjson} C'est ici que la magie opère. On indique à VS Code quelles extensions installer et comment se comporter. *[Make your Quarto project Devcontainer-Ready — No More Setup Headaches](/blog/quarto-devcontainer) parcourt ce fichier ligne par ligne.* **.devcontainer/devcontainer.json** ```json { "name": "WritingDoc Lab", "dockerComposeFile": "../compose.yaml", "service": "docs", "workspaceFolder": "/workspace", "customizations": { "vscode": { "extensions": [ "quarto.quarto", "ms-python.python", "yzhang.markdown-all-in-one", "tomoyukim.vscode-mermaid-editor" ], "settings": { "files.exclude": { "index_files/": true, "index.html": true }, "quarto.renderOnSave": true, "editor.formatOnSave": true } } } } ``` ### 4. Nos scripts Python {#4-our-python-scripts} On va utiliser quelques scripts Python simples pour générer le contenu de la documentation. Par exemple, `my_feature.py` contient une fonction qui génère un diagramme Mermaid à partir d'une liste d'éléments, `file_stats.py` analyse les fichiers du projet et produit un rapport, et `project_viz.py` crée une représentation visuelle de la structure du projet. **scripts/call\_api.py** ```python #!/usr/bin/env python3 """ Fetch 10 fake users from RandomUser API and display the result as a Markdown table. """ from typing import Any, Dict, List import requests API_URL: str = "https://randomuser.me/api/" def fetch_users() -> List[Dict[str, Any]]: """Call RandomUser API and return a list of users.""" params: Dict[str, Any] = {"results": 10} response: requests.Response = requests.get( API_URL, params=params, timeout=10 ) response.raise_for_status() data: Dict[str, Any] = response.json() return data["results"] def format_markdown_table(users: List[Dict[str, Any]]) -> str: """Convert users data into a Markdown table.""" headers = ["Name", "Email", "Phone", "City", "Country"] lines = [] lines.append("| " + " | ".join(headers) + " |") lines.append("| " + " | ".join(["---"] * len(headers)) + " |") for user in users: name = f"{user['name']['first']} {user['name']['last']}" email = user["email"] phone = user["phone"] city = user["location"]["city"] country = user["location"]["country"] lines.append(f"| {name} | {email} | {phone} | {city} | {country} |") return "\n".join(lines) def main() -> None: """Main program.""" users = fetch_users() markdown_table = format_markdown_table(users) print(markdown_table) if __name__ == "__main__": main() ``` **scripts/file\_stats.py** ````python import os from collections import Counter from pathlib import Path def render_extension_pie(directory="."): """Scans the directory and generates a Mermaid pie chart of file types.""" p = Path(directory) ignored_folders = {'.git', '.quarto', '__pycache__', 'node_modules'} extensions = [] for f in p.rglob('*'): if f.is_file() and not any(part in ignored_folders for part in f.parts): ext = f.suffix.lower() if f.suffix else "No extension" extensions.append(ext) stats = Counter(extensions) mermaid = ["pie title Project File Distribution"] for ext, count in stats.items(): label = ext.replace('.', '').upper() if ext.startswith('.') else ext mermaid.append(f' "{label}" : {count}') print("\n```{mermaid}") print("\n".join(mermaid)) print("```\n") ```` **scripts/my\_feature.py** ````python def generate_flow(nodes): """Generates a Mermaid flowchart from a list of names.""" mermaid = ["flowchart LR"] for i, name in enumerate(nodes): mermaid.append(f' node{i}["{name}"]') if i > 0: mermaid.append(f" node{i-1} --> node{i}") # We output "asis" Markdown so Quarto interprets it as a block print("\n```{mermaid}\n") print("\n".join(mermaid)) print("\n```\n") ```` **scripts/project\_viz.py** ````python import yaml import re from pathlib import Path def render_project_arch(): compose_path = Path("compose.yaml") docker_path = Path("Dockerfile") parent_image = "Unknown" volume_mapping = "Unknown" exposed_port = "Unknown" cmd = "Unknown" if docker_path.exists(): content = docker_path.read_text() from_match = re.search(r"FROM\s+(.+)", content) if from_match: parent_image = from_match.group(1).strip() if compose_path.exists(): with open(compose_path, 'r') as f: data = yaml.safe_load(f) svc = data.get('services', {}).get('docs', {}) volumes = svc.get('volumes', ['?']) volume_mapping = volumes[0].split(":")[0] if ":" in volumes[0] else volumes[0] ports = svc.get('ports', ['?']) exposed_port = str(ports[0]) cmd = svc.get('command', 'sleep infinity') mermaid = [ "flowchart TD", " classDef host fill:#f9f9f9,stroke:#333,stroke-dasharray: 5 5", " classDef container fill:#e1f5fe,stroke:#01579b,stroke-width:2px", " classDef image fill:#fff3e0,stroke:#e65100,stroke-width:1px", "", ' HostFolder["📁 Local Folder"]', ' UserBrowser["🌐 Web Browser"]', f' BaseImage(["📦 Parent Image: {parent_image}"]):::image', ' subgraph WritingDoc ["🚀 Documentation Container"]', ' QuartoEngine{"Quarto Engine"}', ' PythonEngine[["🐍 Python Helpers"]]', ' end', ' class WritingDoc container', "", ' HostFolder -- "Mount" --> WritingDoc', ' BaseImage -- "Extends" --> WritingDoc', ' WritingDoc -- "HTTP" --> UserBrowser' ] print("\n```{mermaid}") print("\n".join(mermaid)) print("```\n") ```` ### 5. Le document (`index.qmd`) {#5-the-document-indexqmd} C'est ici qu'on écrit notre document Quarto. Au lieu de Markdown statique, on peut exécuter du code Python directement dans le document pour générer du contenu dynamique. Par exemple, on peut appeler notre script `my_feature.py` pour générer un diagramme Mermaid basé sur l'état actuel du code. **index.qmd** ````qmd --- title: "Project Self-Analysis" --- ## 🛠️ Automated Infrastructure Map ```{python} #| echo: false #| output: asis from project_viz import render_project_arch render_project_arch() ``` ## 📈 File System Analytics The following chart analyzes the physical structure of this DevContainer. It counts every file in our workspace by its extension. ```{python} #| echo: false #| output: asis from file_stats import render_extension_pie render_extension_pie(".") ``` ## 📊 Business Logic Flow ```{python} #| echo: false #| output: asis from my_feature import generate_flow generate_flow(["Load Balancer", "API Gateway", "Auth Service", "Database"]) ``` ## 🧑 Call API Call RandomUser API and return a list of 10 fake users. ```{python} #| echo: false #| output: asis from call_api import main main() ``` ```` Vous voyez les trois blocs `{python}` dans le document ? C'est là que la magie opère. C'est là qu'on exécute notre code Python. La sortie de ce code est rendue directement dans le document. Quand Quarto rencontre le bloc ci-dessous, il exécute la fonction `render_project_arch()` de notre script `project_viz.py` (cette feature doit être présente dans le `PYTHONPATH`). La fonction génère un diagramme Mermaid basé sur l'état actuel du projet, et Quarto le rend directement dans le document. ````markdown ```{python} #| echo: false #| output: asis from project_viz import render_project_arch render_project_arch() ``` ```` L'option `output: asis` dit à Quarto de traiter la sortie comme du Markdown brut. Autrement dit, si notre script Python sort un diagramme Mermaid, Quarto le rend comme un diagramme au lieu d'afficher simplement le code. ### Le projet complet {#the-full-project} Voici la structure complète du projet. Vous pouvez copier cette configuration et la lancer sur votre machine pour voir comment ça marche en pratique. Le plus simple : cliquez sur le bouton `Generate install script` puis sur le bouton `Copy`. Ouvrez un terminal et collez la commande. Elle créera un dossier `/tmp/quarto_industrialisation` avec tous les fichiers et le setup Docker prêt à l'emploi. **Project: /tmp/quarto\_industrialisation** > **Note:** > > Lancez maintenant vscode avec `code .` et choisissez **Reopen in Container** quand la question vous est posée. Dans le terminal, exécutez `./scripts/preview.sh` et ouvrez le lien dans votre navigateur. Modifiez la liste dans `my_feature.py` et regardez le diagramme se mettre à jour en temps réel. **.devcontainer/devcontainer.json** ```json { "name": "WritingDoc Lab", "dockerComposeFile": "../compose.yaml", "service": "docs", "workspaceFolder": "/workspace", "customizations": { "vscode": { "extensions": [ "quarto.quarto", "ms-python.python", "yzhang.markdown-all-in-one", "tomoyukim.vscode-mermaid-editor" ], "settings": { "files.exclude": { "index_files/": true, "index.html": true }, "quarto.renderOnSave": true, "editor.formatOnSave": true } } } } ``` **scripts/call\_api.py** ```python #!/usr/bin/env python3 """ Fetch 10 fake users from RandomUser API and display the result as a Markdown table. """ from typing import Any, Dict, List import requests API_URL: str = "https://randomuser.me/api/" def fetch_users() -> List[Dict[str, Any]]: """Call RandomUser API and return a list of users.""" params: Dict[str, Any] = {"results": 10} response: requests.Response = requests.get( API_URL, params=params, timeout=10 ) response.raise_for_status() data: Dict[str, Any] = response.json() return data["results"] def format_markdown_table(users: List[Dict[str, Any]]) -> str: """Convert users data into a Markdown table.""" headers = ["Name", "Email", "Phone", "City", "Country"] lines = [] lines.append("| " + " | ".join(headers) + " |") lines.append("| " + " | ".join(["---"] * len(headers)) + " |") for user in users: name = f"{user['name']['first']} {user['name']['last']}" email = user["email"] phone = user["phone"] city = user["location"]["city"] country = user["location"]["country"] lines.append(f"| {name} | {email} | {phone} | {city} | {country} |") return "\n".join(lines) def main() -> None: """Main program.""" users = fetch_users() markdown_table = format_markdown_table(users) print(markdown_table) if __name__ == "__main__": main() ``` **scripts/file\_stats.py** ````python import os from collections import Counter from pathlib import Path def render_extension_pie(directory="."): """Scans the directory and generates a Mermaid pie chart of file types.""" p = Path(directory) ignored_folders = {'.git', '.quarto', '__pycache__', 'node_modules'} extensions = [] for f in p.rglob('*'): if f.is_file() and not any(part in ignored_folders for part in f.parts): ext = f.suffix.lower() if f.suffix else "No extension" extensions.append(ext) stats = Counter(extensions) mermaid = ["pie title Project File Distribution"] for ext, count in stats.items(): label = ext.replace('.', '').upper() if ext.startswith('.') else ext mermaid.append(f' "{label}" : {count}') print("\n```{mermaid}") print("\n".join(mermaid)) print("```\n") ```` **scripts/my\_feature.py** ````python def generate_flow(nodes): """Generates a Mermaid flowchart from a list of names.""" mermaid = ["flowchart LR"] for i, name in enumerate(nodes): mermaid.append(f' node{i}["{name}"]') if i > 0: mermaid.append(f" node{i-1} --> node{i}") # We output "asis" Markdown so Quarto interprets it as a block print("\n```{mermaid}\n") print("\n".join(mermaid)) print("\n```\n") ```` **scripts/project\_viz.py** ````python import yaml import re from pathlib import Path def render_project_arch(): compose_path = Path("compose.yaml") docker_path = Path("Dockerfile") parent_image = "Unknown" volume_mapping = "Unknown" exposed_port = "Unknown" cmd = "Unknown" if docker_path.exists(): content = docker_path.read_text() from_match = re.search(r"FROM\s+(.+)", content) if from_match: parent_image = from_match.group(1).strip() if compose_path.exists(): with open(compose_path, 'r') as f: data = yaml.safe_load(f) svc = data.get('services', {}).get('docs', {}) volumes = svc.get('volumes', ['?']) volume_mapping = volumes[0].split(":")[0] if ":" in volumes[0] else volumes[0] ports = svc.get('ports', ['?']) exposed_port = str(ports[0]) cmd = svc.get('command', 'sleep infinity') mermaid = [ "flowchart TD", " classDef host fill:#f9f9f9,stroke:#333,stroke-dasharray: 5 5", " classDef container fill:#e1f5fe,stroke:#01579b,stroke-width:2px", " classDef image fill:#fff3e0,stroke:#e65100,stroke-width:1px", "", ' HostFolder["📁 Local Folder"]', ' UserBrowser["🌐 Web Browser"]', f' BaseImage(["📦 Parent Image: {parent_image}"]):::image', ' subgraph WritingDoc ["🚀 Documentation Container"]', ' QuartoEngine{"Quarto Engine"}', ' PythonEngine[["🐍 Python Helpers"]]', ' end', ' class WritingDoc container', "", ' HostFolder -- "Mount" --> WritingDoc', ' BaseImage -- "Extends" --> WritingDoc', ' WritingDoc -- "HTTP" --> UserBrowser' ] print("\n```{mermaid}") print("\n".join(mermaid)) print("```\n") ```` **scripts/preview\.sh** ```bash #!/bin/bash set -euo pipefail echo -e "\n\e[1;34m🚀 WritingDoc Engine is starting...\e[0m" echo -e "\e[1;32m👉 Click to view your live docs: http://localhost:4242\e[0m\n" # Run Quarto in quiet mode so the 0.0.0.0 binding notice doesn't clutter the output quarto preview index.qmd --port 4242 --host 0.0.0.0 --no-browser ``` **compose.yaml** ```yaml services: docs: build: . volumes: - .:/workspace ports: - "4242:4242" environment: - PYTHONPATH=/workspace/scripts command: > bash -c "chmod +x /workspace/scripts/preview.sh && sleep infinity" ``` **Dockerfile** ```docker # Use a clean Ubuntu base FROM ubuntu:22.04 # Install Dependencies & Quarto RUN apt-get update && apt-get install -y --no-install-recommends curl python3 python3-pip && \ rm -rf /var/lib/apt/lists/* RUN curl -L -o quarto.tar.gz https://github.com/quarto-dev/quarto-cli/releases/download/v1.4.550/quarto-1.4.550-linux-amd64.tar.gz \ && mkdir -p /opt/quarto && tar -xzf quarto.tar.gz -C /opt/quarto --strip-components=1 \ && ln -s /opt/quarto/bin/quarto /usr/local/bin/quarto WORKDIR /workspace # Disable Python bytecode generation to keep image layers lean ENV PYTHONDONTWRITEBYTECODE=1 # Force Python stdout and stderr streams to be unbuffered for immediate logging ENV PYTHONUNBUFFERED=1 # Install Python requirements for Quarto (Jupyter & YAML) RUN pip3 install --no-cache-dir jupyter PyYAML ``` **index.qmd** ````qmd --- title: "Project Self-Analysis" --- ## 🛠️ Automated Infrastructure Map ```{python} #| echo: false #| output: asis from project_viz import render_project_arch render_project_arch() ``` ## 📈 File System Analytics The following chart analyzes the physical structure of this DevContainer. It counts every file in our workspace by its extension. ```{python} #| echo: false #| output: asis from file_stats import render_extension_pie render_extension_pie(".") ``` ## 📊 Business Logic Flow ```{python} #| echo: false #| output: asis from my_feature import generate_flow generate_flow(["Load Balancer", "API Gateway", "Auth Service", "Database"]) ``` ## 🧑 Call API Call RandomUser API and return a list of 10 fake users. ```{python} #| echo: false #| output: asis from call_api import main main() ``` ```` ## Mon cas d'usage réel {#my-real-use-case} Dans mon écosystème de 50 projets, j'ai repris ce concept de « labo » pour en faire une **image de base WritingDoc** de qualité industrielle. La plupart des setups de documentation partent d'une image « minimale ». C'est une erreur. « Minimal » veut dire « démarrage lent ». J'ai fait l'inverse. Mon image est un monstre de 2,5 Go, mais elle suit une **philosophie « Zero-Wait »**. J'ai tout intégré dedans : Quarto, Python, Node.js, Mermaid-CLI, et tous les linters imaginables. Quand un rédacteur ouvre un projet dans un DevContainer VS Code, il n'attend ni `apt-get` ni `pip install`. L'environnement est vivant et prêt dès l'ouverture de la fenêtre. Pour la version accessible aux débutants de cette idée, voyez [Rendez votre projet Quarto Devcontainer-Ready](/blog/quarto-devcontainer) — cet écosystème, c'est le même principe poussé à 50 projets. ### Doc-as-Code : la magie de l'AST Python {#doc-as-code-the-python-ast-magic} Le vrai facteur « Wow » a commencé quand j'ai arrêté de copier-coller du code dans du Markdown. J'ai écrit une suite de « Features » Python qui utilisent l'analyse **AST (Abstract Syntax Tree)** pour lire mon code source. En réalité, j'ai plus de 40 « Features » différentes capables d'analyser mon code, d'en extraire des informations et de générer de la documentation automatiquement. ![Plus de 40 « Features » différentes capables d'analyser mon code, d'en extraire des informations et de générer de la documentation automatiquement.](./images/having_more_than_40_features.webp) Quelques exemples de ces features : #### 🧠 Analyseurs sémantiques AST Python Réalisent une analyse d'arbre syntaxique abstrait du code source pour en extraire la logique *sans l'exécuter*. Génèrent automatiquement des diagrammes UML, des tableaux d'API et des dictionnaires de données qui restent synchronisés avec le code. #### 🗄️ Introspection de base de données en direct Se connecte à une instance PostgreSQL vivante pour générer des diagrammes ERD en temps réel (Mermaid), des flux de domaines fonctionnels et des dictionnaires de données complets avec aperçu des données. #### 🎨 Moteur Mermaid conscient du format Détecte la cible de rendu (HTML → SVG, PDF/Word → PNG via Puppeteer à 300 DPI). Inclut un système de cache basé sur des empreintes pour ignorer les diagrammes inchangés. #### 🔐 Orchestrateur d'API authentifiées sécurisé Récupère des données en direct depuis des environnements protégés pendant le rendu. Gère tout le handshake OAuth2/Bearer et utilise un **Secrets Resolver** (préfixe `env:`) pour que les identifiants ne touchent jamais Git. #### 🖥️ Capture de terminal haute fidélité Capture une véritable exécution CLI dans une image SVG vectorielle, en préservant les couleurs ANSI, les bordures ASCII et l'intégrité de la mise en page sur les sorties HTML, PDF et Word. #### ⚙️ Cartographe d'infrastructure GitLab CI/CD Reconstruit le pipeline effectif en résolvant les `includes` et `extends` récursifs. Génère une architecture visuelle des étapes CI/CD avec des descriptions de jobs lisibles. #### 🐳 Visualiseurs d'écosystème Docker Cartographient les chaînes d'héritage des images, la topologie des services (ports, réseaux, volumes) et les séquences de build multi-stage en diagrammes clairs et compréhensibles. #### 📋 Tableau de bord des TODO « invisibles » Scanne les commentaires HTML cachés (``) dans tout le projet pour générer un backlog interne consolidé — invisible dans l'export PDF ou Word final destiné au client. #### ✨ Assistant de workspace dynamique Gère automatiquement la sidebar Quarto selon la structure des fichiers et fournit en temps réel des snippets IntelliSense VS Code pour chaque variable et chaque script utilitaire du projet. #### 📊 Visualiseur de données multi-formats intelligent Un moteur de rendu polymorphe pour CSV, JSON et XML. Gère les tableaux de comparaison horizontaux et les vues de détail verticales, avec sélection de nœuds en profondeur (XPath/notation pointée) et cache TTL. #### 🏗️ Générateur de projet conscient du langage Utilise un système de tags par sous-ensembles (`file.python.fastapi.qmd`) pour générer instantanément une structure de documentation complète et adaptée aux technologies du projet. ### Visualiser l'invisible : Mermaid & cache {#visualizing-the-invisible-mermaid--caching} Quarto sait rendre Mermaid.js quand la documentation est produite sous forme de site web, mais ça ne fonctionne pas pour les sorties hors ligne comme Word ou PDF. J'ai résolu ça en construisant un **Advanced Mermaid Renderer**. Chez moi, le moteur de rendu est conscient du format. Si vous consultez la prévisualisation HTML, il sert des SVG nets. Si vous générez un `.docx` pour un décideur métier, il démarre automatiquement une instance Chromium headless (via Puppeteer), rend le diagramme à 300 DPI et intègre un PNG haute résolution. Et comme je déteste attendre, j'ai implémenté un **système de cache basé sur le contenu**. Chaque diagramme et chaque snapshot d'API a son empreinte. Si la source n'a pas changé, le build saute le gros du travail et récupère l'image depuis le cache. Mes temps de build sont passés de plusieurs minutes à quelques secondes. ### La documentation « vivante » : appels d'API authentifiés {#the-living-documentation-authenticated-api-calls} Je voulais que ma documentation soit une fenêtre sur le système en production. J'ai donc construit une feature capable d'effectuer de **vrais appels d'API REST pendant le rendu**. Elle gère tout le handshake OAuth2 : elle interroge le serveur d'authentification, obtient un token Bearer, récupère les dernières données et masque les secrets avant d'afficher une « trace terminal » dans la documentation. Inquiet pour la sécurité ? J'ai écrit un **Secrets Resolver** maison. En utilisant un préfixe `env:` dans mes fichiers Quarto, je peux récupérer des identifiants depuis un fichier `.secrets` local qui n'est jamais commité dans Git. Le framework les résout au runtime et les masque dans tous les fichiers de log. De la sécurité de niveau entreprise pour du Markdown. ### L'assistant « invisible » : daemons en arrière-plan & snippets {#the-invisible-assistant-background-daemons--snippets} Je me suis rendu compte que, malgré toute cette automatisation, les rédacteurs devaient encore se souvenir des noms de variables ou des signatures de scripts. Pour corriger ça, j'ai créé le **WritingDoc Assistant**. C'est un **daemon** Python qui tourne en arrière-plan dans le container. Il surveille mon `_variables.yml` et mon répertoire `features/`. Dès que je sauvegarde une modification, le daemon se réveille et génère des **snippets IntelliSense VS Code** à la volée. * Je tape `v-` et j'obtiens la liste de toutes les variables du projet avec leur valeur actuelle. * Je tape `feature-` et j'obtiens la liste de tous les scripts d'automatisation avec leur documentation complète. * Je tape `r-` et il a déjà scanné tous mes fichiers pour me proposer des références croisées vers chaque titre et chaque figure du projet. C'est comme avoir un bibliothécaire dédié qui vit dans mon éditeur et observe chacun de mes gestes pour me simplifier la vie. Le daemon est démarré dans mon Devcontainer via la commande `nohup`, ce qui garantit qu'il tourne en arrière-plan sans bloquer le processus principal. Il surveille les changements en continu et met à jour les snippets en temps réel, pour une expérience toujours à jour. C'est un vrai bond en productivité. ### Centralisation à grande échelle {#centralization-at-scale} Passer à 50 projets impliquait de ne pas avoir 50 configurations différentes. J'ai tout centralisé : * **`_variables.yml`** : le « cerveau » du projet. Chaque nom, chaque lien, chaque réglage est ici. C'est la source unique de vérité. * **`update_chapters.py`** : un script que j'ai écrit pour maintenir la sidebar Quarto. Il respecte la numérotation des dossiers et ignore les dossiers « draft », pour une table des matières parfaite à chaque fois. * **Le glossaire** : un seul fichier YAML qui génère un glossaire alphabétique, avec ancres, sur l'ensemble du site. * **Les références globales** : un fichier central pour toutes les URL externes, pour ne jamais chercher deux fois le même lien. ### Le résultat {#the-result} Aujourd'hui, quand je démarre un nouveau projet, je lance une seule commande : `scaffold`. En quelques secondes, j'ai une structure complète adaptée à ma stack. La documentation n'est plus un monument statique dédié au passé. C'est un reflet vivant du code. C'est rapide, c'est sécurisé, et c'est beau. Voilà la puissance de la **documentation ingénierée**. On ne se contente plus d'écrire ; on compile notre savoir. ## 🎖️ Un dernier mot : pourquoi Quarto change tout {#️-a-final-word-why-quarto-changes-everything} Rien de tout ça ne serait possible sans le travail visionnaire des développeurs derrière Quarto. L'équipe Quarto a eu l'intelligence et la clairvoyance de construire quelque chose de différent : un pont entre le récit et l'exécution. En nous permettant d'exécuter du vrai code (Python, R, Julia) directement dans le cycle de vie du document, ils ne nous ont pas simplement offert une meilleure version de [Pandoc](https://pandoc.org/). Ils nous ont donné un moteur de documentation extensible. C'est précisément cette capacité — lancer un script, analyser une API ou scanner un système de fichiers pendant le build — qui m'a permis de construire l'écosystème « WritingDoc ». Aux développeurs de Quarto : **merci pour votre travail extraordinaire** et pour avoir offert au monde un outil open source qui permet enfin aux rédacteurs techniques de travailler comme des ingénieurs logiciels. Vous avez transformé la « corvée » de la documentation en une discipline d'ingénierie haute fidélité. ---