Construire un site complet à base de Boop!

Après une petite pause d’un mois sur la refonte de ce site, il est temps pour moi de m’y remettre. La dernière phase avait consisté à imaginer un prototype pour me donner une vision globale de l’architecture du site. Cela devait aussi me permettre de pointer du doigt les manques de Boop!, mon générateur de sites statiques. Et il y en avait, des manques ! Je reviendrai prochainement sur les évolutions du contenu du site ainsi que sur son habillage (qui n’est d’ailleurs pas terminé). Pour l’instant je vais me contenter de détailler les dernières évolutions techniques.

Des améliorations dans le processus de développement

L’une des choses qui me tenait à cœur avec Boop! était de pouvoir travailler de la manière la plus agréable possible sur mon site. Le mieux dans cette situation étant de s’observer râler.

Premièrement, lors de la génération du site, je voulais pouvoir ouvrir rapidement les fichiers générés. Comme la plupart du temps je travaille sur un article, je veux pouvoir l’ouvrir immédiatement. J’ai donc fini par afficher le nom des fichiers dans la console, et comme il s’agit de chemins absolus commençant par file://, je n’ai plus qu’à cliquer dessus pour ouvrir mon article. Une exécution de Boop! ressemble grosso-modo à ça :

[marien@pizza marienfressinaud.fr]$ boop.py --development
Written page: file:///home/marien/marienfressinaud.fr/site/index.html
Written page: file:///home/marien/marienfressinaud.fr/site/blog.html
Written article: file:///home/marien/marienfressinaud.fr/site/construire-un-site-complet-a-base-de-boop.html
Written article: file:///home/marien/marienfressinaud.fr/site/suivre-le-boop-blog.html
Written article: file:///home/marien/marienfressinaud.fr/site/simplifier-la-redaction-darticles-dans-boop.html
Written article: file:///home/marien/marienfressinaud.fr/site/boop-une-introduction.html
Written feed: file:///home/marien/marienfressinaud.fr/site/feeds/all.atom.xml
Static files copied
Boop!

Ici vous ne pouvez évidemment pas cliquer sur les noms des fichiers, mais ça fonctionne dans un terminal.

À noter que lorsque je génère les fichiers pour la mise en production, ce sont les URL finales qui sont affichées (voir le code), ce qui me permet d’ouvrir l’article directement une fois en ligne.

Le deuxième point agaçant était que je devais quitter mon éditeur de texte régulièrement pour générer les fichiers et vérifier que le résultat me convenait. Lors de la phase de relecture d’un article par exemple, je peux vouloir regénérer le site plusieurs fois en quelques secondes : passer de la fenêtre de mon éditeur à celle de mon terminal pour revenir de nouveau à l’éditeur est très pénible. Utilisant Vim, j’ai ajouté deux raccourcis dans mon .vimrc : l’un pour générer le site (,,) et un autre pour publier le site en ligne (,p).

