<!--
  canonical-url: https://www.avonture.be/fr/blog/docker-python-mermaid
  published:     2026-04-20
  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.
-->

# Documentation as Code - Transformez votre infrastructure en diagrammes soignés avec Python et Mermaid

> Automatisez votre Documentation as Code en construisant une stack dockerisée Python et Mermaid CLI pour générer des diagrammes d'architecture dynamiques directement depuis votre code.

---
![Documentation as Code : transformez votre infrastructure en diagrammes soignés avec Python et Mermaid](/img/v2/docker-python-mermaid.webp)

**TL;DR**Une documentation écrite à la main devient obsolète dès que le code change. Pour éviter ça, adoptez la Documentation as Code : construisez une image Docker sur mesure équipée de Python et du CLI Mermaid pour générer automatiquement vos visuels. Avec de simples scripts Python qui analysent vos fichiers compose.yaml ou parcourent vos répertoires de projet, vous produisez dynamiquement du pseudocode Mermaid et le rendez directement en diagrammes PNG propres et à jour (mindmaps, camemberts, etc.) avec une seule commande.

J'aime la documentation, et quoi de plus inefficace que d'écrire de la doc pour des choses qui peuvent s'auto-documenter ?

Comme vous le savez peut-être, j'adore Docker et je fais presque tout de manière dockerisée. Donc, dans chacun de mes projets, il y a un fichier `compose.yaml`. Si je veux le documenter pour mes collègues, je pourrais écrire : *Il y a trois services, un pour PHP, un pour Nginx, un pour PostgreSQL. Celui-là utilise un volume pour rendre les données persistantes sur le disque et blah blah blah. Le service Nginx publie sur le port xxx et la base de données est accessible via le port yyy.* Je peux faire ça, bien sûr, mais n'y a-t-il pas une meilleure façon de procéder ? Et si j'ajoute un nouveau service ou que je change un numéro de port ? Ma documentation serait déjà obsolète, sauf si...

Voyons comment automatiser la documentation avec Docker, Python et Mermaid.

