55 Minutes

Welcome to the 55 Minutes blog.
55 Minutes is a web development consultancy in San Francisco building apps for design-led businesses and startups. On this blog we discuss the technology tools of our trade: Ruby on Rails, Django, Sass, OS X, and more.

Migrating from Tumblr to Jekyll

We recently switched away from Tumblr to a blog engine called Jekyll. In this post I'll talk about our reasons for doing so, and some of the things we learned along the way.

Table of Contents

  1. The Problem
  2. Jekyll Bootstrap
  3. Environment
  4. Importing old posts
  5. Markdown parsing with Redcarpet
  6. Liquid and Jekyll templating
  7. Compass and Sass
  8. Syntax highlighting with Pygments
  9. Draft/preview/publish
  10. Multiple authors
  11. More to come…
  12. Finally

The Problem

Due to downtime and other issues with Tumblr, we decided to explore our options for migrating to another blogging engine. We wanted a simple, file-system-based solution that would allow us to use version control, write our posts the way we like (in a text editor using Markdown), and with enough flexibility/pluggability that we could add on custom features easily. And we wanted something that was light on server requirements. After some research, we settled on Jekyll, a parsing engine that generates static HTML. However, migrating to the new system wasn’t as simple as it seemed.

Jekyll Bootstrap

Out of the box, Jekyll is incredibly basic: it scans a directory of blog posts written as Textile or Markdown files, translates those files to HTML, and performs some simple templating using Liquid tags. The result is a static website that can be hosted on any web server.

However, to build an actual blog, you need more than just the posts themselves: you need an RSS feed, a paginated list of recent posts, archive pages, and so on. This is where Jekyll Bootstrap comes in.

JB is a quickstart for Jekyll-based blogs that includes these necessities, along with tag and category index pages, a theming API, and other conveniences. Consensus in Jekyll how-tos around the internet seemed to be that one should start a new Jekyll-based blog by cloning and tweaking JB, so that’s what we did.

Environment

What we wanted to do with Jekyll required installing both Python eggs and Ruby gems. To keep the environment setup clean and repeatable, we created a Python virtual environment using virtualenv and virtualenvwrapper as well as an RVM gemset.

Importing old posts

As I said, we were on Tumblr, so we used Jekyll’s built-in Tumblr migrator plugin. Originally we had used the modified importer that gets tags and imports the posts as Markdown, but that completely discarded all our custom markup, including special CSS classes for certain parts of our posts, and the <!-- more --> comments we used to break up posts (more on this later). We ended up re-migrating the posts as standard HTML to get all the raw data, then went in and manually stripped out the markup we didn’t want to get the bulk of the content into Markdown format.

Markdown parsing with Redcarpet

Jekyll by default uses Maruku to parse your Markdown. This didn’t work too well for us: we kept seeing errors like “REXML could not parse this XML/HTML” when Jekyll tried to parse our existing posts. Through trial and error, we determined that Redcarpet and rdiscount are better, more robust alternatives to Maruku.

To test Jekyll with Redcarpet, you can specify

jekyll --redcarpet

at the commandline; to always use Redcarpet, add the following line to your _config.yml file:

markdown: redcarpet

Note that the latest version of Redcarpet, v2.1.0, is not compatible with Jekyll—the version Jekyll supports is v1.9. When specifying redcarpet as a dependency in your Gemfile, make sure to explicitly prevent versions 2.x and newer from being used:

gem 'redcarpet', '~> 1.9'

Liquid and Jekyll templating

Our next step was to migrate our existing layout over as a Jekyll Bootstrap theme. Jekyll layouts and JB themes make use of Shopify’s Liquid templating engine. Now, if you’ve ever developed templates for Django before, you should be on familiar ground; Liquid is nearly identical to the Django templating language. I say “nearly;” Liquid is not quite as powerful as Django templates and doesn’t have all the same built-in tags and filters (I particularly miss the block tag, though thankfully it appears to be supported in the latest, unreleased code on Github). Never fear, though: it’s really easy to write your own.

Liquid follows the Django philosophy that the templating system is secure and thin, meaning that the dynamic code capabilities of the engine are intentionally limited—you can’t do much more than simple conditionals and iterations. In Django, this means that your views are lightweight in favor of heavier controllers. This will be particularly galling for Rails enthusiasts who are used to having much more power in view templates.

Nevertheless, Liquid did just about everything we wanted, and the rest of our needs could be met by adding custom fields to our posts' and pages' YAML front matter. For example, we wanted all our single post and page views to have the same CSS class on the body. Ideally, this would be solved by being able to define a block in our default template, but since Liquid doesn’t have that functionality yet, we just added a css_classes (and css_id, for good measure) field to each page’s and post’s YAML front matter. Yes, this is not perfect and can be error-prone, but it’s an acceptably easy workaround until the new version of Liquid supporting blocks is released. One thing we did to make it easier is to alter the rake tasks that create new posts and pages to include them in the default YAML front matter:

require "fileutils"

SOURCE = "."
CONFIG = {
  'themes' => File.join(SOURCE, "_includes", "themes"),
  'layouts' => File.join(SOURCE, "_layouts"),
  'posts' => File.join(SOURCE, "_posts"),
  'post_ext' => "md"
}

