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, task
–env
was originally imported fromfabric.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.