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 ![](diagram.png) 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.