Pelican: The Story So Far
I've used Wordpress (.org, not .com) for many, many years... and in fact developed PHP sites and WP plugins/themes full time for 2 years. It's a pretty decent, though certainly quirky, platform to develop on top of. Lately, I've taken an interest in somewhat more minimalistic web design as you can see on this site, with an emphasis on the CSS not even being necessary. You should be able to turn page styles off completely and the site still works fine -- how to do this varies from browser to browser (in Firefox/Librewolf, tap Alt
once to show the menu and go to View > Page Style > No Style).
Enter Pelican, a Python-based static web site generator. What makes it different from Wordpress? Well, first of all, it's very much a from the ground up kind of platform. That is, you're gonna have to get your hands dirty and learn to code. That's fine for me, but it might not be everyone's cup of tea... though I certainly encourage anyone to try some coding, and Python is a great language to learn first.
But the heart of what makes Pelican different is that the web site it generates is fully static. That is, all it does is output a folder full of .html
files which you then publish (upload) to your web server. There are some nice built-in tools that help make publishing a quick and easy process. Wordpress, by contrast, is itself installed on your web server, and renders the content on-the-fly (using the PHP interpreter and SQL queries into a database) each time someone visits your site.
What's funny to me is that pre-processing static files is essentialy how web sites used to be developed in the internet's early days, before "server side processing" -- technologies like MS's Active Server Pages and PHP -- became the standard. I remember making web sites with Microsoft FrontPage, which was just a GUI on top of a similar process. Though the themes built into it were terrible, even to my 1990s eyes, and the templating system was difficult to make sense out of (What? In a Microsoft Product?). Pelican represents a shift back to Web 1.0, in a certain way, though we've learned a lot and made our browsers much more capable since then. So don't worry, your Pelican site doesn't have to look like Geocities -- though it can if you're into that sort of thing.
Why would anyone want to use a static site generator? Well, for one thing, if all your webserver is doing is serving static files, the attack surface of the server drops dramatically. There is no web-based "admin interface" running on the web server, so there isn't really anything for an attacker to hack into... no passwords to compromise, no place to inject SQL statements, etc. (There are of course OS and webserver level vulnerabilities still, but those are far less common than PHP application exploits.) The other big plus is that your site runs fast... because all the content is pre-rendered, all the web server has to do is transfer it to a visitor's web browser.
There are, of course, downsides... as of now, there isn't a GUI (that I know of) for Pelican, meaning you're just messing around in text files when you update your web site. I am completely at home in this kind of situation, but if you have a site that's updated by multiple people with varying skill levels, it's certainly less than ideal. And a site that requires storing data on the server, even temporarily, isn't really possible with Pelican alone... so e-commerce sites are probably out unless you're a JavaScript whiz. Which I am not.
Pelican makes the process of updating the site far less cumbersome than just directly updating HTML, though. By allowing you to write your pages in a markdown format, which is basically just a glorified text file with some conventions on formatting, composing articles complete with inline formatting is a breeze. For example, the entirety of the source code for the "home page" on this site looks like this:
Title: Home
save_as: index.html
slug: index
status: hidden
Welcome to an obsolete computer
where binary search trees
and sweet birch trees can grow
from the same organic heap.
That's all. The rest of the page... the layout, the colors, and the navigation menu... are all built around it automatically by Pelican through its templating system each time a site update is run. The first 4 lines are metadata for the page, telling Pelican how to render it: The title, the filename, the "slug" (an internal identifier that can be used in the templates), and the "hidden" status meaning the page should not show up on the navigation menu like other pages do.
The templates are similarly simple, and like Django, they use the Jinja language. Here is the template for this page (and all articles):
{# themes/c64/templates/article.html #}
{% extends "base.html" %}
{% block title %}{{ SITENAME }} - {{ article.title|striptags }}{% endblock %}
{% block content %}
<article>
<h1>{{ article.title }}</h1>
<section class="post-info">
> Written {{ article.locale_date }}
</section>
{{ article.content }}
{% if article.modified %}
<span class="modified">Modified {{ article.locale_modified }}</span>
{% endif %}
</article>
{% endblock %}
This file itself inherits from base.html
, which contains the HTML that is present on every page, such as the <head>
section and the <div>
s that wrap the content, allowing it to be styled in the CSS. And speaking of the CSS, I'm using a pre-processed language called Less, which is compiled into minified CSS at the same time as all the other templates are rendered. This is accomplished with a Pelican plugin called "Webassets". Here's a snippet of the file c64.less
(as of today), where the anchors (links) are styled:
a, a:link, a:visited {
color: @lightblue;
background: transparent;
text-decoration: none;
nav &:before, footer &:before {
content: '=>\00a0';
}
article & {
background-color: @lightblue;
color: @blue;
}
&:hover {
background-color: @white;
color: @blue;
text-decoration: none;
article & {
background-color: @white;
color: @blue;
}
}
img {
border: none !important;
background-color: @lightblue;
padding: 4px;
filter: grayscale(50%);
}
&:hover img {
background-color: white;
filter: grayscale(0%);
}
}
Note the heavy use of variables, i.e. @lightblue
and the like. I have a section at the beginning of the file that defines a tweaked version of the Commodore 64 pallete, so I can use those colors elsewhere (feel free to use in your own C64-styled web sites):
/*
* C64 Color Pallette, Adjusted...
* based on the pallette at
* https://lospec.com/palette-list/commodore64,
* with some tweaks of my own
*/
@black: #000000;
@white: #FFFFFF;
@red: #9f4e44;
@cyan: #6abfc6;
@purple: #a057a3;
@green: #5cab5e;
@blue: #0e1627;
@yellow: #c9d487;
@orange: #a1683c;
@brown: #6d5412;
@lightred: #cb7e75;
@darkgrey: #444;
@grey: #898989;
@lightgreen: #9ae29b;
@lightblue: #99a3b4;
@lightgrey: #adadad;
Another feature that has made life a little easier is the ability to run macros within the Markdown source files, thanks to the jinja2content
plugin. Essentially this adds a Jinja2 preprocessor that runs before Pelican itself, and lets you use {{ jinja2 tags }}
which will render into the Markdown. To see what I mean, here is how I add an image gallery to an article:
{{ gallery([
"/static/2010/06/front.jpg",
"/static/2010/06/c642.jpg",
(add any number of additional images here)
],"Photo of Blacktown Sound Labs") }}
The source for the gallery
macro looks like this:
{% macro gallery(sources, alt='',classes='') -%}
{% for source in sources %}
[![{{ alt }} - {{loop.index}} of {{loop.length}}]({{ source }} "{{ alt }} - {{loop.index}} of {{loop.length}}"){.image-process-gallery-thumb {{ classes }} }]({{ source }})
{% endfor %}
{%- endmacro %}
Note that the macro is rendering markdown, not HTML directly (though that's possible too). This is because I want the ability to translate my .md
files into .gmi
so that I can run a Gemini server -- more on that soon, I'm still figuring it out.
So of course with my C64-centric theme, I had to make a macro that would generate the classic color bars:
{{ colorbars() }}
There's one other useful plugin that I'm using that I'll mention before wrapping up: the image-process
plugin. Its purpose is to crop and scale all your source images according to some rules you set in your config file. I ended up making a custom crop function for my gallery thumbnails, like so:
def gallery_thumb_crop(image):
new_size = 170
width, height = image.size
aspect = float(width / height)
if height > width:
image = image.resize((new_size, int(new_size / aspect)))
else:
image = image.resize((int(new_size * aspect), new_size))
width, height = image.size
left = (width - new_size)/2
top = (height - new_size)/2
right = (width + new_size)/2
bottom = (height + new_size)/2
return image.crop((left, top, right, bottom))
This does the equivalent of background-size: cover;
on a square <div>
in CSS: It makes sure the image is just big enough to fill a 170px square, cropping out the portions of the image that don't fit in that square.
So that's where I'm at with Pelican so far. It's been a lot of work to convert an entire web site, but it's been a great learning process. It's also very satisfying seeing the site come together in a clean and precise way -- in contrast with all the gobbeldygook and bloat that comes along with Wordpress these days. I guess you could say I'm a hacker at heart (though not in terms of skill level), and using a graphical interface just doesn't quite scratch that hacker itch. Maybe I'll be motivated to write some things here more often now.
Modified Wednesday, August 25, 2021