Setting up Pelican

How I set up this blog

NB: this blog has been ported to Ghost as of 2022-01

This blog post has been written in Markdown while setting up my Pelican instance.

Adding Scripts to your Fab File

Following Making a Static Blog with Pelican - Automation

This is a thing that should be easy but, because it's taking people a while to move to Python 3, it isn't.

Fabric has been updated to work with Python 3.4+ but not all of the bindings are the same. If you're working with the default fabfile you will need to change/add the following:

  • Comment out or remove
  • from fabric.api import * this has been deprecated and replaced with imports at the top level ( (http://docs.fabfile.org/en/2.1/upgrading.html#api-organization) )
  • import fabric.contrib.project as project NO BLOODY IDEA
  • the decorator @hosts(production) as these decorators have not been prioritised for the rewrite.
  • Add: from invoke import env, taskenv was originally imported from fabric.api, task has been ported and is extremely useful. Note: this refuses to work without the task decorator, would be good to have a better idea what's going on there.

Now you can carry on creating your make_entry function as you usually would:

from invoke import env, task
from datetime import datetime

TEMPLATE = """

Title: {title}
Date: {year}-{month}-{day}
Modified: {year}-{month}-{day}
Tags:
Category:
Slug: {slug}
Summary:
Status: Draft

"""

@task
def make_entry(c, title):
    today = datetime.today()
    translator = str.maketrans('', '', string.punctuation)
    slug = title.lower().strip().translate(translator).replace(" ", "-")
    f_create = "content/{}-{:0>2}-{:0>2}-{}.md".format(
                today.year, today.month, today.day, slug)
    t = TEMPLATE.strip().format(title=title,
                                year=today.year,
                                month="{:0>2}".format(today.month),
                                day="{:0>2}".format(today.day),
                                slug=slug)
    with open(f_create, "w") as w:
        w.write(t)

    print("File created -> " + f_create)

You'll notice that make_entry has c as the first argument, I don't currently know what that does, but without it you get the error No idea what 'make_entry' is!.

Once you've saved your file run fab --list which should return make-entry. As documented ( http://docs.pyinvoke.org/en/latest/getting-started.html#task-parameters ) you can invoke this by using a variety of methods including fab make-entry "Title".

Note: if you're using a title with more than one word in it (highly likely), don't forget the inverted commas.

Addendum:

If you would prefer this to automatically open your file on creation (and I can't think of a situation where I'd want to create the file and not then edit its contents) import run from invoke and then replace the print statement with run('atom {}'.format(f_create)).

Theming

After having a quick look at some of the available responsive themes I decided to throw my own together so I shimmied over to Pelican Docs > Creating Themes for some guidance.

Templates are created using Jinja2, however, don't try to be clever and name the files *.jinja2 instead of *.html, the devserver will have no idea what to do with them and will completely ignore them.

The "Simple" theme gives you a good basic structure that you can use to bootstrap your site. I, like many other developers have a basic layout setup which I recycle, usually with some changes. Things I used:

Slidey sidebar

One of the things I really like about the Decode WordPress theme that I was using on my previous blog is the fact that the sidebar is off screen unless it's needed. This actually isn't a complicated thing to achieve so I recreated it:

HTML

I'm using this as a navigation, since tags and categories provide a more more useful way of sorting the content of this kind of blog than a chronological menu would.

<nav id="sidebar" role="navigation">
    <i id="menu-hide" class="fas fa-times btn float-right" role="button" data-toggle="sidebar" type="button"></i>
    <div class="pages">
        <h4>Pages</h4>
        <ul class="list-unstyled">
            {% for page in pages %}
            <li><a href="{{ SITEURL }}/{{ page.slug }}">{{ page.title|striptags }}</a></li>
            {% endfor %}
        </ul>
    </div>
    <div class="tag-cloud">
        <h4><a href="/tags">Tags</a></h4>
        <ul class="list-inline">
            {% for tag in tag_cloud %}
                <li class="tag-{{ tag.1 }} list-inline-item"><a href="/tag/{{ tag.0 }}/">{{ tag.0 }}</a></li>
            {% endfor %}
        </ul>
    </div>
    <div class="categories">
        <h4><a href="/categories">Categories</a></h4>
        <ul class="list-unstyled">
            {% for category in categories %}
                <li><a href=/category/{{ category[0] }}>{{ category[0] }}</a></li>
            {% endfor %}
        </ul>
    </div>
    <div class="google-advert" aria-hidden="true">
    </div>
</nav>

In order for this to be accessible I need a "hamburger" or equivalent visible at all times, as it needs to sit over the top of everything else I put it in the main body of the page, rather than inside either the header or body containers:

<i class="fas fa-bars btn text-light" id="menu-show" data-toggle="sidebar" type="button" role="button"></i>

SASS

#menu-show {
    padding: 1rem;
    position: fixed;
    left: 0;
    top: 0;
    font-size: 2.5rem;
}

#sidebar {
    width: 18.75rem;
    height: 100vh;
    padding: 1rem;
    box-shadow: 1px 0 0.9375rem rgba(0,0,0,0.5);
    background-color: white;

    position: fixed;
    left: -20rem;
    top: 0;

    overflow-x: hidden;
    overflow-y: scroll;

    transition: all 0.5s ease;

    .tag-1 {
        font-size: 2.5rem;
    }

    .tag-2 {
        font-size: 2rem;
    }

    .tag-3 {
        font-size: 1.75rem;
    }

    .tag-4 {
        font-size: 1.5rem;
    }
}

