### Introduction

Cet article détaille le changement de technologie opéré en 2023 pour mon site web, et s'adresse à un public possédant certaines notions du développement web comme le HTML et l'hébergement, ainsi que les fondamentaux des systèmes _\*nix_ (GNU/Linux, Unix).

#### Historique

La première version de mon site, qui possède par ailleurs [son propre article](/projets/static_site/content.html), souffre de quelques lacunes techniques qui m'ont poussé à engager une refonte.

En voici un rapide aperçu.

##### Adhérence technologique

Des traces de PHP sont utilisées pour factoriser des composants. J'impose un environnement d'exécution PHP pour le serveur d'hébergement alors que j'en ai une utilisation très partielle.

##### Complexité d'écriture

Je dois écrire le contenu en code bas niveau, en HTML ou PHP. De plus, certaines tâches sont manuelles. Par exemple, à chaque fois que je créée une nouvelle page pour un projet, je dois ensuite l'ajouter dans la page listant tous les projets.

##### Déploiement maison

Déploiement à base de scripts *shell* maison non standards, dont la compréhension, l'utilisation et la maintenance s'avéraient fastidieuses.

### Planification

J'ai donc décidé de refondre mon site afin de résoudre ces points de friction.

#### Besoins

Mon désir est d'avoir une solution **simple**, **efficace** et **durable**.

Mon principal objectif est de faciliter la production de contenus sur mon site. Je suis un adepte du *Markdown* : j'utilise [QOwnNotes](https://www.qownnotes.org/) pour prendre mes notes personnelles et professionnelles. J'admire sa simplicité et ses fonctionnalités, j'y suis familier, et je le juge tout à fait apte pour formater mon futur contenu.

J'avais également envie d'une solution **élégante** et **techniquement intéressante**, basée sur du **logiciel libre** dont je souhaite promouvoir les valeurs et l'usage.

#### Veille technologique

J'ai donc réalisé quelques recherches pour me documenter sur les solutions libres existantes qui pourraient répondre à mon besoin.

##### CMS

CMS signifie _Content Management System_ : les CMS sont architecturés pour prendre en charge tous les besoins techniques d'édition de contenu avancée (authentification, droits, rédaction, publication, présentation) tout en les présentant de manière accessible via une interface graphique. Une des solutions libres les plus connues est [Wordpress](https://wordpress.org/download/), mais nous pouvons aussi citer [Wagtail](https://wagtail.org/features/).

J'ai hésité à utiliser un CMS pour m'épargner la tâche de structuration du contenu, mais plusieurs points m'ont amené à délaisser cette option : la difficulté de la migration, la complexité d'une IHM de gestion et la difficulté d'hébergement. J'avais envie d'une solution plus sobre.

##### Générateurs de site statiques

J'ai trouvé la notion de site statique très intéressante pour sa simplicité et sa sobriété. Le principe est d'avoir un service minimal côté serveur : les pages sont stockées au format brut HTML et seront distribuées telles quelles au clients webs. Cela facilite la gestion de l'hébergement, et augmente par ailleurs sa performance car aucune tâche n'est réalisée entre la réception de la requête et l'envoi de la réponse.

La conséquence de cela est que le contenu ne peut pas être interactif : une fois publié, il ne peut être modifié seulement par un nouveau déploiement.

L'inconvénient est qu'il faut produire ces pages au format brut. Afin de faciliter et d'industrialiser cette tâche, des outils de génération de contenus statiques sont apparus. On peut citer par exemple [Jekyll](https://jekyllrb.com/), [HUGO](https://gohugo.io/) ou bien encore [Grav](https://getgrav.org/features).

Ces générateurs fournissent d'excellentes solutions et conviennent tout à fait à mon besoin, et je vous conseille de les utiliser si vous souhaitez faire quelque chose de sérieux.

##### Revenir aux bases

Je désirais cependant un peu plus de contrôle sur le processus de création, j'ai donc décidé d'explorer une voie un peu plus expérimentale et fondamentale.

J'ai eu en effet l'idée d'associer deux technologies différentes pour satisfaire mes besoins tout en me permettant de progresser techniquement.

