Porting this blog to djot

published 2023-04-09 [ home ]

Over 10 years ago, I created this blog and wrote the static generator that powers it. I used Markdown for the articles and lunamark, a library written by John MacFarlane of CommonMark and Pandoc fame.

Recently, John created djot, a markup syntax intended to be an evolution of Markdown. The initial reference implementation was written in Lua and I decided to port my static generator to it.

Converting the Markdown parts of the post themselves was not very hard, the main think I can think of was switching my old posts from intented code blocks - which are not supported by djot - to fenced code blocks. However, my posts were not pure vanilla Markdown. I used two specific features from lunamark and a few extensions of my own.

The first lunamark feature I had to find a replacement for was Lua metadata. djot does not support metadata yet but it does support raw blocks with a format which I could easily abuse.

The second feature is Pandoc-style title blocks, which I could just move to the metadata section so I did that.

I also had a custom block format syntax that I used for three things: raw blocks, syntax highlighting language choice and post descriptions. The first two are natively supported by djot, and I moved the description to the metadata as well.

For those transformations I used Perl one-liners such as this one which converts the metadata format:

for i in separateconcerns/blog/articles/*.md; do
    perl -0pe 's/<!\-\-@(.*)\-\->/"```=lua-meta". join("\n", split(\/\n  \/, $1)) ."```"/es' \
        $i > "$(dirname $i)/$(basename $i .md).dj"

Once that was done, it was time to port the generator itself. I started with the HTML part. As I said some of the things I was doing manually in lunamark are supported natively in djot, there were two things left to do: support my metadata blocks and detect posts with code in them so I can include the JavaScript syntax highlighting library only when needed.

Djot has a step when you can get the AST and I could have used it to extract this information, but I decided to use the same method I used with lunamark instead, which is overriding parts of the renderer. To do that I created a small helper library which mostly exposes the StringHandle used internally in djot.lua. I would need it for the next part anyway.

All in all, it was pretty smooth. I just hit a small issue with URL parsing which I worked around for now by using URL encoding in my .dj source files.

Since I did not change my output format much and kept exactly the same metadata, my index and Atom feed generation worked out of the box. But there was still one last thing to do: because this blog is also available on Gemini, I had to implement a Gemtext renderer.

In lunamark, there was a generic writer which I overloaded for that purpose. As djot.lua only supports HTML output natively for now, this time I just wrote a renderer from scratch. I could also have gone the Pandoc route as djot includes a custom writer and reader, but I wanted to avoid it and Pandoc does not natively support Gemtext anyway.

All in all, this took a few hours to do, and the blog post you are reading now has been published with this new stack. I like djot, it is an improvement over CommonMark even though it is still has a few rough edges. I like parts of the djot.lua library a bit less, especially the parser. lunamark uses LPeg instead and I find it simpler, but it’s still fine.

This experience also conforts me in the choice I made 10 years ago to write my own static generator using a language I know well with few dependencies. It is very low maintenance most of the time compared to the likes of Hugo, Pelican or Jekyll, and it lets me do changes like this easily when I want to.