Colophon

The site doesn’t use any JavaScript or resources from other domains and there are no referral links or cookies. The only analytics are from my hosting provider, Netlify, which ostensibly uses log scraping.

Internals

This site’s HTML is generated from a directory structure that looks like this:

/pages/index.md
/pages/colophon.md
/pages/...
/stream/<year>/<month>/<day>-<time>-<short-link>.md
/notes/<subtopic>/<note>.md
/static/style.css

This structure makes it easy to update the site with a basic text editor, as opposed to a web interface normally found in blog engines. There’s support for longer articles, but I prefer writing short updates that link to long-lived notes, since it’s difficult to maintain a steady stream of substantial, write-once content.

Pages and notes are written in Markdown, to make adding emphasis, code, headings, and links easy. I wrote the program that generates this site in Rust, using the pulldown-cmark crate to convert the Markdown to HTML and Askama to incorporate that HTML into templates that provide a consistent look and feel for each page. Tokio provides a multi-threaded runtime for regenerating notes, allowing I/Os to be scheduled asynchronously and multiple CPUs to help convert notes. For notes that need it, KaTeX can typeset mathematical expressions, at the cost of almost 100KB in additional page assets and an order of magnitude more time to convert to HTML.

The content of the site is stored in a directory called site-content, which is supported by other directories, in a structure like this:

site-content/
site-out/
site-rs/
site-tools/

The site-out directory holds the HTML and CSS results of generating the website and is pushed to GitHub, which Netlify makes available across the internet. The HTML and CSS are small enough to leave un-minified, so it’s easier to inspect for curious visitors.

The code is in site-rs while site-tools contains shell scripts that build and publish the site, keeping the site-content and site-out directories in sync, like this publish.sh script:

#!/bin/sh

set -o pipefail
set -o errexit
set -o nounset

msg="$*"
len=`echo "$msg" | wc -c | sed -E 's/^ +//'`
if [ $len -gt 50 ]; then
	> /dev/stderr echo "error: message is $len characters"
	exit 1
fi

if [ -z "$(git -C site-content status --porcelain)" ]; then
	> /dev/stderr echo error: site-content is clean
	exit 1
fi

if [ -z "$(git -C site-out status --porcelain)" ]; then
	> /dev/stderr echo error: site-out is clean
	echo consider implementing automatic rebuild
	exit 1
fi

git -C site-content add .
git -C site-content commit -m "$msg"

git -C site-out add .
git -C site-out commit -m "$msg"
git -C site-out push

To make those scripts accessible, I made a short Makefile in the site’s parent directory:

default: all

all:
	site-tools/build.sh
	site-tools/publish.sh $(m)

build:
	site-tools/build.sh
b: build

amend:
	site-tools/amend.sh
a: amend

publish:
	site-tools/publish.sh $(m)
p: publish

.PHONY: default all build b amend a publish p

So building and publishing the site is as simple as make m='The commit message'.

Nova integration

Since I do most of my editing of the site in The Nova text editor, I also wrote a small extension that adds an action to publish the site using. The main JavaScript file is just:

function publish(workspace) {
  var options = {
    "placeholder": "Commit message",
    "prompt": "Publish"
  }
  nova.workspace.showInputPanel("Publish Site", options, (result) => {
    if (result) {
      siteToolsPath = `${nova.workspace.path}/site-tools`
      let p = new Process(`${siteToolsPath}/publish.sh`, {
        args: [result],
        cwd: nova.workspace.path,
        shell: true
      })
      let errorLines = []
      let lines = []
      p.onStderr((line) => { errorLines.push(line) })
      p.onStdout((line) => { lines.push(line) })
      p.onDidExit((code) => {
        let request = new NotificationRequest(
            'net.mattwidmann.publish-notification')
        if (code != 0) {
          request.title = nova.localize('Failed to Publish')
          request.body = nova.localize(errorLines.join('\n'))
        } else {
          request.title = nova.localize('Published')
          request.body = nova.localize(lines.join('\n'))
        }
        nova.notifications.add(request)
      })
      p.start()
    }
  })
}

nova.commands.register("net.mattwidmann.publish", publish);

In order for Git to unlock my SSH key and push changes to GitHub, I had to add my SSH key to 1Password. With just ssh-agent, it can’t ask for the passphrase over stdin and the push will fail.

The 404 page includes this HTML for a JavaScript-free site-specific search:

<form class="search" action="https://www.duckduckgo.com/" method="GET">
	<input name="sites" type="hidden" value="mattwidmann.net">
	<input name="q" type="text">
	<input type="submit" value="Search">
</form>

Updated on 7 April 2023.