A generator for this site
This site used to be generated by a few hundred lines of Nim built around
htmlgen, which meant every markup change was a code change. The layout now
lives in plain HTML templates and the generator only fills in the parts that
vary.
How an article is built
Every folder under src/articles becomes one page. The folder holds an
index.md and whatever assets that article references:
src/articles/
a-generator-for-this-site/
index.md
diagram.png
The generator reads the front matter, renders the markdown, drops the result
into src/article.html, and copies the rest of the folder next to the finished
page — so a relative  keeps working without any link
rewriting.
One templating primitive
There is no template engine. The HTML files carry paired marker comments, and the generator swaps out whatever sits between them:
def replace_block(template: str, name: str, content: str) -> str:
begin, end = f"<!-- BEGIN {name} -->", f"<!-- END {name} -->"
pattern = re.compile(re.escape(begin) + ".*?" + re.escape(end), re.DOTALL)
if not pattern.search(template):
fail(f"template is missing the {begin} … {end} markers")
return pattern.sub(lambda _: f"{begin}\n{content}\n{end}", template, count=1)
That is the whole abstraction: one call fills the article body, another the
<head>, a third the list of teasers on the home page. Renaming a marker
without updating the generator stops the build instead of publishing an empty
page.
Front matter
Three keys, all required:
| Key | Purpose |
|---|---|
title |
The <h1>, the <title>, and the link text on the home page |
date |
ISO date; the article index sorts on it |
summary |
The teaser paragraph on the home page |
Because the heading comes from title, the body starts at ##.
A build that quietly produces the wrong page is worse than one that stops. Missing or malformed front matter fails the build and names the file.
Article pages carry no sidebar — just the column you are reading.