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
2026-08-12
alias, tag et match
Imaginons le scénario suivant:
- je dois me connecter à 3 domaines: foo.fr, bar.org et baz.com
- chaque domaine est divisé en 2 sous-domaines: .dev et .prod
- je dois me connecter avec mon login actuel, un login spécifique (“pouet”) et en “root” (pas la peine de gnagna root gnagna pabien: je sais)
- le nommage des machines sur lesquelles je dois me connecter est identique: ‘vm’ + ‘-’ + service (où service est dans la liste http, cache, db)
Un extrait des connexions possibles:
$ ssh vm-http.dev.foo.fr
$ ssh pouet@vm-cache.prod.bar.org
$ ssh root@vm-db.dev.baz.com
La complétion
On peut se simplifier la vie en créant des fichiers correspondant aux connexions:
$ mkdir .ssh/completion/ && cd .ssh/completion
$ touch vm-http.dev.foo.fr pouet@vm-cache.prod.bar.org root@vm-db.dev.baz.com ...
et compléter la commande ssh avec la liste de ces fichiers. Inconvénient: la touche Tab va passer un sale quart d’heure.
Mon idéal
Il faudrait avoir une “commande” par domaine: foo pour se connecter à foo.fr, bar pour bar.org et baz pour baz.com . En rajoutant une lettre (minuscule et/ou majuscule ?) pour le sous-domaine, on aurait Pfoo pour se connecter à .prod.foo.fr et dbaz pour .dev.baz.com .
Pour les logins et les machines, il ne faudrait préciser que le service et utiliser une lettre pour le login: Rdb pour root@vm-db, pcache pour pouet@vm-cache, http pour vm-http .
En assemblant le tout, l’extrait des connexions possibles deviendrait:
$ dfoo http
$ Pbar pcache
$ dbaz Rdb
Le mixte minuscule/majuscule n’est là que pour illustrer le propos et pourrait ici se traduire par “si c’est important/risqué/dangereux alors c’est en majuscule”.
Match
Une de mes utilisations de cette directive de mon .ssh/config est de définir des “raccourcis”:
Match originalhost owrt
HostName openwrt.mon.domaine.que.j.ai
mais si je veux pouvoir préciser un éventuel login, je dois être un peu plus générique:
Match originalhost *db
HostName vm-db
qu’on traduira par “si le nom de machine de la commande ssh se termine par db alors le nom de machine à utiliser est vm-db”. J’utilise la même chose pour les logins:
Match originalhost p*
User pouet
Match originalhost R*
User root
À noter que le “p” passerait en majuscule (pour bien le distinguer du nom de machine) dans le cas où un service commencerait par “p” (proxy, pgsql …).
Tag
C’est avec cette fonctionnalité que je vais résoudre en partie la simplification domaine / sous-domaine. Il est possible de passer un “tag” à une connexion à l’aide du paramètre -P tag et de définir des valeurs par “tag”:
Match tagged old
KexAlgorithms diffie-hellman-group1-sha1
Pour vérifier si la directive est prise en compte:
$ ssh -P old -G pouet | grep ^kexalgorithms
kexalgorithms diffie-hellman-group1-sha1
Chaque domaine / sous-domaine va correspondre à un “tag” qui va définir le nom de domaine:
Match tagged devfoo
CanonicalDomains .dev.foo.fr
...
Match tagged prodbaz
CanonicalDomains .prod.baz.com
Je dois aussi forcer les machines commençant par “vm-” à utiliser un nom de domaine:
Match host vm-*
CanonicalizeHostname yes
Je vérifie mes combinaisons “tag” / “host”:
$ ssh -G -P devfoo Rdb | grep -e '^user ' -e '^hostname '
user root
hostname vm-db.dev.foo.fr
Attention, une connexion ne peut avoir qu’un seul “tag” et l’ordre des directives est important !
Alias
Je n’ai plus qu’à définir les alias:
alias dfoo ssh -P devfoo
alias Pfoo ssh -P prodfoo
...
alias Pbaz ssh -P prodbaz
et attendre que mes doigts mémorisent tout ça.
Un petit pour la route
Parce que la vie n’est qu’une longue suite d’exceptions, imaginons qu’une machine d’un sous-domaine ne respecte pas la convention de nommage:
# putain de vs-database de mes couilles
Match tagged devbar originalhost *db
HostName vs-database
Match originalhost *db
HostName vm-db
Tiré d’un exemple @TAF (et oui, le commentaire est d’origine).
Commentaires: https://github.com/bsdsx/blog_posts/issues/24