Context
When porting my blog from Drupal to Pelican, I wanted to preserve
my clean URLs as much as possible for old blog entries.
In Pelican you typically achieve this by making sure each article has
a slug
metadata entry with the desired clean URL and setting up
_SAVE_AS
and _URL
configs in pelicanconf.py
for example
as follows:
ARTICLE_SAVE_AS = '{slug}/index.html'
ARTICLE_URL = '{slug}/'
In addition (or instead) you can also play with rewrite rules, if you have control over this on your setup. Because I'm planning to host my blog on GitHub Pages, I don't.
However, the above ARTICLE_URL
setting means that each article will
produce a directory with a single index.html
file, which felt a
bit too much as overkill to avoid where not necessary.
So config-wise, for new articles I wanted something more like this (the default):
ARTICLE_SAVE_AS = '{slug}.html'
ARTICLE_URL = '{slug}.html'
How to combine these?
Dynamic Pelican settings
Turns out it is possible to combine both with "dynamic" settings
that allow you to add a touch of logic to the configuration settings.
The trick below is based on the fact that Pelican interpolates
config variables like ARTICLE_SAVE_AS
and ARTICLE_URL
to
actual values for each article with something like
setting.format(**metadata)
So we just have to assign objects with a custom .format(**kwargs)
method to the config variables.
First, let's define this helper class:
class DynamicSetting(object):
def __init__(self, f):
self.f = f
def format(self, **metadata):
return self.f(metadata).format(**metadata)
On construction of a DynamicSetting
object, we pass it a function f
to generates a variation of the setting (e.g. {slug}
or {slug}.html
)
based on metadata.
For example, say that for articles where we want to enforce a
certain clean URL, we use a custom metadata entry CleanUrl
, like:
Title: hello world
CleanUrl: hello
Hello world
and for articles where we don't need a particular clean URL we don't.
With the helper class of above, we can now define ARTICLE_SAVE_AS
and
ARTICLE_URL
dynamically as follows:
ARTICLE_SAVE_AS = DynamicSetting(lambda metadata:
'{cleanurl}/index.html' if 'cleanurl' in metadata else '{slug}.html'
)
ARTICLE_URL = DynamicSetting(lambda metadata:
'{cleanurl}/' if 'cleanurl' in metadata else '{slug}.html'
)
So for articles where there is CleanUrl
metadata we'll use that for
output directory path and URL.
For other articles we fall back on the normal {slug}.html
way.
Note that it is also possible to use the helper as a decorator
@DynamicSetting
def ARTICLE_URL(metadata):
if 'cleanurl' in metadata:
return '{cleanurl}'
else:
return '{slug}.html'
which gives bit more breathing room for the custom logic.