The Best Static HTML Generator

This post first appeared 25 August 2024.

This bank holiday weekend I found myself writing a small amount of documentation for project I was doing. I wanted it to be simple and easy to edit but still look good and feel professional.

I started out writing some pure HTML and used simple.css to style the semantic elements. It was looking very neat, and I was enjoying getting back to the roots of web development by writing out tags manually instead of using a markdown parser.

But then I hit a snag. I wanted the code blocks to be syntax highlighted. It would also be cool if the code blocks could be actual excerpts from the sample script I had, so that there wasn’t any slippage between the docs and the code.

At first I laboured onwards in my pure HTML and just copied the output of pygmentize into my document, but this soon became untenable.

I considered looking for a static HTML generator. This site uses hugo but after they introduced modules and I suddenly needed npm for most themes I’ve become less of a fan. I briefly considered looking for a general purpose markdown generator (again), but realised I wanted the semantic tags that simple.css uses because, well, semantic tags are cool.

So I decided to roll my own. I decided it should handle single files of HTML, support including the contents of other files from elsewhere in the filesystem (recursively), be fast to run, support syntax highlighting and code excerpts, and have a very flexible plugin system.

Oh, and it’s source code should also be one line long1.

So without further ado, may I present render:

#!/bin/bash

cat $1 | \
  sed '/^% /! s/"/\\\"/g' | \
  sed '/^% /! s/^\(.*\)/echo "\1"/' | \
  sed "s/^% \(.*\)/\1/" | \
  bash > ${1%.template}

You can invoke it like this:

./render index.html.template

and it produces a file index.html.

In case its not clear from the sed-soup above, the way that render works is that it takes the file line by line and if the line begins with '% ', it removes that. Otherwise it escapes any quotation marks and then replaces the line with echo "LINE_HERE". Once its done it passes it to be evaluated by bash and saves the stdout.

This means that any shell command can be executed as part of the script. For example, it supports snippet inclusion:

<head>
  <style>
% cat ./css/site_styles.css
  </style>
</style>

even from remote sources2

<head>
  <style>
% curl https://raw.githubusercontent.com/richleland/pygments-css/master/default.css
  </style>
</style>

You can make code blocks with syntax highlighting

<div>
Here is some code:
% pygmentize -f html << EOF
% def foo():
%     print('bar')
% EOF
</div>

It even supports loops and conditional execution! In fact it is Turing-complete.

To grab excerpts from my script, I wrote a plugin (bash script) called code_chunk

#!/bin/bash

sed -n "/# $1\$/,/# $1_end\$/{/# $1\$/!{/# $1_end\$/!p}}" $2 | pygmentize -f html

which I could then use like

<div>
Here is all the code between <code># my_chunk</code> and <code>#my_chunk_end</code>
% code_chunk my_script.py my_chunk
</div>

I’m truly in awe of what we can do with modern technology. Now that we finally have such powerful tools as bash and sed and curl, the sky is the limit! Imagine doing all this but actually putting in… effort!


P.S. For complicated reasons I can’t share the documentation I was working on, but here is a similar example.


  1. Though I’ve broken it up into multiple lines here for readability, and in actual fact it has a bunch of nice logging and helptext and stuff which makes it a bit longer. ↩︎

  2. The version I was using actually cached the response in /tmp so it rendered super quickly once it had downloaded the file once! ↩︎