Moving from Middleman to Pelican on FreeBSD

I've had this sort of ongoing project to update my Middleman installation for this blog, and in the process of doing so, decided to re-evaluate and switch to a different production technology called Pelican.

Middleman is servicable software for generating static websites, and is written in Ruby. The officially approved and supported modules work. The unapproved ones, however, are corroded and do not work (for me) out of the box in a FreeBSD jail. As far as I can tell, people don't maintain them. As any Ruby user knows, if a Gemfile has like a million dependencies in it - it will go out-of-sync very quickly and become impossible for other people to use out of the box. In fact, none of the unsupported Middleman modules I tested worked under a FreeBSD jail RVM install of the latest stable Ruby.

So this is partly a Ruby thing, partly an RVM thing, and partly a Middleman unofficial modules thing. FreeBSD jails are non-negotiable for me. The software must work in a jail. Plus I want cool features, and an unbroken ecosystem. All the baggage is just a bit too much to maintain and retain when months may go by between blog posts. And I'm bored. not enough interesting features (like Pelican's Themes) and a broken ecosystem.

So with this situation, it's time for a new platform.

Some big pluses for Pelican:

It's elegant. Not an Elephant. I don't have to mess with frameworks to get it running.

INSTALLATION

## PELICAN INSTALLATION STEPS

# install python3
sudo pkg install python3

# install pip package manager for python
fetch https://bootstrap.pypa.io/get-pip.py
sudo python3 ./get-pip.py


# make shell Unicode
vi .login_conf
me:\
        :charset=UTF-8:\
        :lang=en_US.UTF-8:\
        :setenv=LC_COLLATE=C:

# check Unicodeness
locale

# install pelican and some needed packages
sudo pip install pelican
sudo pip install Markdown
sudo pip install typogrify
sudo pkg install gmake

That's all. No RVM muckity-muck or much in the way of shell-dotfile-trickery. `Python3 is installed via the FreeBSD package system.

Configuration

To create a project, all I have to do is run pelican quickstart and some questions get asked, and a project directory tree gets built.

There is only one config file: pelicanconf.py

#!/usr/bin/env python
# -*- coding: utf-8 -*- #
from __future__ import unicode_literals

AUTHOR = 'Allan'
SITENAME = 'SyntaxFX'
SITEURL = 'http://abowhill.github.io'

PATH = 'content'
STATIC_PATHS = ['images','javascripts','examples']
# skips processing of all .html
READERS = {'html': None}

TIMEZONE = 'America/Vancouver'
DEFAULT_LANG = 'en'

# Feed generation is usually not desired when developing
FEED_ALL_ATOM = None
CATEGORY_FEED_ATOM = None
TRANSLATION_FEED_ATOM = None
AUTHOR_FEED_ATOM = None
AUTHOR_FEED_RSS = None

SITESUBTITLE="Tech Blog"

# Blogroll
LINKS = (('Pelican', 'http://getpelican.com/'),
         ('Python.org', 'http://python.org/'),)

# Social widget
SOCIAL = (('Facebook', 'https://www.facebook.com/allan.bowhill.1/'),)

DEFAULT_PAGINATION = 10

# Uncomment following line if you want document-relative URLs when developing
#$RELATIVE_URLS = True

Project layout is very simple. There are just content and output directories that your should care about. The content directory is where you put your markdown for blog articles. The output directory is where your html gets compiled.

Beneath content you may create other directories to include in the site. Photos and javascript directories are easily handled by Pelican's STATIC_PATHS config file setting. Files in these directories (as long as they are not markdown or html) will be included in the built output tree unmodified.

One niggle about configuration: the READERS = line in the above pelicanconf.py has to be there if you want to be able to insert and publish raw HTML verbatim, not to be processed by the framework. I had to supress compilation of html because I needed a place to store html files verbatim, without exposing them to the compile process. Some Javascripts for example will not play well with certain complex Javascript layout frameworks like Gumby and need to be isolated in their own space. If you don't use this READERS setting, Pelican will aggressively try to embed any html it finds into the layout framework's wrapper, or in some cases, simply not include it in the files to be uploaded to your site.

Development

The development process can be done several ways, most of which can be controlled from the Pelican Makefile. You just enter: gmake for a help screen:

[devblog]$gmake
Makefile for a pelican Web site

