Tags

arm64 bhyve bit blacklistd bluepill bluetooth cloudinit contabo cu dns dovecot elf encrypt envsubst esp8266 filter freebsd iot ipfw lets lowdown meross openocd opensmtpd openwrt orke perl prises python shell ssh ssl stm32 template tls unbound vps wifi yubikey

2026-08-08

[ orke lowdown ]

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:

Le blog est physiquement organisé de la façon suivante:

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:

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

Lien vers ce billet