.show {
    left: 0 !important;
    top: 0;
    transition: all 0.5s ease;
}

jQuery

$('#menu-show').click(function(e){
    e.preventDefault();
    $('#sidebar').toggleClass('show');
})
$('#menu-hide').click(function(e){
    e.preventDefault();
    $('#sidebar').toggleClass('show');
})

Done.

Tagline

Again a thing that's been pulled over from my original WordPress blog. I'm kinda attached to it, and it's pretty simple to setup. Just add TAGLINE = "My awesome tagline" to your pelicanconf.py and call it in your template in the same way as any other variable {{ TAGLINE }}.

Cover image(s)

I wrote a quick plugin to make this happen - I've covered this before Writing a Pelican Plugin to output a carousel.

Outputting them as part of the entry content is relatively simple: check they exist then iterate over them.

{% if article.cover %}
    <div class="cover-image">
    {% for image in article.cover %}
        <img src="{{ image }}">
    {% endfor %}
    </div>
{% endif %}

I wanted to show just the middle of the image(s) so that they wouldn't dominate the whole screen so I added a bit of SASS to hide the majority of the image:

.cover-image {
    height: 25vh;
    max-width: 100%;
    overflow: hidden;
    img {
        transform: translateY(-50%);
    }
}

Removing the cruft from the URL

This is done via the settings file as explained beautifully clearly in Custom Urls with Pelican

I have used the following:

ARTICLE_SAVE_AS = '{slug}/index.html'
ARTICLE_URL = '{slug}'
PAGE_SAVE_AS = '{slug}/index.html'
PAGE_URL = '{slug}'

Plugins

In order to use plugins you need to tell Pelican where to find them and which of the things it finds in the given location it's to load.

PLUGIN_PATHS = ["plugins"]
PLUGINS = ["tag_cloud"]

Tag Cloud

A lot of people think that tag clouds have had their day, however, for this kind of blog, tags and categories are arguably considerably more useful than archives.

The standard tag cloud plugin is Pelican Tag Cloud, I set this up using list-inline and list-inline-item, I then defined the tag sizes to match Bootstrap's header sizes for simplicity.

<ul class="list-inline">
    {% for tag in tag_cloud %}
        <li class="tag-{{ tag.1 }} list-inline-item"><a href="/tag/{{ tag.0 }}/">{{ tag.0 }}</a></li>
    {% endfor %}
</ul>

Importing all my old posts from WordPress

This took a few goes... I initially tried Pelican Import, it output one post beautifully then failed horribly.

The next thing I tried truncated all my posts leaving only excerpts and comments.

Finally I found WordPress to Markdown (Jekyll Posts) which worked beautifully. Yes, I know I could have written my own script to do this and have it output exactly the right format for Pelican. But why reinvent the wheel and this is close enough. Especially as all of my code had been mullered by the transport anyway so I had to read through and straighten it out.

Comments powered by Talkyard.