Usage:
   make html                        (re)generate the web site
   make clean                       remove the generated files
   make regenerate                  regenerate files upon modification
   make publish                     generate using production settings
   make serve [PORT=8000]           serve site at http://localhost:8000
   make devserver [PORT=8000]       start/restart develop_server.sh
   make stopserver                  stop local server
   make ssh_upload                  upload the web site via SSH
   make rsync_upload                upload the web site via rsync+ssh
   make dropbox_upload              upload the web site via Dropbox
   make ftp_upload                  upload the web site via FTP
   make s3_upload                   upload the web site via S3
   make cf_upload                   upload the web site via Cloud Files
   make github                      upload the web site via gh-pages

Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html

[devblog]$

gmake devserver displays the website on local port 8000 while you edit markdown. gmake stopserver shuts down the dev server. I haven't tried the upload commands yet.

Conversion of Articles

Writing articles in markdown is almost identical to doing it in Middleman, so the articles translate reasonably well. Headers are not the same between the two platforms. Pelican uses more optional information:

Title: My super title
Date: 2010-12-03 10:20
Modified: 2010-12-05 19:30
Category: Python
Tags: pelican, publishing
Slug: my-super-post
Authors: Alexis Metaireau, Conan Doyle
Summary: Short version for index and feeds

Middleman uses less, in my config:

layout: single
title: 'Using Opal to Generate JavaScript'
tags: ruby, opal, www

Converting headers between then required a crude script (beware: it's hackish) and an ERB template:

require 'erb'
require 'date'

class Convert

   attr_accessor :title, :date_a, :date_b, :slug, :mainfile, :tags, :template

   def initialize template
      @title = nil
      @date_a = nil
      @date_b = nil
      @slug = nil
      @mainfile = ""
      @tags = nil
      @template = template
   end

   def post_init
      cvt names
   end

   def names
      # writes copies of all *.html.markdown files in current directory to *.md!
      rbfiles = File.join("**", "*.markdown")
      Dir.glob(rbfiles)
   end

   def cvt(file_ary)
      file_ary.each do |file|
        basename = File.basename(file,".html.markdown")
        part = basename.partition(/\d+-\d+-\d+-/)
        @slug = part[2]
        @date_a = part[1].chop
        @date_b = DateTime.now.strftime ("%m-%d-%Y %I:%M:%S")
        @mainfile = read_header file
        header = render
        fh = File.new("#{basename}.md","w")
        fh.write(header + @mainfile)
        fh.close
      end
   end

   def read_header(fn)
      fh = File.open fn
      mfile = ""
      fh.each_line do |line|
         mfile += line
         line.chomp!
         @title = clean line,"title:" if line =~ /title:/i
         @tags = clean line,"tags:" if line =~ /tags:/i
      end
      mfile
   end

   def clean(line,type)
      part = line.partition type
      item = part[2]
      item = item.delete "'"
      item = item.delete "\""
      item.strip
   end

   def render
      @resolved = ERB.new(@template).result(binding).to_s
      @resolved
   end
end

template = File.read "convert.erb"
c = Convert.new template
c.post_init
Title: <%= @title %>
Date: <%= @date_a %>
Modified: <%= @date_b %>
Category: <%= @tags %>
Tags: <%= @tags %>
Slug: <%= @slug %>
Authors: Allan Bowhill
Summary: <%= @title %>

Also, Pelican is less tolerant of certain things like uneven open and close code gating symbols, which I had to correct with in a Perl/sed editing pipelines and manual edits.

Selecting a Theme

This is one of Pelican's Big Strengths as a blogging platform. It has many themes (around 90) and they're all easy to test. Just run pelican-themes --list to list currently installed or linked themes. To get the entire themes collection you'll need git. Here's how to get all the themes, link to one called blue-penguin and serve it up on port 8000

# don't forget --recursive switch since many themes will be empty due to project linking
git clone https://github.com/getpelican/pelican-themes.git --recursive

# link your blog to the theme
sudo pelican-themes --symlink <path to pelican-themes>/blue-penguin

# Verify the themes registered
pelican-themes -l -v

# build and serve the new theme
gmake clean && pelican ./content -o ./output -t blue-penguin  && gmake serve

Just repeat the last three commands from the above code for each new theme you want to test. It's an eye opener and a great blogging platform overall!

Compiling with a Theme

gmake clean && pelican ./content -o ./output -t <theme-name> && gmake serve