2026-08-08
Bienvenue à lowdown !
Mon générateur de blog statique utilisait jusqu’ici un script awk pour transformer du markdown en html. Cela fait quelques temps déjà que j’utilise l’excellent lowdown et son option -tterm pour documenter des projets. Ainsi mes Makefile contiennent désormais une cible doc qui exécute
lowdown -tterm README.md | less -R
lowdown ne manque pas de fonctionnalités parmis lesquelles une sortie html bien plus complète que mon script awk. Métadonnées et gabarits vont clouer définitivement le cercueil de mon générateur.
Générer du html
lowdown a le bon goût d’utiliser l’entrée standard (bien utile pour vérifier le comportement d’une option):
$ echo '## titre de niveau 2' | lowdown -thtml
<h2 id="titre-de-niveau-2">titre de niveau 2</h2>
$ echo '## titre de niveau 2' | lowdown -thtml --html-no-head-ids
<h2>titre de niveau 2</h2>
Générer une page html
Pour obtenir une page, il faut utiliser l’option -s (ou --out-standalone) qui va utiliser un gabarit par défaut:
$ printf '## titre de niveau 2\nbla bla\nbla bla' | lowdown -thtml --html-no-head-ids -s
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title></title>
</head>
<body>
<h2>titre de niveau 2</h2>
<p>bla bla
bla bla</p>
</body>
</html>
On peut préciser un autre gabarit à l’aide de l’option --template:
$ cat /tmp/tmpl.tmpl
<!DOCTYPE html>
<html>
<body>
$body$
</body>
</html>
$ printf '## titre de niveau 2\nbla bla\nbla bla' | lowdown -thtml --html-no-head-ids -s --template /tmp/tmpl.tmpl
<!DOCTYPE html>
<html>
<body>
<h2>titre de niveau 2</h2>
<p>bla bla
bla bla</p>
</body>
</html>
La magie opère avec les métadonnées:
$ cat /tmp/tmpl.tmpl
<!DOCTYPE html>
<html>
<head><title>$title$</title></head>
<body>
$body$
</body>
</html>
$ cat /tmp/md.md
---
title: mon super titre
---
## Niveau 2
bla bla bla
$ lowdown -thtml --html-no-head-ids -s --template /tmp/tmpl.tmpl /tmp/md.md
<!DOCTYPE html>
<html>
<head><title>mon super titre</title></head>
<body>
<h2>Niveau 2</h2>
<p>bla bla bla</p>
</body>
</html>
Mon blog
Une page du blog est composée de 3 parties:
- liste des mots clefs et quelques liens
- un (des) billet(s)
- archives et quelques liens
Le blog est physiquement organisé de la façon suivante:
- ./assets: css et logos (et non, pas de javascript)
- ./src: billets au format markdown
- ./tmpl: gabarits
- ./www/YYYY/MM: pages des billets
- ./www/tags: répertoires des mots clefs et liens symboliques
Un fichier blog.md contient les métadonnées du blog:
$ cat blog.md
---
blog_title: Mon ch'ti blog
blog_description: Unix, libre, toussa ...
blog_url: http://blog.bsdsx.fr
blog_author: bsdsx
blog_root: /
---
Ajouter un billet
L’ajout d’un billet va:
- extraire sa date depuis le nom du fichier et la liste des mots clefs depuis ses métadonnées
- créer des répertoires (un par date et par mot clef)
- générer le rendu des différentes parties d’une page du blog
- générer les pages html (billet, archive, mots clefs)
- générer les flux atom et rss
Le rendu du billet
Le rendu du billet src/2026-08-08_orke.md sera stocké dans www/2026/08/2026-08-08_orke.md.htm et généré avec la commande:
lowdown -thtml $LOWDOWN_OPTS -M "date: $YYYY-$MM-$DD" -M "post_url: /$YYYY/$MM/$SRC.html" -s --template tmpl/post.tmpl src/$SRC > $ARCHIVES/$SRC.htm
Le gabarit est très simple:
$ cat tmp.post.tmpl
<p class="date">$date$</p>
<p class="tags">[$for(tags.split)$ <a href="/tags/$this$">$this$</a>$endfor$ ]</p>
<h2>$title$</h2>
$body$
<p>Commentaires: <a target="_blank" href="https://github.com/bsdsx/blog_posts/issues/$id_issue$">https://github.com/bsdsx/blog_posts/issues/$id_issue$</a></p>
<p class="post"><a href="$post_url$" target="_blank">Lien vers ce billet</a></p>
<hr>
Je distingue le rendu du billet (.htm) de la page du billet (.html). Faire cette distinction va me permettre de concaténer plusieurs rendus de billet au sein d’une même page.
Les mots clefs
Pour chaque mot clef, créer un répertoire et un lien vers le rendu du billet:
TAGS=$(lowdown -X tags src/$SRC)
for tag in $TAGS; do
mkdir -p www/tags/$tag
(cd www/tags/$tag; ln -fs ../../$YYYY/$MM/$SRC.htm .)
done
Le rendu des mots clefs (partie gauche d’une page):
OUT=www/.tags.htm
echo '<div id="left"><div id="cloud_tags"><p>Tags</p>' > $OUT
for tag in $(find www/tags -type d -mindepth 1 -printf '%f\n' | sort); do
printf '<a href="/tags/%s/index.html">%s</a>\n' $tag $tag >> $OUT
done
echo '</div>' >> $OUT
lowdown -thtml -s --template tmpl/left_links.tmpl blog.md >> $OUT
echo '</div><!-- end left -->' >> $OUT
Les archives
Le rendu de la partie droite:
OUT=www/.archives.htm
echo '<div id="right"><div id="archives"><p>Archives</p>' > $OUT
for yyyy_mm in $(find www/2* -type d -mindepth 1 | sort -r); do
yyyy_mm=${yyyy_mm#www/}
printf '<a href="/%s/index.html">%s</a>\n' $yyyy_mm $yyyy_mm >> $OUT
done
echo '</div>' >> $OUT
lowdown -thtml -s --template tmpl/right_links.tmpl blog.md >> $OUT
echo '</div><!-- end right -->' >> $OUT
Début et fin de page
Génération du début d’une page à l’aide des métadonnées du blog:
OUT=www/.before_main.htm
lowdown -thtml -s --template tmpl/top.tmpl blog.md > $OUT
cat www/.tags.htm www/.archives.htm >> $OUT
echo '<div id="main">' >> $OUT
OUT=www/.after_main.htm
echo '</div><!-- end main -->' > $OUT
lowdown -thtml -s --template tmpl/bottom.tmpl blog.md >> $OUT
Arrivé ici le rendu des différentes parties est terminé, reste à générer les pages html complètes.
Page des mots clefs
Concaténer le contenu des liens de chaque mot clef:
for tag in $TAGS; do
OUT=www/tags/$tag/.index.html
HTMS=$(find www/tags/$tag/ -type l | sort -r | tr '\n' ' ')
cat www/.before_main.htm $HTMS www/.after_main.htm > $OUT
mv $OUT www/tags/$tag/index.html
done
Page d’archive
Concaténer les rendus de billet:
OUT=$ARCHIVES/.index.html
cat www/.before_main.htm $ARCHIVES/*.htm www/.after_main.htm > $OUT
mv $OUT $ARCHIVES/index.html
Page d’un billet
Identique à une page d’archive mais avec un seul billet:
OUT=$ARCHIVES/.$SRC.html
cat www/.before_main.htm $ARCHIVES/$SRC.htm www/.after_main.htm > $OUT
mv $OUT $ARCHIVES/$SRC.html
Page d’accueil
Concaténer les derniers billets:
[ -f www/.posts ] || touch www/.posts
grep -q --max-count 1 $ARCHIVES/$SRC.htm www/.posts || echo $ARCHIVES/$SRC.htm >> www/.posts
...
latest_posts=$(tail -n $POSTS_PER_PAGE www/.posts | sort -r | tr '\n' ' ')
OUT=www/.index.html
cat www/.before_main.htm $latest_posts www/.after_main.htm > $OUT
mv $OUT www/index.html
Les flux atom et rss
Pour générer ces fichers, j’ai besoin des métadonnées du blog et des billets. Je ne peux pas simplement faire:
$ cat blog.md src/billet.md | lowdown ...
car lowdown ne prendra en compte que les métadonnées du premier fichier comme le montre la commande suivante:
$ cat blog.md src/2026-08-08_orke.md | lowdown -L
blog_title
blog_description
blog_url
blog_author
blog_root
Je ruse en supprimant les marqueurs (“---”) en trop (le dernier du premier fichier et le premier du deuxième fichier) à l’aide d’un script awk:
BEGIN { ff = 1 } # First File
NF == 1 && $1 == "---" {
if (ff && FNR != 1) { ff = 0; nextfile } # skip last mark
if (!ff && FNR == 1) { getline } # skip first mark
}
{ print }
Chaque flux utilise 2 gabarits: un pour le début de fichier et un par “entrée”:
awk -f concat.awk blog.md src/$SRC | lowdown -thtml $LOWDOWN_OPTS -M "date: $YYYY-$MM-$DD" -M "post_url: /$YYYY/$MM/$SRC.html" -s --template tmpl/atom_entry.tmpl > $ARCHIVES/$SRC.htm.atom
OUT=www/.feed.atom
[ -f www/.atom ] || lowdown -thtml -s --template tmpl/atom.tmpl blog.md > www/.atom
cat www/.atom > $OUT
echo "<updated>${YYYY}-${MM}-${DD}T08:00:00Z</updated>" >> $OUT
for post in $latest_posts; do
cat $post.atom >> $OUT
done
echo '</feed>' >> $OUT
mv $OUT www/feed.atom
Le blog
Je peux facilement repartir de zéro:
$ cat reset.sh
#!/bin/sh
set -eu
rm -rf www
for src in src/2*.md; do
./orke.sh $src
done
cp assets/* www/
ou prévisualiser un billet:
$ cat preview.sh
#!/bin/sh
set -eu
cat www/.before_main.htm
lowdown -thtml --html-no-head-ids --parse-no-intraemph -s --template tmpl/post.tmpl $1
cat www/.after_main.htm
$ ./preview.sh src/2026-08-08_orke.md > www/preview.html
Cette nouvelle version d’**orke** méritait bien un petit quelque chose, j’ai donc pris le temps de faire (enfin !) un thème sombre.
Commentaires: https://github.com/bsdsx/blog_posts/issues/23