Making internal linking in pelican effortless

Translations: br
Jan 20, 2022

One interesting feature of Pelican, the static site generator I use for this blog, is the internal link expansion syntax with {}. It is documented here. Some examples are {filename}, {static} and {author}. The purpose of the syntax is to have shorter and easier aliases to link to internal content in the blog. For example, {filename} can be used to link to other files, like posts.

The idea is good, but to my needs it fell short. What I really need internal link expansions for in my blog are images, posts and code.


The idea for images is really simple. I write my posts in rst, and this is how an image is included in this format:

.. image:: path/to/image.jpg

The way I structure the files in the blog (which you can see in its repository), is that inside the content folder where all content is, the sources for the posts can be found inside articles/<year>/, and the images can be found at images/<post_id>/, where <post_id> is a string that identifies the post in which the image appears (it is the trans_id property of the post, derived by its filename and used to associate translations of the same post, but I'm calling it post_id here since it makes more sense in this context).

From this structure, if I use a relative path from a post to one of its images, it'd have to be something like ../../images/<post_id>/image_name.jpg. The {static} expansion can be used here to simplify it a bit: {static}/images/<post_id>/image_name.jpg. We can do better than this though 🙂.

It'd be way better if this path could be really shortened. All images are inside the images/ folder, so that should be implied. Heck, while we're at it, might as well make the <post_id>/ part be derived from the current post's id. That would make it perfect, since the only information left is the image name, which is the unique thing about the link.

The first step in implementing this custom logic was adding the linker plugin to pelican. It allows you to implement your own {} link expansions through python classes.

And the second step was implementing a {image} expansion through this commit. This is the commit where I updated all image links to use {image}. Feel the joy! It's so much neater 🙂.


Linking to posts is a little trickier, but not too bad. The idea is, I somewhat frequently want to link to another post I wrote previously on the blog. Using the {filename} expansion, it's not too bad:

I've shown that `in a previous post`__.

.. __: {filename}08-task-context-en.rst

But it could certainly be better. The {filename} expansion is relative to the current file, so if I'm referencing a post from the same year, it's like in the example above, but referencing one from a different year requires an additional ../<year>/ in the path. Also, I shouldn't need to write the whole filename. Ideally I'd only need to type what's unique to the post, that is, its post_id. Yes, even the language suffix (-en or -br) can be omitted, since I can derive it from the current post's language.

So far so good, it could be done with a bit more logic on top of what was done for {image}. But since I'm already improving things, I'd also like to take this chance to better standardize the text I use in the links. Sure, using a text like "in a previous post" blends well with the surrounding text, but it isn't immediately obvious that the link is to another post in my blog.

So the idea is to have a link expansion that not only maps to the right path to the post, but also automatically sets its text to have the post title. One final touch is that when setting the text I want it to also take into account the language of the post: if it's in English, the text should be in the format '"Title of the post" post' and if it's in Portuguese, 'artigo "Title of the post"'.

Updating the link's text is not something the linker plugin can do natively, so I first needed to extend it to enable that in this commit. With the basic mechanism in place, I actually implemented the new {article} expansion in this commit.

All that work pays off, as now I can show you how it looks by pointing to the very first post I wrote on this blog through a simple {article}tasklist: "Creating movie and game lists using Taskwarrior" post

Of course I also changed all references to blog posts to this new amazing expansion in this commit.


This is where it gets messy... You see, it's quite common for my blog posts, being technical, to have code blocks amidst the text. The way I include the code is by using the include rst directive and giving it the path to a separate file containing the code.

The issue is, unlike the image rst directive and rst links whose target appears in the final HTML, which makes it simple to edit them inside pelican, the include directive and its path are processed by the rst reader at an earlier stage. This means that the normal link tweaking methods in pelican can't be used here (like the linker plugin).

I could put the code inline instead of using the include directive and avoid this problem altogether, but when the code is more than a few lines, I feel like it would pollute the post source file too much to have it inline.

Well, if you've read the "Blog customizations" post you might remember that I already have a custom rst reader. So in order to implement a link expansion for code, I had to extend this reader to substitute any {code} occurrence inside an include directive with the code file's path. That's what I did in this commit, by copying the code from pelican's rst reader and making a few changes.

It's worth saying that this kind of customization I'm doing (and was already doing) by overriding the rst reader is not really stable. If there are changes in pelican's rst reader, I might need to either stop updating pelican or reimplement their changes in my own custom reader. But since there isn't really a less intrusive way of implementing this, I'm willing to take the risk.

Despite the ugly changes, in the end it's all worth it when you look at the improvements in the post sources. This is the commit where I updated them to use the new {code} expansion. I dare you to say it wasn't worth it (please don't say it).


And here we are. Took some work, but after these changes, internal linking on my blog is effortless! Anything that makes writing blog posts easier is well worth it to me.