Setting up TILs in Pelican

June 21, 2022

Inspired by Simon Willison, I started writing TILs (today I learned). I find it incredibly helpful to write as I code, but most of that writing has never left my private notebooks. TILs are my attempt at documenting and sharing my day-to-day learnings in case they might help others. The focus on learning also feels less daunting than writing blog posts.

I wanted to support TILs on my blog as a separate set of posts with their own listing page. Thanks to Pelican's incredible flexibility, this was quite easy!

Following these steps requires using a custom theme. I personally use a custom theme (forked from the builtin simple theme) precisely so that I can easily make these sorts of customisations.

Table of contents

Reconfigure your archives

Start by renaming archives.html to posts/index.html (relative to your theme's templates directory).

Edit the loop over dates in posts/index.html to exclude articles tagged til:

{% for article in dates if 'til' not in article.tags|default([]) %}

Add the new path to DIRECT_TEMPLATES, the corresponding line of my pelicanconf.py now looks like:

DIRECT_TEMPLATES = ['index', 'posts/index']

... because I don't have tag or category pages yet. Disable the original archives page:

ARCHIVES_SAVE_AS = ''

It should be working as it was before, but we're now able to add a few more listings in the same way!

Create the TILs listing

Copy posts/index.html to tils/index.html, and edit the for loop to only include articles tagged til (note that the not from before is missing):

{% for article in dates if 'til' in article.tags|default([]) %}

Add the new path to DIRECT_TEMPLATES in your pelicanconf.py:

DIRECT_TEMPLATES = ['index', 'posts/index', 'tils/index']

You probably also want to link to the listing from your nav bar. For my theme, that's done by adding a line to the <nav> tag in my base.html template:

<a href="{{ SITEURL }}/tils/">TILs</a>

Hack article URLs

This is my favourite part! At this point, you should have two working listings, but TIL article URLs will be the same as any other article. Pelican determines the URL and output location of an article by calling format with the article's metadata on strings ARTICLE_URL and ARTICLE_SAVE_AS. That means we can implement a tiny string class with a custom format to dynamically set the URL of TILs to tils/{slug} and of posts to posts/{slug}!

Simply include the following in your pelicanconf.py:

class ArticleUrl(str):
    def format(self,tags=[],**kwargs): return ('tils/' if 'til' in tags else 'posts/') + super().format(**kwargs)

ARTICLE_URL = ArticleUrl('{slug}/')
ARTICLE_SAVE_AS = ArticleUrl('{slug}/index.html')

Update invoke task

If you're using live reload via the invoke livereload task, you'll need to update your task definition to include nested HTML files in your theme:

-    server.watch('{}/templates/*.html'.format(theme_path), lambda: build(c))
+    server.watch('{}/templates/**/*.html'.format(theme_path), lambda: build(c))