**Quick Jump:** [Voir le résultat](#the-result) | [Tous les fichiers nécessaires](#all-needed-files)

## Le résultat {#the-result}

Pointez le pipeline construit dans cet article vers un vrai `compose.yaml`, et une seule commande produit ceci :

![La mindmap du compose](./files/images/compose.webp)

Cette image a été entièrement générée depuis le fichier `compose.yaml` — aucun dessin manuel, aucun outil de diagramme. Modifiez le YAML, relancez la commande, et le diagramme se met à jour tout seul.

## Pourquoi Mermaid {#why-mermaid}

> **Du pseudocode en entrée, une image en sortie:**
>
> [Mermaid](https://mermaid.js.org/) transforme un petit bloc de texte — son propre *pseudocode* — en diagramme rendu. Regardez cet [exemple](https://mermaid.live/edit#pako:eNpFjctqwzAQRX9FzNoNtmzJtrYtfUALhWbT4o1ijR0RWRMUmTYN-fcqCU3uauZw7swBejIICqKd0FmPnWcp0UaH7NnuIoU9o4F9UG-1Y29orGbvTseBwnRxeZ5zptir9Rs0L_4KqwQfdY8ros0FnqPYE9Ho8OqJhD5pXs6rG5OJLb9tjBgggzFYAyqGGTOYMEz6tMLhZHcQ1zhhByqNBgc9u9hB54-pttX-i2j6bwaaxzWoQbtd2uat0REfrB6DvinoDYZ7mn0EVYi6Ph8BdYAfUHdFVS7aVuZlK9uiraumzGAPipcLIaXkKTWvmrqRxwx-z4-LBW8L0XDR5GVTiroSxz-rtW0b) : à gauche, le pseudocode — lisible d'un coup d'œil ; à droite, l'image rendue.

![Un exemple de timeline depuis le Mermaid Live Editor](./images/timeline.webp)

Rendez-vous sur le [Mermaid Live Editor](https://mermaid.live/edit) et jouez avec les *Sample Diagrams* en bas à gauche pour découvrir tout ce que l'outil propose. Un script Python peut générer ce même pseudocode depuis n'importe quelle source lisible — un `compose.yaml`, une arborescence de dossiers, n'importe quelles données que vous avez déjà — et le CLI Mermaid le rend en PNG transparent.

## Installation — créons l'image Docker {#installation--lets-create-the-docker-image}

Le Dockerfile que nous allons utiliser est celui-ci. Il est assez simple.

**Dockerfile**

```docker
FROM node:20-alpine

RUN apk add --no-cache \
    chromium \
    nss \
    freetype \
    harfbuzz \
    ca-certificates \
    ttf-freefont \
    font-noto-emoji \
    python3 \
    py3-pip \
    py3-yaml

RUN fc-cache -fv

RUN npm install -g @mermaid-js/mermaid-cli

RUN mkdir -p /home/node/.cache/puppeteer && \
    chmod -R 777 /home/node

ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
ENV HOME=/home/node

WORKDIR /app
```

Pour construire cette image, lancez `docker build -t yourself/docker-python-mermaid .` dans votre terminal :

```bash
$ docker build -t yourself/docker-python-mermaid .
```

La taille finale de l'image sera d'environ 2 Go.

![Construction de l'image](./images/build.webp)

## Préparer le rendu Mermaid {#prepare-mermaid-rendering}

Il nous faut deux fichiers externes pour le rendu.

Le premier va configurer Puppeteer :

**puppeteer-config.json**

```json
{
  "args": [
    "--no-sandbox",
    "--disable-setuid-sandbox",
    "--disable-dev-shm-usage",
    "--disable-accelerated-2d-canvas",
    "--no-first-run",
    "--no-zygote",
    "--disable-gpu"
  ]
}
```

Et le second va nous permettre d'utiliser notre propre mise en forme (police, couleurs, ...) lors du rendu du pseudocode en image :

**mermaid-config.json**

```json
{
  "theme": "base",
  "themeVariables": {
    "fontFamily": "Inter, Segoe UI, Arial",
    "fontSize": "16px",
    "lineColor": "#475569",
    "mainBkg": "#ffffff",

    "cScale0": "#0f172a",
    "cScaleLabel0": "#ffffff",

    "cScale1": "#2563eb",
    "cScaleLabel1": "#ffffff",

    "cScale2": "#059669",
    "cScaleLabel2": "#ffffff",

    "cScale3": "#d97706",
    "cScaleLabel3": "#ffffff",

    "cScale4": "#6366f1",
    "cScaleLabel4": "#ffffff",

    "nodeBorder": "#cbd5e1",
    "clusterBkg": "#f8fafc"
  }
}
```

## Enfin, il nous faut un script Python {#finally-we-need-a-python-script}

Nous avons notre image Docker, nous avons nos fichiers de configuration. Il ne reste plus qu'à écrire un script Python qui va faire « quelque chose », générer du pseudocode Mermaid et rendre ce pseudocode sous forme d'image.

Que peut-on faire ? Tout ce que vous voulez ! Utilisez simplement votre moteur d'IA préféré et demandez-lui de créer le script Python correspondant.

Essayons deux choses :

### Nous allons lire un compose.yaml Docker et créer une mindmap {#well-read-a-docker-composeyaml-and-create-a-mindmap}

Le script ci-dessous va lire un fichier `compose.yaml` et générer une mindmap visuelle. *Il existe aussi un outil prêt à l'emploi pour ça, voir [How to generate a graph from compose.yaml](/blog/docker-compose-viz), mais écrire le script nous-mêmes nous donne une liberté totale sur la sortie.*

**mindmap.py**

```python
import yaml
import argparse
import subprocess
import os

def get_emoji(name):
    name = name.lower()
    if any(x in name for x in ['db', 'postgres', 'sql', 'redis', 'cache']):
        return "🗄️"
    if any(x in name for x in ['nginx', 'proxy', 'gateway', 'api']):
        return "🌐"
    if 'auth' in name:
        return "🔐"
    return "📦"

def generate_mindmap(compose_path):
    with open(compose_path, 'r') as f:
        data = yaml.safe_load(f)

    lines = ["mindmap", "  root(( 🏗️ INFRASTRUCTURE ))"]

    lines.append("    Services")
    for name, config in data.get('services', {}).items():
        emoji = get_emoji(name)
        lines.append(f"      ({emoji} {name})")

        if 'depends_on' in config:
            for dep in config['depends_on']:
                lines.append(f"        {{{{ 🔗 {dep} }}}}")

    lines.append("    Networks")
    for net in data.get('networks', {}).keys():
        lines.append(f"      ) {net} (")

    lines.append("    Volumes")
    for vol in data.get('volumes', {}).keys():
        lines.append(f"      [{vol}]")

    return "\n".join(lines)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("input", help="compose.yaml file")
    parser.add_argument("-o", "--output", default="infra_map.png")
    parser.add_argument("--style", default="/app/mermaid-config.json")
    args = parser.parse_args()

    mmd_code = generate_mindmap(args.input)
    with open("temp.mmd", "w") as f:
        f.write(mmd_code)

    print("🚀 Generating Pro diagram...")

    cmd = [
        "mmdc",
        "-i", "temp.mmd",
        "-o", args.output,
        "--width", "1600",
        "-p", "/app/puppeteer-config.json",
        "--backgroundColor", "transparent"
    ]

    if os.path.exists(args.style):
        cmd.extend(["-c", args.style])

    subprocess.run(cmd, check=True)
    os.remove("temp.mmd")
    print(f"✨ Done! Image: {args.output}")

if __name__ == "__main__":
    main()
```

Voici le `compose.yaml` bidon que je vais utiliser comme exemple :

**compose.yaml**

```yaml
services:
  nginx-proxy:
    image: nginx:alpine
    ports: ["80:80", "443:443"]
    networks: [frontend]
    volumes: ["./nginx.conf:/etc/nginx/nginx.conf"]

  api-gateway:
    build: ./services/gateway
    depends_on: [auth-service, user-service]
    networks: [frontend, backend]
    environment:
      - DB_URL=postgres://db:5432/app

  auth-service:
    image: node:18
    networks: [backend]
    volumes: ["auth_data:/data"]

  user-service:
    image: python:3.11
    networks: [backend, database]
    environment:
      - REDIS_URL=redis:6379

  db-primary:
    image: postgres:15
    networks: [database]
    volumes: ["pg_data:/var/lib/postgresql/data"]
    environment:
      - POSTGRES_PASSWORD=secret

  redis-cache:
    image: redis:alpine
    networks: [database]

networks:
  frontend:
  backend:
  database:
    driver: bridge

volumes:
  pg_data:
  auth_data:
```

Maintenant, le meilleur moment : lancez cette commande pour produire l'image :

```bash
$ docker run --rm -u $(id -u):$(id -g) -e HOME=/tmp -v .:/app yourself/docker-python-mermaid python3 /app/scripts/mindmap.py /app/samples/compose.yaml -o /app/compose.png

Generating single mermaid chart

🚀 Generating Pro diagram...
✨ Done! Image: /app/compose.png
```

Une seule ligne de commande produit la mindmap montrée en haut de cet article. Cette image a été générée par le script Python `mindmap.py` après traitement de notre fichier `compose.yaml`. Nous pouvons maintenant modifier notre fichier YAML, relancer la commande `docker run`, et l'image sera mise à jour automatiquement.

> **Couleurs:**
>
> Souvenez-vous : il suffit de modifier le fichier `mermaid-config.json` si vous voulez une autre palette de couleurs.

## Plus d'exemples {#more-examples}

### Un autre exemple {#another-example}

Ce script va examiner notre structure de fichiers, identifier les fichiers présents et créer une mindmap également :

**project-dna.py**

```python
import os
import argparse
import subprocess

def get_category(filename):
    ext = os.path.splitext(filename)[1].lower()
    name = filename.lower()

    if name == 'dockerfile' or ext == '.dockerfile': return '🐳 Docker'
    if ext in ['.py', '.sh', '.js', '.go']: return '💻 Code'
    if ext in ['.yaml', '.yml', '.json', '.conf', '.ini']: return '⚙️ Config'
    if ext in ['.md', '.txt', '.pdf']: return '📝 Doc'
    if ext in ['.png', '.jpg', '.svg', '.ico']: return '🎨 Assets'
    return '📄 Other'

def generate_dna(path):
    structure = {}

    # Analyze files
    for root, dirs, files in os.walk(path):
        # Ignore hidden and heavy folders
        dirs[:] = [d for d in dirs if not d.startswith('.') and d != '__pycache__']

        for file in files:
            cat = get_category(file)
            structure.setdefault(cat, set()).add(file)

    if not structure:
        return "mindmap\n  root(( 🧬 PROJECT DNA ))\n    (Empty or unreadable folder)"

    lines = ["mindmap", "  root(( 🧬 PROJECT DNA ))"]

    for cat, files in structure.items():
        lines.append(f"    {cat}")
        # Show max 6 files per category to avoid cluttering the image
        for i, f in enumerate(sorted(list(files))):
            if i >= 6:
                lines.append("      (...)")
                break
            # Clean special characters for Mermaid
            clean_name = f.replace('(', '').replace(')', '').replace('[', '').replace(']', '')
            lines.append(f"      ({clean_name})")

    return "\n".join(lines)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("path", help="Folder to scan")
    parser.add_argument("-o", "--output", default="dna.png")
    args = parser.parse_args()

    # Ensure the path is absolute
    target_path = os.path.abspath(args.path)
    mmd_code = generate_dna(target_path)

    with open("temp.mmd", "w", encoding='utf-8') as f:
        f.write(mmd_code)

    print(f"🔎 Analyzing {target_path}...")

    subprocess.run([
        "mmdc", "-i", "temp.mmd", "-o", args.output,
        "-p", "/app/puppeteer-config.json",
        "-c", "/app/mermaid-config.json",
        "--width", "1200"
    ], check=True)

    os.remove("temp.mmd")
    print(f"✨ DNA successfully generated in {args.output}")

if __name__ == "__main__":
    main()
```

```bash
$ docker run --rm -u $(id -u):$(id -g) -e HOME=/tmp -v .:/app yourself/docker-python-mermaid python3 /app/scripts/project-dna.py /app -o /app/project-dna.png

Generating single mermaid chart
🔎 Analyzing /app...
✨ DNA successfully generated in /app/project-dna.png
```

![La mindmap project-dna](./files/images/project-dna.webp)

Cette mindmap ne se contente pas de montrer nos dossiers : elle regroupe les fichiers dans des catégories comme `Code`, `Configuration`, `Assets`, etc.

### Dernier exemple {#last-example}

Cette fois, un camembert :

**pie.py**

```python
import os
import argparse
import subprocess

def get_category(filename):
    ext = os.path.splitext(filename)[1].lower()
    name = filename.lower()

    if name == 'dockerfile' or ext == '.dockerfile': return '🐳 Docker'
    if ext in ['.py', '.sh', '.js', '.go']: return '💻 Code'
    if ext in ['.yaml', '.yml', '.json', '.conf', '.ini']: return '⚙️ Config'
    if ext in ['.md', '.txt', '.pdf']: return '📝 Doc'
    if ext in ['.png', '.jpg', '.svg', '.ico']: return '🎨 Assets'
    return '📄 Other'

def generate_pie_chart(path):
    counts = {}

    # Analyze files
    for root, dirs, files in os.walk(path):
        # Ignore hidden and heavy folders
        dirs[:] = [d for d in dirs if not d.startswith('.') and d != '__pycache__']

        for file in files:
            cat = get_category(file)
            counts[cat] = counts.get(cat, 0) + 1

    if not counts:
        return "pie title Empty or unreadable folder\n    \"None\" : 1"

    lines = ["pie title Project File Type Distribution"]

    for cat, count in counts.items():
        # Mermaid pie charts use raw numbers and automatically calculate percentages
        lines.append(f'    "{cat}" : {count}')

    return "\n".join(lines)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("path", help="Folder to scan")
    parser.add_argument("-o", "--output", default="file_types_pie.png")
    args = parser.parse_args()

    # Ensure the path is absolute
    target_path = os.path.abspath(args.path)
    mmd_code = generate_pie_chart(target_path)

    with open("temp.mmd", "w", encoding='utf-8') as f:
        f.write(mmd_code)

    print(f"🔎 Analyzing {target_path}...")

    subprocess.run([
        "mmdc", "-i", "temp.mmd", "-o", args.output,
        "-p", "/app/puppeteer-config.json",
        "-c", "/app/mermaid-config.json",
        "--width", "1200",
        "--backgroundColor", "transparent"
    ], check=True)

    os.remove("temp.mmd")
    print(f"✨ Pie chart successfully generated in {args.output}")

if __name__ == "__main__":
    main()
```

```bash
$ docker run --rm -u $(id -u):$(id -g) -e HOME=/tmp -v .:/app yourself/docker-python-mermaid python3 /app/scripts/pie.py /app -o /app/pie.png

Generating single mermaid chart
🔎 Analyzing /app...
✨ Pie chart successfully generated in /app/pie.png
```

![Le camembert project-dna](./files/images/pie.webp)

## Tous les fichiers nécessaires {#all-needed-files}

**Project: /tmp/mermaid**

> **Note:**
>
> Maintenant, lancez 'docker build -t yourself/docker-python-mermaid .' pour
> créer l'image Docker.

**Dockerfile**

```docker
FROM node:20-alpine

RUN apk add --no-cache \
    chromium \
    nss \
    freetype \
    harfbuzz \
    ca-certificates \
    ttf-freefont \
    font-noto-emoji \
    python3 \
    py3-pip \
    py3-yaml

RUN fc-cache -fv

RUN npm install -g @mermaid-js/mermaid-cli

RUN mkdir -p /home/node/.cache/puppeteer && \
    chmod -R 777 /home/node

ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
ENV HOME=/home/node

WORKDIR /app
```

**puppeteer-config.json**

```json
{
  "args": [
    "--no-sandbox",
    "--disable-setuid-sandbox",
    "--disable-dev-shm-usage",
    "--disable-accelerated-2d-canvas",
    "--no-first-run",
    "--no-zygote",
    "--disable-gpu"
  ]
}
```

**mermaid-config.json**

```json
{
  "theme": "base",
  "themeVariables": {
    "fontFamily": "Inter, Segoe UI, Arial",
    "fontSize": "16px",
    "lineColor": "#475569",
    "mainBkg": "#ffffff",

    "cScale0": "#0f172a",
    "cScaleLabel0": "#ffffff",

    "cScale1": "#2563eb",
    "cScaleLabel1": "#ffffff",

    "cScale2": "#059669",
    "cScaleLabel2": "#ffffff",

    "cScale3": "#d97706",
    "cScaleLabel3": "#ffffff",

    "cScale4": "#6366f1",
    "cScaleLabel4": "#ffffff",

    "nodeBorder": "#cbd5e1",
    "clusterBkg": "#f8fafc"
  }
}
```

**scripts/mindmap.py**

```python
import yaml
import argparse
import subprocess
import os

def get_emoji(name):
    name = name.lower()
    if any(x in name for x in ['db', 'postgres', 'sql', 'redis', 'cache']):
        return "🗄️"
    if any(x in name for x in ['nginx', 'proxy', 'gateway', 'api']):
        return "🌐"
    if 'auth' in name:
        return "🔐"
    return "📦"

def generate_mindmap(compose_path):
    with open(compose_path, 'r') as f:
        data = yaml.safe_load(f)

    lines = ["mindmap", "  root(( 🏗️ INFRASTRUCTURE ))"]

    lines.append("    Services")
    for name, config in data.get('services', {}).items():
        emoji = get_emoji(name)
        lines.append(f"      ({emoji} {name})")

        if 'depends_on' in config:
            for dep in config['depends_on']:
                lines.append(f"        {{{{ 🔗 {dep} }}}}")

    lines.append("    Networks")
    for net in data.get('networks', {}).keys():
        lines.append(f"      ) {net} (")

    lines.append("    Volumes")
    for vol in data.get('volumes', {}).keys():
        lines.append(f"      [{vol}]")

    return "\n".join(lines)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("input", help="compose.yaml file")
    parser.add_argument("-o", "--output", default="infra_map.png")
    parser.add_argument("--style", default="/app/mermaid-config.json")
    args = parser.parse_args()

    mmd_code = generate_mindmap(args.input)
    with open("temp.mmd", "w") as f:
        f.write(mmd_code)

    print("🚀 Generating Pro diagram...")

    cmd = [
        "mmdc",
        "-i", "temp.mmd",
        "-o", args.output,
        "--width", "1600",
        "-p", "/app/puppeteer-config.json",
        "--backgroundColor", "transparent"
    ]

    if os.path.exists(args.style):
        cmd.extend(["-c", args.style])

    subprocess.run(cmd, check=True)
    os.remove("temp.mmd")
    print(f"✨ Done! Image: {args.output}")

if __name__ == "__main__":
    main()
```

**scripts/pie.py**

```python
import os
import argparse
import subprocess

def get_category(filename):
    ext = os.path.splitext(filename)[1].lower()
    name = filename.lower()

    if name == 'dockerfile' or ext == '.dockerfile': return '🐳 Docker'
    if ext in ['.py', '.sh', '.js', '.go']: return '💻 Code'
    if ext in ['.yaml', '.yml', '.json', '.conf', '.ini']: return '⚙️ Config'
    if ext in ['.md', '.txt', '.pdf']: return '📝 Doc'
    if ext in ['.png', '.jpg', '.svg', '.ico']: return '🎨 Assets'
    return '📄 Other'

def generate_pie_chart(path):
    counts = {}

    # Analyze files
    for root, dirs, files in os.walk(path):
        # Ignore hidden and heavy folders
        dirs[:] = [d for d in dirs if not d.startswith('.') and d != '__pycache__']

        for file in files:
            cat = get_category(file)
            counts[cat] = counts.get(cat, 0) + 1

    if not counts:
        return "pie title Empty or unreadable folder\n    \"None\" : 1"

    lines = ["pie title Project File Type Distribution"]

    for cat, count in counts.items():
        # Mermaid pie charts use raw numbers and automatically calculate percentages
        lines.append(f'    "{cat}" : {count}')

    return "\n".join(lines)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("path", help="Folder to scan")
    parser.add_argument("-o", "--output", default="file_types_pie.png")
    args = parser.parse_args()

    # Ensure the path is absolute
    target_path = os.path.abspath(args.path)
    mmd_code = generate_pie_chart(target_path)

    with open("temp.mmd", "w", encoding='utf-8') as f:
        f.write(mmd_code)

    print(f"🔎 Analyzing {target_path}...")

    subprocess.run([
        "mmdc", "-i", "temp.mmd", "-o", args.output,
        "-p", "/app/puppeteer-config.json",
        "-c", "/app/mermaid-config.json",
        "--width", "1200",
        "--backgroundColor", "transparent"
    ], check=True)

    os.remove("temp.mmd")
    print(f"✨ Pie chart successfully generated in {args.output}")

if __name__ == "__main__":
    main()
```

**samples/compose.yaml**

```yaml
services:
  nginx-proxy:
    image: nginx:alpine
    ports: ["80:80", "443:443"]
    networks: [frontend]
    volumes: ["./nginx.conf:/etc/nginx/nginx.conf"]

  api-gateway:
    build: ./services/gateway
    depends_on: [auth-service, user-service]
    networks: [frontend, backend]
    environment:
      - DB_URL=postgres://db:5432/app

  auth-service:
    image: node:18
    networks: [backend]
    volumes: ["auth_data:/data"]

  user-service:
    image: python:3.11
    networks: [backend, database]
    environment:
      - REDIS_URL=redis:6379

  db-primary:
    image: postgres:15
    networks: [database]
    volumes: ["pg_data:/var/lib/postgresql/data"]
    environment:
      - POSTGRES_PASSWORD=secret

  redis-cache:
    image: redis:alpine
    networks: [database]

networks:
  frontend:
  backend:
  database:
    driver: bridge

volumes:
  pg_data:
  auth_data:
```

**scripts/project-dna.py**

```python
import os
import argparse
import subprocess

def get_category(filename):
    ext = os.path.splitext(filename)[1].lower()
    name = filename.lower()

    if name == 'dockerfile' or ext == '.dockerfile': return '🐳 Docker'
    if ext in ['.py', '.sh', '.js', '.go']: return '💻 Code'
    if ext in ['.yaml', '.yml', '.json', '.conf', '.ini']: return '⚙️ Config'
    if ext in ['.md', '.txt', '.pdf']: return '📝 Doc'
    if ext in ['.png', '.jpg', '.svg', '.ico']: return '🎨 Assets'
    return '📄 Other'

def generate_dna(path):
    structure = {}

    # Analyze files
    for root, dirs, files in os.walk(path):
        # Ignore hidden and heavy folders
        dirs[:] = [d for d in dirs if not d.startswith('.') and d != '__pycache__']

        for file in files:
            cat = get_category(file)
            structure.setdefault(cat, set()).add(file)

    if not structure:
        return "mindmap\n  root(( 🧬 PROJECT DNA ))\n    (Empty or unreadable folder)"

    lines = ["mindmap", "  root(( 🧬 PROJECT DNA ))"]

    for cat, files in structure.items():
        lines.append(f"    {cat}")
        # Show max 6 files per category to avoid cluttering the image
        for i, f in enumerate(sorted(list(files))):
            if i >= 6:
                lines.append("      (...)")
                break
            # Clean special characters for Mermaid
            clean_name = f.replace('(', '').replace(')', '').replace('[', '').replace(']', '')
            lines.append(f"      ({clean_name})")

    return "\n".join(lines)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("path", help="Folder to scan")
    parser.add_argument("-o", "--output", default="dna.png")
    args = parser.parse_args()

    # Ensure the path is absolute
    target_path = os.path.abspath(args.path)
    mmd_code = generate_dna(target_path)

    with open("temp.mmd", "w", encoding='utf-8') as f:
        f.write(mmd_code)

    print(f"🔎 Analyzing {target_path}...")

    subprocess.run([
        "mmdc", "-i", "temp.mmd", "-o", args.output,
        "-p", "/app/puppeteer-config.json",
        "-c", "/app/mermaid-config.json",
        "--width", "1200"
    ], check=True)

    os.remove("temp.mmd")
    print(f"✨ DNA successfully generated in {args.output}")

if __name__ == "__main__":
    main()
```

## Conclusion {#conclusion}

En combinant la puissance de parsing de Python avec l'élégance de rendu de Mermaid, nous avons transformé une documentation statique en un actif dynamique.

Si l'approche *Documentation as Code* vous plaît, jetez un œil à deux autres de mes articles qui vont dans la même direction : [Docker - Diagrams as code](/blog/docker-diagram-as-code) et [Python - Generate flows using pydot](/blog/python-pydot). Et quand il n'y a aucun parser à écrire — une phrase qui décrit un processus, un format de config pour lequel je n'ai pas de script — [ai-diagram](/blog/ollama-ai-diagram) demande le Mermaid à un LLM local.
