Support my work ♥

Blog Bugs - not migrating to Zola (for now)

In the last week I experienced the limits of the pelican blog software. My workflow includes saving work between steps in git. However, when I switch machines I have to push these commits.

The result is, that unfinished articles end up on the public page. For that I am sorry.

This weekend I looked into Zola as a replacement. It has some very compelling features:

  • Has an option to render drafts locally, this would solve my problem.
  • Live Reload, very comfortable.
  • It is faster, but since pelican takes ~2 seconds to render the whole page I am okay with this kind of speed.

However, it is very hard to keep the dates visible in the URLs. So /2021/3/28/blog-bugs/ would just be /blog-bugs/ or /posts/blog-bugs/.

There is a workaround for that by placing files in every folder:

title = "2018"
sort_by = "date"
template = "section.html"
page_template = "page.html"
transparent = true

My blockers for using Zola are:

  • Transparent option should cascade down so I don't have to specify it on every level
  • More compatible integration for Categories and Tags

In the end I would have to make the theme work with the new generator again, but that is just work that has to be done.

Pelican to Zola Migration Script

For completeness, I wrote this script to migrate most of my content. If this helps you, please let me know.

#!/usr/bin/env python3
# LICENSE: GPLv3 with attribution

import glob
from pathlib import Path
from dateutil import parser as timeparser
import os

for e in glob.glob('../*'):
    p = Path(e)

    if p.is_dir():
        print("is dir, aborting")
    if p.suffix != ".md":
        print("is not .md, aborting")

    date = p.stem.split("_")[0]
    d = timeparser.parse(date)

    dirname = "content/{}/{}/{}/".format(d.year, d.month,
    name = "content/{}/{}/{}/{}{}".format(d.year, d.month,, p.stem.split("_")[1], p.suffix)

    os.makedirs(dirname, exist_ok=True)

    with open(e) as r:
        lines = r.readlines()
        in_header = True
        content = []
        headers = {
            "date": "{}-{:02d}-{:02d}".format(d.year, d.month,,
        taxonomy = {}

        for line in lines:
            if in_header:
                line = line.strip()
                if line == "":
                    in_header = False
                    print("   line: {}".format(line))
                    (key, val) = line.split(':', 1)
                    if key == "Tags":
                        taxonomy[key.lower()] = list(map(lambda t: t.strip(), val.split(',')))
                    elif key not in ["Date", "Lang", "SocialImage"]:
                        if key == "Summary":
                            key = "description"
                        headers[key.lower()] = val.strip()

        ## make header
        with open(name, 'w') as w:
            headers['slug'] = headers["title"].lower()
            for k, v in headers.items():
                if k in ["status", "category"]:
                elif k in ["title", "description", "slug", "path", "template"]:
                    w.write('{} = "{}"\n'.format(k, v))
                    w.write('{} = {}\n'.format(k, v))
            for k, vs in taxonomy.items():
                s = None
                for v in vs:
                    if s is None:
                        s = ""
                        s += ", "
                    s += '"{}"'.format(v)
                w.write('{} = [{}]\n'.format(k, s))