My blog software

by Ketil; March 30, 2012

Twan van Laarhoven recently wrote about his home-grown blogging software, implemented in PHP. He stands tall, making no excuses, and I must admit the site looks nice. As posted previously, I have chosen to use Hakyll to implement this site, and I thought I’d share some recent experiences.

Hakyll is a templating system, so it consists of a set of HTML templates and a Haskell program that expands them and builds a set of static pages constiuting the site. There are an assortment of auxiliary files, like images and css that are just copied. Post content is written in markdown1, and metadata specified at the top is available to the template processing.

This setup has some advantages: I can develop code or textual content on my laptop, build, and check the site by accessing a file:// URL. I use darcs version control, and when I’m happy with the result, I simply darcs push the changes to the server, and run hakyll.hs to rebuild the site. Also, the site being static, there are few holes to exploit (which were my main reason for moving off Wordpress).

However, although there’s a blog example included with Hakyll, it’s not entirely ready to use. There are plenty of batteries are included, but you need to fit them correctly, so to speak.

Fixing the RSS feed

In a fit of hubris, I emailed the maintainer of Planet Haskell to ask to be included. The quick and brief reply stated that my site, and in particular, its RSS feed, was not deemed adequate. So I started to implement the list of required fixes:

Dates were missing.

By default, Hakyll assumes that posts are named according to date, and sets data variables from the file name. I don’t care much for that, so to get a reasonable date field in the RSS, I added a published field to each post’s metadata:

published: 2012-01-06T15:00:00Z

The default RSS rendering function looked like this:

-- Render RSS feed
match  "rss.xml" $ route idRoute
create "rss.xml" $
     requireAll_ "posts/*" >>> renderRss feedConfiguration

In order to sort on the published field, I added a custom sort:

match  "rss.xml" $ route idRoute
create "rss.xml" $ requireAll_ "posts/*"
   >>> arr (reverse . chronological)
   >>> renderRss feedConfiguration

chronological = sortBy $ comparing $ getField "published"
  

The RSS file was missing post contents.

It’s a bit unclear to me what the description field is supposed to contain in RSS; Wordpress has this “more” thing, and includes stuff above it, typically an introduction, but most people seem to just include the post contents.

The easy way out is to include the post body, and the easy way to do that, is to just copy it into a variable. Here’s the diff:

      match "posts/*" $ do
          route   $ setExtension ".html"
          compile $ pageCompiler
 +            >>> arr (copyBodyToField "description")
              >>> applyTemplateCompiler "templates/post.html"
              >>> applyTemplateCompiler "templates/default.html"
              >>> relativizeUrlsCompiler

Hopefully, my RSS feed will pop up in Planet H. real soon now, in the meantime, why, you’ll just have to subscribe directly, won’t you?

Other tweaks

Now, since the description field holds the whole post contents, I changed the metadata field to summary (since that’s what it contained). To actually make use of this, I added this variable to templates/postitem.html, the template responsible for rendering postitems, that is, items when posts are listed.

 <li>
     <a href="$$url$$">$$title$$</a>
     - <em>$$date$$</em> - by <em>$$author$$</em>
+    <p class="noindent">$$summary$$</p>
 </li>

One important problem is that I’m sometimes sloppy, and I want site compilation to fail if there’s something wrong - if, say, I forgot to specify an important field. For this, I can use trySetField, which only evaluates its argument if the field isn’t already present. Having the value be error thus will halt compilation:

match  "rss.xml" $ route idRoute
create "rss.xml" $ requireAll_ "posts/*"
   >>> arr (reverse . chronological)
   >>> arr (map (trySetField "summary" $ error "Missing field: description"))
   >>> arr (map (trySetField "published" $ error "Missing field: published"))
   >>> renderRss feedConfiguration

In addition, I’ve made minor edits to template and CSS, but although I use cooler software than Twan, I think it’s evident that he has greater CSS-fu, so I’ll spare you the details…

Shoulders of giants, or forever voyaging through dark seas?

As you may have noticed, there are no forum included. My WP forum drowned in spam, so this is at least 50% intentional. I have included my email address, so if you have something interesting to say, I’ll update the post, taking it into account. Otherwise, discuss away on G+ or Reddit - that’s what they’re for.

All in all, I’m pretty happy with this. There is a certain amount of yak-shaving involved, but on the other hand: infinite flexibility and reuse of awesome code like pandoc, as well as excellent support (hint: Jasper spends all his spare time hanging around on #hakyll, just itching for you to bring forth your complaints.)


  1. Hakyll uses pandoc, so it supports a variety of formats automatically, but I’m only using markdown.

comments powered by Disqus
Feedback? Please email ketil@malde.org.