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

# 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 (`<!-- TODO: -->`) 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é.