La première s'appelle [Pandoc](https://pandoc.org), qui constitue une solution libre très connue pour convertir des documents. Je l'utilise notamment pour convertir mes notes prises en [Markdown](https://www.markdownguide.org/cheat-sheet) vers des langages de formatage compris par certains outils que j'utilise, comme les formats *dokuwiki* ou *textile*. Ce qui m'intéresse dans pandoc, c'est qu'il fonctionne très bien et sa responsabilité est simple : il convertit des documents. La conversion qui m'intéresse est celle qui permet d'obtenir des documents HTML à partir de documents Markdown.

La seconde s'appelle [GNU make](https://www.gnu.org/software/make) développée à la base pour gérer la compilation de programmes en C. J'en connais les bases suffisantes pour compiler mes projets en C++. GNU make prolonge l'idéologie des systèmes _\*nix_ qui prône que tout est fichier. Les fichiers `makefile` décrivent des recettes de fabrication en détaillant les relations entre les fichiers. J'y trouve là une solution élégante de développer un système de compilation tout en documentant la structure de mon projet.

### Refonte

J'ai donc adopté Pandoc et Make pour propulser mon site web. Je vous guide pas à pas dans la mise en place de ces composants, en détaillant pour chaque problème rencontré la solution que j'ai mise en place.

#### Pandoc

Comment générer des pages HTML complètes à partir d'un article rédigé en markdown ?

![carte des flux permettant a Pandoc de construire un fichier](PandocEngine.svg)

##### Markdown en HTML

Prenons un exemple de base, dans lequel je décris un contenu simple sans contrainte technique.

```md
# Titre 1

Bonjour **Markdown** ! Je parle de toi sur [mon site](www.jovian-hersemeule.eu).
```

Pour pouvoir le transformer en HTML, je peux invoquer pandoc avec le minimum d'arguments :

```bash
pandoc -o sortie.html entree.md
```

```html
<h1 id="titre-1">Titre 1</h1>
<p>Bonjour <strong>Markdown</strong> ! Je parle de toi sur <a
href="www.jovian-hersemeule.eu">mon site</a>.</p>
```

Le résultat est celui attendu : pandoc réalise fidèlement son travail de conversion. On notera que pandoc ajoute automatiquement un identifiant `id` à chaque titre.

La syntaxe comprise par pandoc est un sur-ensemble du markdown, dont la description est disponible sur la [documentation officielle](https://pandoc.org/MANUAL.html#pandocs-markdown).

Si vous souhaitez désactiver le retour à la ligne automatique, vous pouvez utilisez l'option `--wrap=none`.

##### HTML indépendant

L'exemple précédent produit un document HTML trop simple pour constituer une page web. On voudrait en effet entourer notre contenu avec des éléments de navigation, comme un menu d'en-tête et un pied de page.

Le fichier HTML produit est pour l'instant insuffisant pour être affiché en tant que page web pour le navigateur. En effet, le standard impose de caractériser la page, en utilisant les balises `<!DOCTYPE html>` pour préciser la version 5 du standard et la balise `<head>` qui peut contenir toutes sortes de paramètres. Cette encapsulation permet aussi de pouvoir styliser le contenu avec du CSS et d'ajouter des métadonnées pour caractériser notre contenu.

Pandoc a prévu le coup, et propose propose une option `--standalone` pour rendre notre document indépendant, c'est à dire lisible directement depuis un navigateur web.

```bash
pandoc --standalone -o sortie.html entree.md
```

```html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
  <meta charset="utf-8" />
  <meta name="generator" content="pandoc" />
  <!-- passage coupé pour éviter la surcharge -->
</head>
<body>
<h1 id="titre-1">Titre 1</h1>
<p>Bonjour <strong>Markdown</strong> ! Je parle de toi sur <a
href="www.jovian-hersemeule.eu">mon site</a>.</p>
</body>
</html>
```

On remarque ainsi que pandoc utilise un modèle pour rendre notre document auto-suffisant.

##### Templating

Pour contrôler le code ajouté autour de notre document pour le rendre indépendant, Pandoc propose un [système de modèle](https://pandoc.org/MANUAL.html#templates).

Il reste maintenant à personnaliser ce modèle pour pouvoir lui ajouter notre style et nos menus de navigation. Pandoc assure et nous met à disposition [une syntaxe](https://pandoc.org/MANUAL.html#template-syntax) pour écrire nos propres templates. En écrivant un template `mon-template.html` :

```bash
pandoc --template mon-template.html -o mieux.html entree.md
```

Remarquez que l'option `--template` implique l'option `--standalone`, que l'on peut donc retirer.

Autre avantage de cette fonctionnalité modèle, c'est qu'on peut aussi l'utiliser sur des pages que l'on souhaite garder en HTML. C'est par exemple le cas de ma page CV où j'ai fait des efforts de présentation pour la rendre adaptable à toute taille d'écran. Il suffit de supprimer les décorations du document HTML pour ne laisser que l'essentiel, et laisser pandoc rajouter les décorations :

```bash
pandoc --template mon-template.html -o encore.html deja.html
```

Attention cependant, pandoc n'est pas un moteur de template : la conversion HTML → HTML n'est pas idem-potente. En effet, pandoc utilise en interne une représentation intermédiaire appelée AST (Abstract Syntax Tree) qui sert d'aiguillage entre tous les formats. Nous avons déjà vu que le HTML rajoutait des identifiants inoffensifs, mais des légendes sont parfois ajoutées aux images et les classes disparaissent des balises qui ne sont pas des `<div>`.

Exemple de fichier source en HTML : `src.html`

```html
<h1>Bonjour HTML</h1>
<img src="pingouin.jpg" alt="Un magnifique pingouin">
<p>
Le <b class="magnifique">pingouin</b> est un superbe animal.
</p>
```

Conversion depuis et vers l'HTML :

```bash
pandoc -o different.html src.html
```

Le fichier `different.html` obtenu :

```html
<h1 id="hello-html">Hello HTML</h1>
<p><img src="pingouin.jpg" alt="Un magnifique pingouin" /></p>
<p>Le <strong>pingouin</strong> est un superbe animal.</p>
```

##### Les métadonnées

Les plus observateur·ices d'entre vous auront peut-être remarqué que l'attribut `lang` de la balise `html` était vide dans le code produit dans la version auto-contenue du document HTML.

```html
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
```

En effet, pandoc implémente un système de méta-données qui est nécessaire pour piloter le rendu du template.

Voici à quoi ressemble la ligne en question dans le template :

```html
<html xmlns="http://www.w3.org/1999/xhtml" lang="$lang$" xml:lang="$lang$"$if(dir)$ dir="$dir$"$endif$>
```

Ainsi, si je précise la métadonnée manquante :

```bash
pandoc --metadata=lang:fr --standalone -o avec-lang.html entree.md
```

Cela donne :

```html
<html xmlns="http://www.w3.org/1999/xhtml" lang="fr" xml:lang="fr">
```

Ce système de métadonnées me sera très précieux pour personnaliser les pages.

Avec l'ancienne technologie, j'instanciais une variable PHP avant d'inclure un fragment :

```php
<?php
    $title = "Contact";
    include("include/head.php");
?>
```

Avec pandoc, la variable `$title` pourra être convertie en une métadonnée qui sera utilisée dans le template.

Le système de templating de pandoc est puissant tout en restant simple. Il est notamment possible de découper son template en [parties](https://pandoc.org/MANUAL.html#partials), d'appliquer des [conditions](https://pandoc.org/MANUAL.html#conditionals), et de réaliser des [formatages simples](https://pandoc.org/MANUAL.html#pipes).

Pour finir, afin de faciliter l'usage de plusieurs métadonnées, il est possible de les rajouter directement dans le fichier Markdown ou dans un fichier dédié :

```yml
author: Jovian HERSEMEULE
title: Bienvenue !
img:
        share:
                path: "/include/images/avatar-cartoon600.png"
                alt: Jovian avatar cartoon
description: Le site officiel de Jovian HERSEMEULE.
lang: fr

# Technical
version: development
date: 2023-06-18
css:
        - "/include/css/bootstrap.css"
        - "/include/css/custom/main.css"
```

On nommant ce fichier `meta.yml`, il peut être référencé via la ligne de commande :

```bash
pandoc --metadata-file=meta.yml --standalone -o avec-fichier-meta.html entree.md
```

Les arguments `metadata-file` et `metadata` peuvent être combinés ; cela permettra d'avoir d'une part des données statiques provenant de fichiers, comme le titre ou l'auteur, et d'autre part avoir des données calculées passées à la ligne de commande, comme la date ou la version.

Je fais un usage avancé des métadonnées pour ce nouveau site, ce qui sera détaillé dans la partie suivante dédiée à la construction.

#### GNU Make

Comment orchestrer la génération successive des fichiers pour aboutir à un site entier ?

![graphe des dependances Make](StaticMakeDependencyGraph.svg)

##### La philosophie de Make

GNU make est un utilitaire d'aide à la compilation qui fonctionne à partir d'un `makefile`, un document de recettes qui décrit chaque fichier, comment le construire et quelles sont ses dépendances.

Si Make est nouveau pour vous, n'hésitez pas à consulter [cette courte introduction](https://dev.to/woile/digesting-make-and-makefiles-394m) qui vous permettra de vous familiariser rapidement avec le concept de base.

Les prochaines parties vous renseigneront en même temps sur la structure de mon projet et sur mon usage de make.

##### Convertir un fichier source

Tous les fichiers sources du projet sont dans le dossier `src`. Les fichiers qui portent le contenu texte au format source possèdent la particule `src` dans leur nom.

Exemples de fichiers de contenu :

```txt
src/index.src.html
src/cv/content.src.html
src/projets/pandoc_site/content.src.md
```

Peu importe le radical du fichier, mais comme mon arborescence est suffisamment fine, je peux me permettre de les appeler tous `content`.

J'ai donc écrit une recette pour appliquer pandoc sur ces fichiers source, en décidant d'utiliser le suffixe `.gen.html` pour les fichiers produits.

```makefile
PANDOC_GEN := pandoc --wrap=none --template src/templates/main.html --metadata-file src/meta.yml

%.gen.html: %.src.md
    $(PANDOC_GEN) --output $@ $<

%.gen.html: %.src.html
    $(PANDOC_GEN) --output $@ $<
```

Ce qui donne en pratique :

```bash
make src/cv/content.gen.html
```

```bash
pandoc --wrap=none --template src/templates/main.html --metadata-file src/meta.yml --output src/cv/content.gen.html src/cv/content.src.html
```

On obtient donc les sorties dérivées des fichiers sources.

- `content.src.md` -> `content.gen.html`
- `content.src.html` -> `content.gen.html`

##### Produire tous les fichiers sources

Maintenant que Make connaît la relation entre un fichier source et sa version compilée, je vais créer une recette permettant de générer tous les fichiers avec une instruction `build`.

```makefile
.PHONY: build
build: $(GEN_CONTENT_FILES)
    $(info HTML generated)
```

Que je pourrai appeler de la manière suivante pour compiler toutes mes sources :

```bash
make build
```

J'ai utilisé plusieurs variables successives avant d'aboutir au calcul de la liste de tous les fichiers à générer contenue dans `GEN_CONTENT_FILES` :

```makefile
SRC_HTMLS := $(shell find src -name '*.src.html')
SRC_MDS := $(shell find src -name '*.src.md')

GEN_HTMLS := $(SRC_HTMLS:%.src.html=%.gen.html)
GEN_MDS := $(SRC_MDS:%.src.md=%.gen.html)
GEN_CONTENT_FILES := $(GEN_HTMLS) $(GEN_MDS)
```

J'utilise la commande shell `find` pour trouver les fichiers sources et la mécanique de translation de nom native à *GNU Make* pour en déduire la liste des noms à générer. Ce que va comprendre `Make` sera :

```makefile
SRC_HTMLS := src/index.src.html src/cv/content.src.html
SRC_MDS := src/projets/pandoc_site/content.src.md

GEN_HTMLS := src/index.gen.html src/cv/content.gen.html
GEN_MDS := src/projets/pandoc_site/content.gen.html
GEN_CONTENT_FILES := src/index.gen.html src/cv/content.gen.html src/projets/pandoc_site/content.gen.html
```

La cible `build` a donc en dépendance tous les fichiers `*.gen.html` qui peuvent être construits, et utilisera les deux recettes présentées dans la partie précédente pour les générer s'ils sont absents ou si la source est plus récente que le fichier généré.

J'ai également créé une recette pour pouvoir nettoyer mon répertoire :

```makefile
.PHONY: clean
clean:
    rm -f $(GEN_FILES)
```

Et j'ai rajouté les fichiers produits dans mon `.gitignore` pour éviter de les versionner.

```gitignore
*.gen.html
```

##### Produire un dossier distribuable

Pour l'instant, chaque fichier dérivé `*.gen.html` est généré dans le même dossier que son fichier source `*.gen.*` associé. Cela peut être pratique pour comparer la source et son équivalent généré, mais cela ne convient pas pour un déploiement. Le besoin est donc de créer un dossier contenant tous les fichiers à déployer, mais seulement les fichiers à déployer.

```makefile
.PHONY: install
install: $(DIST_DIR)
    $(info Distributed folder generated)

$(DIST_DIR): $(DIST_FILES)

$(DIST_DIR)/%.html: src/%.gen.html
    mkdir --parents $(dir $@)
    cp $< $@
```

Ces recettes accomplissent deux objectifs :

- Copier les fichiers générés dans un dossier ;
- Enlever la particule `gen` du nom du fichier.

Il reste à construire la liste `DIST_FILES` des fichiers à distribuer.

```makefile
DIST_DIR := dist

DIST_GEN_FILES := $(GEN_CONTENT_FILES:src/%.gen.html=$(DIST_DIR)/%.html)
```

Ce qui donne lorsque que l'on invoque `make install` :

- `src/index.gen.html` -> `dist/index.html`
- `src/cv/content.gen.html` -> `dist/cv/content.html`
- `src/projets/pandoc_site/content.gen.html` -> `dist/projets/pandoc_site/content.html`


Le dossier en question sera `dist` par défaut, mais pourra être surchargé pour déployer sur un serveur web local de test par exemple.

```bash
DIST_DIR=/var/www/html make -e install
```

Notez l'usage de l'option `-e` de *make* pour prendre en compte les variables d'environnement afin de surcharger la valeur par défaut présente dans le fichier.

Il ne faut pas oublier que parmi les fichiers à déployer figurent aussi des ressources autres que tu contenu HTML : les fichiers de style *CSS*, les images ou bien les éventuels scripts *JS*.

Pour inclure l'ensemble de ces fichiers, on les déclare en tant que ressources à distribuer :

```makefile
INCLUDED_FILES := $(shell find src/include -type f)
EMBEDDED_FILES := $(shell find src -name '*.png' -or -name '*.svg' -or -name '*.pdf')
DIST_INC_FILES := $(INCLUDED_FILES:src/%=$(DIST_DIR)/%)
DIST_EMB_FILES := $(EMBEDDED_FILES:src/%=$(DIST_DIR)/%)
DIST_FILES := $(DIST_GEN_FILES) $(DIST_INC_FILES) $(DIST_EMB_FILES)
```

Dans mon cas, les ressources partagées comme les scripts *JS* et styles *CSS* sont regroupées dans le dossier `src/include` et listées dans `INCLUDED_FILES`. Alors que les fichiers image peuvent être présents dans le dossier contenant la page dans laquelle ils sont inclus ; la recherche est donc basée sur les extensions de fichier désirées car afin de pouvoir explorer tous les dossiers. Les fichiers ainsi trouvés sont listés dans `EMBEDDED_FILES`.

La variable `DIST_FILES` contient au final la totalité de tous les fichiers distribuables.

Pour que `make` puisse copier ces fichiers sans modification, on rajoute une règle générique :

```makefile
$(DIST_DIR)/%: src/%
    mkdir --parents $(dir $@)
    cp $< $@
```

Et pour terminer, une recette, que certain·es pourraient trouver un peu dangereuse à cause du risque de perte de données, dont l'objectif est de nettoyer ce dossier de distribution :

```makefile
.PHONY: clean-dist
clean-dist:
    rm -rf $(DIST_DIR)/*
```

Et enfin, une recette pour tout nettoyer, à la fois les fichiers générés `*.gen.html` et le dossier de distribution :

```makefile
.PHONY: clean-all
clean-all: clean clean-dist
```

##### Automatiser le déploiement distant

Le déploiement d'un site statique n'est en réalité qu'une copie simple des fichiers générés dans le dossier de distribution vers le dossier de notre hébergeur.

Pas de magie ni d'élégance dans cette partie : j'ai amélioré mon vieux script maison sans utiliser *Make*. Les seules évolutions que j'ai réalisées sur cette partie lors de la refonte est l'utilisation de **sftp** à la place du *ftp* simple. Le **s** signifie sécurisé, et évite que mon mot de passe ftp transite en clair sur le réseau. La seconde évolution consiste à créer automatiquement les nouveaux dossiers, ce qui est important quand on sait que je créée maintenant un dossier par projet.

J'utilise le shell [fish](https://fishshell.com) pour écrire mon script. Voici un extrait qui montre l'agencement des différentes commandes (j'ai omis le contrôle des variables):

```bash
#!/usr/bin/fish

set make_dirs (find $DIST_DIR -type d -printf "mkdir $FTP_DEST/%P\n")
set files (find $DIST_DIR -type f)

set ftp_sync "put -R $DIST_DIR/* $FTP_DEST"
printf %s\n $make_dirs $ftp_sync 'bye' | sftp -P $FTP_PORT $FTP_USER@$FTP_HOST
```

On reconnaît la variable `DIST_DIR` qui était également utilisée dans mon `makefile`. Pour définir automatiquement toutes les variables sans versionner d'informations sensibles, toutes sont regroupées dans un fichier dédié `.env.local` :

```bash
#!/bin/usr/fish
set --export --global DIST_DIR /var/www/html

set --export --global FTP_HOST 'ftp.my-host.fr'
set --export --global FTP_USER 'me'
set --export --global FTP_PORT '2222'
set --export --global FTP_DEST 'www'
```

Pour injecter les variables dans mon environnement courant, j'utilise la commande `source` :

```bash
source .env.local
```

#### YQ

Comment générer la page listant tous les projets ?

En plus de Pandoc et GNU Make, j'ai eu besoin d'une troisième dépendance afin de manipuler programmatiquement les métadonnées stockées dans des fichiers YAML.

![illustration liste des liens vers chaque page projet](ProjectLinkListToDetails.svg)

##### Recenser les projets

Mon besoin est d'avoir une page qui recense tous mes projets. Chaque projet possède un encart avec une description, une illustration et une icône, ainsi qu'un lien vers la page détaillée qui permet d'en savoir plus.

L'objectif serait d'automatiser la découverte des projets et la génération de cette page d'indexation.

##### Description d'une page de projet

Avant toute chose, regardons comment est structuré un projet.

- `src/projets/pandoc_site/` : dossier racine d'un projet
    - `content.src.md` : fichier source décrivant le projet
    - `content.gen.html` : *fichier temporaire généré par pandoc*
    - `content.meta.yml` : métadonnées du projet
    - `pandocHtmlIcon.png` : icône du projet
    - `pandocMakeHtmlLogo.png` : image d'illustration du projet

Le contenu du fichier `content.meta.yml` contient toutes les métadonnées utilisées dans le modèle *Pandoc* qui sont spécifiques à la page associée :

```yaml
---
title: Pandoc HTML
img:
        icon:
                path: "pandocHtmlIcon.png"
                alt: "Pandoc : HTML document with Markdown inside it"
        main:
                path: "pandocMakeHtmlLogo.png"
                alt: "From Markdown to HTML via GNU/Make and Pandoc"
subtitle: Génération statique de mon site web avec GNU/Make et Pandoc.
content-date: 2023-06-05
content-description:
        Article explicatif sur Pandoc piloté par GNU/Make
        pour générer mon site web statique.
...
```

Ces métadonnées du contenu, en plus d'enrichir les métadonnées des documents HTML générés, sont utilisés dans mon modèle personnalisé de pandoc pour générer du contenu visible normalisé :

```html
<main role="main" class="container">
$if(title)$
            <header id="title-block-header" class="focus-center">
                <h1 class="title">$title$</h1>
$if(img.main)$
                <img class="img-fluid rounded" src="$img.main.path$"$if(img.main.alt)$ alt="$img.main.alt$"$endif$>
$elseif(img.icon)$
                <img class="img-fluid rounded" src="$img.icon.path$"$if(img.icon.alt)$ alt="$img.icon.alt$"$endif$>
$endif$
```

Pouvoir parcourir les métadonnées de tous les projets est une bonne piste pour générer automatiquement une page indexant les projets.

##### Itérer sur la liste des projets

Étant donné une liste de projets au format yaml, nous pouvons utiliser les [parties du moteur de template de Pandoc](https://pandoc.org/MANUAL.html#partials) pour l'appliquer à chaque élément de la liste et ainsi formater une liste de projets.

Étant donné un fichier `yaml` listant tous les projets (je l'ai tronqué pour un souci de concision) que l'on pourrait passer en entrée à *Pandoc* via l'argument `--metadata-file` :

```yaml
list:
  - data:
      title: ASCII Space Destroyer
      img: {}
      subtitle: Jeu vidéo réalisé en C++
      content-date: 2015-08-15
      content-description: ASCII description
    target:
      content: content.html
      dir: ascii_space
  - data:
      title: Pandoc HTML
      img: {}
      subtitle: Génération statique de mon site web avec GNU/Make et Pandoc.
      content-date: 2023-06-05
      content-description: Pandoc description
    target:
      content: content.html
      dir: pandoc_site
```

Il est possible d'utiliser les [parties](https://pandoc.org/MANUAL.html#partials) de Pandoc pour itérer sur chaque projet et générer l'aperçu associé. Voici l'extrait de code à ajouter au template principal :

```html
$if(list)$
            <div class="card-columns">
                    $^$${ list:card() }
            </div>
$endif$
```

Il faut alors que le template `card.html` existe et soit dans le même dossier que le modèle principal, l'élément de la liste courant étant accessible via la variable `it` :

```html
<div class="card">
    <img class="card-img-top" src="$it.target.dir$/$it.data.img.main.path$"$if(it.img.main.alt)$ alt="$it.data.img.main.alt$"$endif$>
    <div class="card-body">
        <h4 class="card-title">
            <img class="icon" src="$it.target.dir$/$it.data.img.icon.path$"$if(it.img.icon.alt)$ alt="$it.data.img.icon.alt$"$endif$>
            $it.data.title$
        </h4>
        <p class="card-text">
            $it.data.content-description$
        </p>
        <a href="$it.target.dir$/$it.target.content$" class="btn btn-success btn-block">
            Page du projet
        </a>
    </div>
</div>
```

Côté `makefile`, il faut créer une recette particulière pour ajouter le fichier `list.gen.yml` pour la page listant les projets seulement, sinon les projets seraient listés dans toutes les pages.

```makefile
DIR_OF_PROJECTS := src/projets
GEN_LIST_YML := $(DIR_OF_PROJECTS)/list.gen.yml

$(DIR_OF_PROJECTS)/content.gen.html: $(DIR_OF_PROJECTS)/content.src.html $(DIR_OF_PROJECTS)/content.meta.yml $(GEN_LIST_YML) $(SRC_TEMPLATES)
    $(PANDOC_GEN) --metadata-file $(DIR_OF_PROJECTS)/content.meta.yml --metadata-file $(GEN_LIST_YML) --output $@ $<
```

Si le dossier `list.gen.yml` existe, tout est prêt pour générer automatiquement la page des projets.

```bash
make src/projets/content.gen.html
```

Il reste à déterminer comment construire ce nécessaire fichier `list.gen.yml`.

##### Construire automatiquement la liste de projets

L'enjeu est donc de construire la liste YAML des projets automatiquement à partir des metadonnées de chaque projet pour éviter la double saisie.

J'ai pour cela eu recours à [yq](https://mikefarah.gitbook.io/yq/), qui est un utilitaire qui permet de manipuler les fichiers yaml.

La première étape consiste à simplement aggréger les fichiers yaml présents dans tous les sous-dossiers du dossier `projets` en un seul fichier.

```bash
yq eval-all '. as $item ireduce ([]; . + $item) | {"list": .}' projets/*/*.meta.yml
```

On obtient alors le code yaml suivant dans la sortie standard :

```yaml
list:
  - title: ASCII Space Destroyer
    img: {}
    subtitle: Jeu vidéo réalisé en C++
    content-date: 2015-08-15
    content-description: ASCII description
  - title: Pandoc HTML
    img: {}
    subtitle: Génération statique de mon site web avec GNU/Make et Pandoc.
    content-date: 2023-06-05
    content-description: Pandoc description
```

La deuxième partie fut un peu plus acrobatique et consistait à construire une entrée `target` pour chaque élément afin de construire le lien vers la page du projet concerné. Heureusement, `yq` fournit une variable `filename` contenant le nom du fichier origine du fichier `yaml` en cours de traitement.

```bash
yq eval-all '{"data": ., "target": filename} as $item ireduce ([]; . + $item) | {"list": .}' projets/*/*.meta.yml
```

Vous remarquerez que j'en ai aussi profité pour regrouper les données brutes dans le bloc data :

```yaml
list:
  - data:
      title: ASCII Space Destroyer
      img: {}
      subtitle: ASCII subtitle
      content-date: 2015-08-15
      content-description: ASCII description
    target: ascii_space/content.meta.yml
  - data:
      title: Pandoc HTML
      img: {}
      subtitle: Pandoc subtitle
      content-date: 2023-06-05
      content-description: Pandoc description
    target: pandoc_site/content.meta.yml
```

Il reste maintenant à créer deux entrées dans target : une pour le dossier du projet, une autre pour le nom de fichier de contenu. Cette séparation est nécessaire pour que les images inclues dans la liste des projets soient relatives au dossier du projet.

Cette opération est possible avec `yq`, grâce à sa fonction de substitution `sub` qui accepte les expressions régulières en argument. La lisibilité est cependant fortement impactée, accrochez vous :

```bash
yq eval-all '{"data": ., "target": {"content": filename | sub("meta.yml", "html") | sub(".*/", ""), "dir": filename | sub("projets/", "") | sub("/[^/]*.yml", "")}} as $item ireduce ([]; . + $item) | {"list": .}' projets/*/*.meta.yml
```

Mais cela permet bien d'obtenir l'aggrégation nécessaire à notre modèle Pandoc.

```yaml
list:
  - data:
      title: ASCII Space Destroyer
      img: {}
      subtitle: ASCII subtitle
      content-date: 2015-08-15
      content-description: ASCII description
    target:
      content: content.html
      dir: ascii_space
  - data:
      title: Pandoc HTML
      img: {}
      subtitle: Pandoc subtitle
      content-date: 2023-06-05
      content-description: Pandoc description
    target:
      content: content.html
      dir: pandoc_site
```

Je vous laisse parcourir la [documentation yq](https://mikefarah.gitbook.io/yq/operators/reduce) pour déchiffrer les commandes `yq`. Je vous préviens, un peu de pratique est nécessaire pour en maîtriser la puissance.

Il reste un dernier effort à accomplir pour injecter cette commande dans notre `makefile` :

```makefile
DIR_OF_PROJECTS := src/projets
GEN_LIST_YML := $(DIR_OF_PROJECTS)/list.gen.yml

SRC_LIST_YML := $(shell echo $(DIR_OF_PROJECTS)/*/*.meta.yml)

LIST_GEN := yq eval-all '{"data": ., "target": {"content": filename | sub("meta.yml", "html") | sub(".*/", ""), "dir": filename | sub("$(DIR_OF_PROJECTS)/", "") | sub("/[^/]*.yml", "")}} as $$item ireduce ([]; . + $$item) | {"list": .}'

$(GEN_LIST_YML): $(SRC_LIST_YML)
    $(LIST_GEN) $(SRC_LIST_YML) > $@
```

Et voilà, nous avons achevé notre `makefile` permettant la génération complète de mon site web !

### Pour aller plus loin

Voici quelques idées d'amélioration possibles pour mon générateur de site.

#### Minimiser l'empreinte du site produit

Scanner les fichiers inclus depuis les fichiers sources et les indiquer comme étant ressources à distribuer.

Empêche le déploiement de fichiers inutiles, en permettant de les détecter et de les supprimer.

#### Minifier les fichiers déployés

Enlever les caractères blancs, les commentaires, supprimer le CSS ou JS inutilisé.

#### Auto-compiler à chaque modification

Déclencher un `make install` à chaque sauvegarde de fichier avec un watcher, ou bien après chaque commit avec un [post-commit git hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks).

#### Utiliser les fonctionnalités de référence de Pandoc

Pour avoir un sommaire et des références en bas de page. Vous aurez peut-être remarqué que le lien sur les [parties Pandoc](https://pandoc.org/MANUAL.html#partials) était présent à plusieurs endroits, ce qui n'est pas facile à maintenir en cas de changement de lien ou en cas d'envie d'avoir une liste complète des références.

#### Augmenter les métadonnées de chaque projet

Rajouter des étiquettes pour catégoriser les projets, rajouter des références comme des liens vers les sites externes (code source, page de téléchargement, etc.).

#### Ajouter des recettes de création de composant

Ajouter une recette qui permet de créer un squelette de nouveau projet en une commande.

#### Valider les metadata

Mettre en place un YAML schéma pour s'assurer que les metadata sont bien au bon format.

#### Localisation

Assigner une langue à chaque fichier de contenu, pour pouvoir sauvegarder des traductions. Et structurer le site pour permettre au visiteur de choisir sa langue.

### Conclusion

J'ai donc construit un système plus léger au niveau de la rédaction de contenu qui me permettra de facilement produire du nouveau contenu en markdown. J'ai troqué la dépendance serveur PHP contre deux dépendances au build, pandoc et GNU Make. L'opération est donc un succès et j'utilise aujourd'hui ce projet en production.

J'ai gagné en confort, en employant des technologies stables que je connaissais par ailleurs.

Mais cela reste un projet d'exploration que je ne recommande pas forcément pour un usage pro. Ces choix souffrent de quelques limites : j'arrive aux limites du moteur de template de pandoc. Et contrairement à Jekyll, je ne dispose pas (encore) de rechargement à chaud. Pour les besoins de mon site ce n'est pas très grave car il est déjà construit et je compte seulement rajouter des projets ou articles, mais si j'ai besoin de faire quelque chose de sérieux j'utiliserai Jekyll au autre technologie destinée à cet usage.

Merci pour votre lecture !

#### Refs

- Outils
    - [Pandoc](https://pandoc.org)
    - [GNU make](https://www.gnu.org/software/make)
    - CMS
        - [Wagtail](https://wagtail.org/features/)
        - [Wordpress](https://wordpress.org/download/)
    - Static generators
        - [Jekyll](https://jekyllrb.com/)
        - [HUGO](https://gohugo.io/)
        - [Grav](https://getgrav.org/features)
- Moi
    - [Mon site](https://www.jovian-hersemeule.eu)
    - [Mon code source](https://gogs.jovian-hersemeule.eu/DricomDragon/StaticWebsiteJovian)
- Illustrations
    - CC : Markdown icon created by Heztasia from Noun Project
    - CC : HTML icon created by Bagus Kusnandar
    - CC : Diagrammes créés avec [draw.io](https://www.drawio.com/)
    - CC : Icônes du projet réalisées avec [Gimp](https://www.gimp.org/)