# Usage: rake post title="A Title"
desc "Begin a new post in #{CONFIG['posts']}"
task :post do
  mkdir_p CONFIG['posts']
  title = ENV["title"] || "new-post"
  slug = title.downcase.strip.gsub(/\s+/, '-').gsub(/[^\w-]/, '')
  filename = File.join(CONFIG['posts'], "#{Time.now.strftime('%Y-%m-%d')}-#{slug}.#{CONFIG['post_ext']}")
  if File.exist?(filename)
    abort("rake aborted!") if ask("#{filename} already exists. Do you want to overwrite?", ['y', 'n']) == 'n'
  end

  puts "Creating new post: #{filename}"
  open(filename, 'w') do |post|
    post.puts "---"
    post.puts "layout: post"
    post.puts "title: \"#{title.gsub(/-/,' ')}\""
    post.puts "author: #{ENV['USER']}"
    post.puts "tags: []"
    post.puts "css_id: "
    post.puts "css_classes: [ permalink ]"
    post.puts "---"
    post.puts
    post.puts
    post.puts "<!-- more -->"
    post.puts
    post.puts
  end
end # task :post
Rakefile-post-snippet.rb – View Gist

Compass and Sass

And let’s not forget Compass. This was relatively easy to fold into our theme’s assets directory; we just copied config.rb, scripts/, styles/, images/, etc. from our css3-foundation project and run Compass as usual.

Syntax highlighting with Pygments

Jekyll is built for programmers. And programmers who write blogs will most likely be posting code snippets that they will want to syntax-highlight. Now, you can of course use Github Gists for most of your needs, but do you really want to create a new Gist for every single-line code snippet? Does that mean you have to give up syntax highlighting for these small code snippets? Not at all. For code you want to syntax-highlight but not bother with creating a Gist for, you’d do something like this:

{% highlight ruby %}
puts "A simple ruby program."

{% endhighlight %}

The key bit there is {% highlight ruby}…{% endhighlight %}. That snippet of code triggers a Python-based syntax highlighting library called Pygments, support for which is built into Jekyll. Using one of its many lexers, Pygments will mark your code up with CSS classes that are styled by an included stylesheet for a highlighting color scheme (we’re using the Pastie theme). This way, we get nice-looking code snippets regardless of whether we use Gists.

Make sure you bookmark the page listing Pygments' default group of lexers; it’ll be invaluable the first few times you post code snippets. And don’t forget to turn on this feature in your _config.yml:

pygments: true

Draft/preview/publish

Our practice is to have someone review each blog post before it gets published, and so we need some kind of draft/preview/publish workflow. Now, there are plugins that do this, but if you store your blog as a GitHub repository (which we do), you get this functionality for free by way of GitHub’s pull requests. So when one of us writes a new post, we create a branch with the post, then open a pull request when it’s ready for review. Once a reviewer has read and approved the post, he or she simply merges the pull request.

…but there’s one small issue. Jekyll requires that post filenames are formatted as YY-MM-DD-slug.md (e.g. 2012-02-16-my-awesome-post.md); it’s how the post’s publish date and permalink URL are determined. Jekyll Bootstrap thoughtfully includes a rake task that prepends the date string representing the current day to the slugified post title. But what happens if the post is approved and published on a different day than the post was created? The publish date of the post will be wrong.

In that case, someone (probably the reviewer) will have to do one of two things:

  1. Change the filename of the post to match the publish date.
  2. You also have the ability to override the publish date derived from the filename by specifying it in your post’s front matter, in the format date: YYYY-MM-DD HH:MM:SS (simply YYYY-MM-DD works as well). However, having different dates in the filename and the front matter could lead to confusion, so we decided to stick with the first method.

Multiple authors

By default, Jekyll Bootstrap is set up for a single author. We had to hack the config a bit to get it to work for our multiple author case, but it was really simple. Following the instructions in a blog post, you simply restructure the author section in _config.yml to sit under a new authors section, and replicate the author section per writer on your site. For example:

authors:
  hanzou:
    name: Hanzou Hattori
    display_name: Hanzou
    gravatar: c66919cb194f96c696c1da0c47354a6a
    email: hanzou@company.com
    web: http://company.com
    twitter: company
    github: hhattori
  jorgen:
    name: Jörgen Mortensen
    display_name: Jörgen
    gravatar: 12e480a364a5c19214f99b4dfe9a11d5
    email: jorgen@company.com
    web: http://company.com
    twitter: company
    github: jorgenm
_config.yml – View Gist

Then, in your post template, you’d do something like:

{% assign author = site.authors[page.author] %}
<article class="entry">
  <header>
    <p class="date">
      <span class="month">{{ page.date | date: '%b' }}</span>
      <span class="day">{{ page.date | date: '%d' }}</span>
      <span class="year">{{ page.date | date: '%Y' }}</span>
    </p>
    <h1>{{ page.title }}</h1>
    <p class="byline">
      by {{ author.display_name }}
    </p> <!-- /.byline -->
  </header></article>
post.html – View Gist

And in your posts themselves, just use the author name from _config.yml as the post author in the YAML front matter:

---
layout: post
title: "Migrating from Tumblr to Jekyll"
author: jorgen
tags: [ Tumblr, Jekyll, tutorial ]
css_classes: [ permalink ]
---
yaml-front-matter.yml – View Gist

More to come…

This isn’t all we did; read our follow-up on how we handle Gists.

Finally

It’s a non-trivial thing migrating to Jekyll. We’ve done a lot to get this new system set up, and we’ll probably be tweaking things for a while yet. But what we learned out of this experience is that while it is not a picnic to set Jekyll up to your specifications, once you do, you can have a nearly pain-free blogging experience, with minimum fuss and maximum control.

comments powered by Disqus