Building This Website

There is no deficiency of static website generators these days and so naturally I had to write my own.

Overview

The main functionality of the tool will be encapsulated in a Python script called render.py. Even though string based templates are the wrong solution we're going to use a combination of Jinja and markdown to generate HTML output. Each page will be generated by a single call to the script which will allow the whole thing to be ran in parallel across multiple CPUs by wiring it together using ninja.

Here you can see an example invocation of the render.py script with some annotated flags.

./render.py \
    # the HTML template file
    --template=index.jinja \
    # this contains a map of each webpage which is available in the renderer
    # as well as page level configuration options.
    --sitemap sitemap.yaml \
    # markdown file, this one contains the text I'm currently typing!
    --content=blog/building-this-website/README.md \
    # output of the rendered HTML page
    gen/blog/building-this-website/index.html

Hierarchical Sitemap

I want to generate a "tree" sitemap similar to a file browser like NERDTree. We'll pass in a YAML document to the render.py script and then transform it into an XML tree.

def _load_sitemap(path, current_path):
    by_name = dict()
    flattened = []
    with open(path, "r") as fp:
        links = yaml.safe_load(fp.read())

    def _link(links, parent=None):
        for link in links:
            flattened.append(link)
            link["parent"] = parent
            if "others" in link:
                _link(link["others"], parent=link)

    def _make_url(link):
        path = [link["name"]]
        parent = link["parent"]
        while parent is not None:
            path.append(parent["name"])
            parent = parent["parent"]
        path.reverse()
        return "/".join(path)

    _link(links, parent=None)

    for link in flattened:
        target = "/" + _make_url(link)
        if target == current_path:
            link["active"] = True
        else:
            link["active"] = False
        link["url"] = target
        by_name[target] = link

    return dict(by_name=by_name, links=links)


def _make_tree(links):
    def _populate(root, links):
        for link in links:
            elm = ET.Element("li")
            if "directory" in link and link["directory"]:
                elm.text = link["name"]
            else:
                lref = ET.Element("a", href=link["url"])
                lref.text = link["name"]
                if link["active"]:
                    lref.text = lref.text + "  <"
                elm.append(lref)
            if "others" in link:
                others = link["others"]
                ul = ET.Element("ul")
                elm.append(ul)
                _populate(root=ul, links=others)
            root.append(elm)

    params = {"class": "tree"}

    root = ET.Element("ul", **params)
    _populate(root=root, links=links)
    return ET.tostring(root).decode()

Dithering Engine

Images can be reduced in size with dithering algorithms and they also look very cool. The idea for this was inspired by LOW←TECH MAGAZINE.

Spell & Grammar Checking

I can implement some basic spell checking against the website with aspell.

find content -name '*.md' -exec aspell --mode=markdown check {} \;

Filesystem "Watch" Support

We'll use the native inotify via the inotifywatch tool which is available on all Linux distributions.

#!/bin/sh

# the actual command we want to run each time any file changes in the project
BUILD_CMD="$@"

# some pretty colorized output
_do_build() {
    $BUILD_CMD && {
        echo -e "\033[32;1;4mSuccess\033[0m"
    } || {
        echo -e "\033[31;1;4mFailure\033[0m"
    }
}

# build the project the first time you start up the watch
_do_build

# watch all the files in the project except for the output directory
find . -type d -not -path './gen*' -printf '%p ' | xargs inotifywait \
    -m -e close_write \
    --format %e/%f |
    while IFS=/ read -r events file; do
        echo "file $file modified, rebuilding"
        _do_build
    done

Now we can test the script by launching it scripts/watch.sh build.py.

You can checkout the source for the whole site here.