New site and site generator
How to not make a site generator
Before writing this post, this site was run using Zola. Zola is a more classic site generator, where you make several files for templates, and use a template engine to fill in the content. Sadly, I found this language rather tedious to use for anything more advanced than simply putting content inside the html.
Templates
At this point, I got inspired by this post by Ben Visness. Instead of having to use some funny templating language, I could use full lua. I didn't opt for using a custom parser to add html syntax to lua, as I find writing html somewhat annoying.
Instead, I did something better. Lua allows calling functions without the braces when the only argument is either a table, or a string. Tables also allow mixing arrays and dictionaries in the same structure. This allows attributes and content in the same table, and thus makes for a pretty neat DSL on it's own:
-- make an example 404 page
local not_found = h {
h.html {
h.head {
h.title 'Not found!',
h.link { rel = 'icon', href = '/icon.svg' },
h.meta { charset = 'utf-8' },
h.style(css),
h.meta {
name = 'viewport',
content = 'width=device-width,initial-scale=1'
},
},
h.body {
class = 'container',
-- page not found
h.main {
class = 'main',
h.h1 'Page not found!',
h.div {
class = 'imgblock',
h.img { src = '/logo.svg', alt = 'SLSG logo' }
},
h.p {
'see ',
h.a {
href = '/index.html',
h.code { class = 'inline', 'index.html' },
},
' instead',
}
}
}
}
}
The start implementation was a bit jank because I didn't really take care to deal with proper semantics, so I had to move around a bunch with fixing escaping not working. In the end, I liked this, so I build a site generator around it.
The idea would be simple: emit files via lua script, add a bunch of functions to parse a markup language, and provide this as library. Then also add a web server that autoreloads when a file changes.
The markup
Yes, I also decided against markdown, as adding custom stuff to it is annoying Instead, I have luamark:
% Line macros
@name This is the rest!
% Inline macros
% also possible with (), []
% {}, <>, || and $$ don't do multiple arguments
@name(arg1, arg2, arg3)
% We can also do block macros, to include code verbatim
% These take all text in them literally,
% and only end at the closing @end@name tag
@begin@name(arg1, arg2, arg3)
This is all verbatim!
@end@name
Which I like a bit more than markdown, but it's still jank because I am not able to nest elements. The original inspiration was Typst but worse. After that I yanked out having the results of a macro being used inside a macro, because that would be difficult.
The web server
I also wrote this myself based on the rust book's web server, because all the other implementations really want async add a lot of complexity that I don't need. HTTP/1.1 is enough for having my browser read the pages, and as there's likely only one user of the site at the same time for previewing, no multithreading to handle simultaneous requests would really be needed.
For reloading on file change, I use notify
to detect when a file changes. If one does, set an atomic bool to true, and when the TCP
listener times out after every 200 ms
, check if it's true, and if so, use a server-sent event to notify the page it needs to reload. To make this work, the following is attached to every *.html
page:
<script>
const ver = "{version}";
const src = new EventSource("/{path}");
// reload when the version differs
src.onmessage = e => e.data != ver && location.reload();
window.onbeforeunload = () => src.close();
</script>
Which works well!
The Lua
Now for the part that works less well. I decided I wanted maximum flexibility, so I would not load posts that end with *.lmk
, but instead allow the lua script to load arbitrary files. luamark
also works in such a way that you have to define all the macros up front, which is tedious, especially for the things you'd expect to be built-in, like headings, links, and code.
Then I decided to not do nesting. This makes tables and lists annoying, because now you need to write several luamark parsers for everything that can nest.
Because the lua was set up to also needing to find and index your own files, you get to the point where you are writing most of a site generator for each site you make. This gets annoying fast.
This is the current state of the site generator, and I wrote half a site generator, again, to get to this point of having the site work again.
Now to make it work better...
The future
I still am looking at how to make this work better, mostly by looking at 11ty, which seems quite neat.
The idea is to instead parse all the luamark files ahead of time, then run main.lua
to generate all needed template files, then run the code in the luamark files afterwards. Adding templates hopefully makes it close enough to 'normal' site generators to actually be useful.