autocmd BufRead,BufNewFile */*marienfressinaud.fr/* nnoremap <leader><leader> !boop.py --development<CR>
autocmd BufRead,BufNewFile */*marienfressinaud.fr/* nnoremap <leader>p make publish<CR>

Habituellement les générateurs de sites statiques proposent un serveur surveillant les changements faits dans les fichiers et qui les regénère à la volée… mais je trouvais cette solution bien compliquée pour un intérêt limité. Cela me permet aussi de garder Boop! concentré sur une seule tâche : générer un site statique.

Enfin, notons que je dispose d’un fichier Makefile dans le répertoire du site qui me permet d’effectuer des tâches usuelles de manière unifiée :

$ make boop    # génère le site en mode développement
$ make publish # génère le site en mode production et le téléverse sur le serveur
$ make clean   # nettoie le répertoire `site`
$ make open    # ouvre la page d'index du site dans le navigateur
$ make tree    # affiche la structure des fichiers en excluant le répertoire `site`

Une réécriture partielle de Boopsy

Si vous vous souvenez, Boopsy est mon système de template maison. Jusque-là, il se contentait d’afficher des variables au sein de fichiers HTML. Cependant, j’ai très vite eu besoin de pouvoir écrire des conditions (if) et des boucles (for). Ces structures ont cela de particulier qu’elles ont un impact sur la portée des variables. La boucle en particulier pose un problème, par exemple dans le cas suivant :

{% for article in liste_articles %}
    <div>{{ article.title }}</div>
{% endfor %}

Avant d’entrer dans la boucle for, la variable article n’existe pas, il faut donc gérer le contexte. L’article sur lequel je me base depuis le début gère cela de manière relativement maline : il transforme le template en mini-programme Python. Le for est donc transformé en syntaxe Python, ce qui permet de déléguer la gestion de la portée des variables à l’interpréteur de ce dernier. J’avais fort justement décidé de ne pas partir sur cette solution initialement (ne la comprenant pas, j’étais allé au plus simple), et j’ai donc réécrit en partie ce que j’avais fait.

Une fois ce travail fait, l’ajout du if et du for a ensuite été très facile vu que je n’avais plus qu’à traduire le code récupéré depuis le template en code Python.

L’apparition des pages…

J’en avais besoin pour avancer : les pages ont été ajoutées très rapidement après mon dernier article. Jusque-là, je ne pouvais avoir qu’une unique page (d’accueil) et des articles. Pourtant, les pages statiques sont bien pratiques pour donner des informations « intemporelles ».

Contrairement aux articles, les pages sont écrites exclusivement en HTML et se trouvent dans un répertoire qui se nomme… pages. Elles peuvent partager elles-aussi un template commun (voir le code).

Le problème auquel j’ai alors fait face a été de pouvoir définir des variables dans les pages pour pouvoir les utiliser dans le template. En effet, le HTML ne permet pas de définir de variables. Je suis donc parti sur la solution utilisée par Jekyll, celle d’accepter un en-tête YAML en haut des fichiers HTML. Je trouvais cette solution d’autant plus élégante qu’au final il s’agissait de la même façon de faire dans les fichiers Markdown.

… d’une page de blog…

Une fois tout cela en place, je n’avais plus grand chose à faire pour avoir le site de mes rêves. Une chose toutefois continuait de m’embêter : je devais lister les articles à la main, un par un, manuellement sur une page de blog.

Disposant désormais de tout ce qu’il me fallait pour construire une liste d’articles automatiquement, il m’a fallu très peu de temps pour ajouter une page de blog générée par Boop!.

… et des séries.

La dernière fonctionnalité que j’ai développée a été celle des « séries ». Une série est une sorte de catégorie regroupant une liste d’articles. La différence avec le système de catégories que l’on peut retrouver ailleurs est que la série liste les articles dans un ordre décidé par l’auteur. C’est-à-dire, dans mon cas actuel, du plus ancien au plus récent (l’inverse donc de la page de blog). Mais on pourrait imaginer que cet ordre soit encore différent ; c’est à l’auteur de voir ce qu’il préfère.

La conséquence de cela est que les liens vers les articles sont aujourd’hui à faire manuellement sur les pages des séries (c’est dommage alors que je viens de vanter les mérites de l’automatisation dans la partie d’avant). J’envisage toutefois un système un peu plus sympathique à terme, sans perdre pour autant en flexibilité.

Une série dispose aussi d’un flux Atom spécifique, de la même manière que les catégories dans Pelican. Je sais qu’ils étaient utilisés sur mon ancien site (pour Lessy notamment), je voulais donc retrouver ce système sur le nouveau. Dans le flux, les articles continuent par contre d’être listés du plus récent au plus ancien.

Une série est créée en regroupant des articles dans un répertoire et en créant une page nommée serie.html dans ce répertoire (voir l’exemple du « Boop! blog »).

Et une grosse réorganisation du code

Vous vous doutez que tous ces changements ont apporté leur lot de dette technique et le fichier presque unique qui contenait le code de Boop! commençait à devenir hors de contrôle (500 lignes de code dont 220 pour la seule fonction principale). Il était grand temps de faire quelque chose, mais pas n’importe comment.

Une chose que je n’aime pas quand je lis le code d’une autre personne, c’est lorsqu’il y a des indirections dans tous les sens et que je dois ouvrir 5 fichiers pour comprendre ce qu’il se passe. Dans mon cas, je voulais que le fichier principal contienne l’ensemble de la logique métier, c’est-à-dire :

  1. chargement de la configuration du site ;
  2. collecte de l’ensemble des articles et des pages ;
  3. écriture des articles et des pages ;
  4. écriture des flux Atom (le principal ainsi que ceux des séries) ;
  5. copie des fichiers contenus dans static.

Ensuite, les fonctions qui ne revêtent pas un caractère important dans la logique métier sont extraites vers un fichier utils.py. Ce fichier apporte néanmoins des informations importantes quant au fonctionnement de Boop!.

Les derniers fichiers quant à eux contiennent quelques classes utiles au fonctionnement, notamment Article, Page et Feed.

Pour les plus curieuses et curieux d’entre vous, vous pouvez retrouver les différents commits de cette réorganisation de code de cette manière :

$ git clone git@framagit.org:marienfressinaud/boop.git && cd boop
$ git log --reverse --oneline 694c4e3f..7bfac8f4
e3c382a Move extension filtering in utility functions
9a2c645 Extract function to build an article from filepath
...
7bfac8f Refactor feeds generation
$ git show [le commit que vous voulez voir]

Je pense toutefois que l’intérêt reste limité, mais c’est vous qui voyez si ça vous intéresse.

Un outil taillé au poil

Boop! a cela de bien qu’il fait très exactement ce dont j’ai besoin : pas une fonctionnalité n’est superflue. Et s’il manque très certainement des choses, ce sont pour d’autres besoins que les miens. Alors, oui, ça m’a pris un certain temps à développer ; je ne conseille évidemment pas à tout le monde de développer son propre générateur de sites statiques. Il y avait avant tout une visée pédagogique dans ma démarche, en montrant comment en partant de zéro on peut concevoir un outil qui fonctionne tout aussi bien qu’un « plus connu ». J’ai d’ailleurs pris le soin au fur et à mesure de l’article (ainsi que des précédents) d’ajouter des liens vers les commits correspondants à ce dont je parlais, pour comprendre « comment j’ai fait ». Je voulais aussi montrer qu’en restant le plus simple possible, on obtient une meilleure maîtrise sur l’outil et on peut le faire évoluer plus facilement. On favorise ainsi la liberté 1 du logiciel libre.

Boop! n’est pas pour autant totalement terminé. Il me reste à l’utiliser régulièrement sur une longue période de temps, comprendre là où ça bloque, fluidifier mon processus de travail, fignoler les morceaux de code toujours pas très élégants, améliorer la documentation que je n’ai jamais pris le temps de structurer correctement… Cela pourrait presque être comparé à du travail d’orfèvre à ce niveau de précision, mais j’aime être pointilleux quand je peux me le permettre. Et je peux.

Revenir à la série