Combining Jekyll, Haml and Sass with GitHub Pages

Posted on Jan 16, 2011

Using Git introduced me to the wonderful services of GitHub. One of the many great features of GitHub is something known as GitHub Pages. To quote the GitHub Pages intro:

The GitHub Pages feature allows you to publish content to the web by simply pushing content to one of your GitHub hosted repositories. You can use GitHub Pages for the website of your project or as a ‘User Page’. GitHub uses Git’s post-commit hooks, to invoke Jekyll a Ruby based static site generator.

Enter Haml

Writing HTML by hand is somewhat tiresome, Jekyll relieves this burden for the most part by providing rendering of Markdown or Textilize if that’s what you prefer. The layout pages still require you to write HTML though. Haml is a Rails view engine that has a really nice syntax and has the ability to execute Ruby code. After converting my default layout from HTML to Haml, I was really happy with the result. The structure was much more clear and no more of those missing end tags. Haml is not integrated into the stock Jekyll version, here is a Jekyll plugin, that does the trick. This plugin works great for the content and stylesheets but does not work for the _layout content. Therefor I decided to call Rake to the rescue and use the following rule to generate the HTML for the _layout directory

1
2
3
4

rule '.html' => ['.haml'] do |t|
    sh %{ haml -E utf-8 #{t.source} #{t.name.sub(/_haml\./,'.')} }
end

Note the non-standard output filename on line 2, this is needed because Jekyll does not use extensions when referring to layouts and it chooses any file in the _layout directory with the correct base name. With this rule in place I can generate my layout using Haml. Since I was rendering the layout HTML locally I decided to skip the plugin altogether and generate the CSS with Sass locallly as well. Since this also gives me more control and I can be sure that the page rendered locally will look exactly the same once everything is committed. For generating the CSS I use the following rule:

1
2
3
4
5


rule '.css' => ['.scss'] do |t|
    sh %{ sass -t compressed #{t.source} #{t.name} }
end

Generating a tag cloud

Using Haml in the layout page opens some interesting possibilities since we can embed Ruby code inside the template. I found some Rake snippets to generate tag lists using Jekyll’s api. Adapt that code a bit and Haml can generate the tag cloud using Jekyll. This in turn gets used as part of the layout for each post. Isn’t that a wonderful self referential system. Here’s the Haml code to produce the tag list you see in the right column.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18


%h3 Categories
  %ul
  :ruby
  require 'rubygems'
  require 'jekyll'
  puts "<!--"
  options = Jekyll.configuration({})
  site = Jekyll::Site.new(options)
  site.read_posts('')
  puts "-->"
  site.categories.sort.each do |category, posts|
      print "<li>"
      print "<a href=\"/tags/#{category.gsub(/\s+/,'-')}.html\">"
      print "#{category} (#{posts.length})</a>"
      print "</li>"
  end

Apparently site.read_posts outputs to stdout to avoid it from clogging up the page it’s output gets rendered as an HTML comment (line 6 and 10).

Generating pages for posts per tag

Also inspired by this gist I added a Rake task to generate a page for each used tag in the site containing all posts that refer to the tag. Here’s the code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40


namespace :tags do
  task :clean do 
    rm_rf "tags"
    mkdir "tags"
  end

  task :generate do
    puts 'Generating tags...'
    require 'rubygems'
    require 'jekyll'
    include Jekyll::Filters

    options = Jekyll.configuration({})
    site = Jekyll::Site.new(options)
    site.read_posts('')
    site.categories.sort.each do |category, posts|
      keywords = aggregate_keywords(category, posts)
      html= <<-HTML
+++
layout: default
title = "Posts tagged #{category}
"
keywords: [#{keywords}]
+++
HTML
      posts.each do |post|
        post_data = post.to_liquid
        html << <<-HTML
<h2><a href="#{post_data['url']}">#{post_data['title']}></a></h2>
#{post_data['content']}
HTML
      end
      File.open("tags/#{category.gsub(/\s+/,'-')}.md", 'w+') do |file|
        file.puts html
      end
    end
  end
end

To be able to fill the <meta keywords... each post puts keywords appropriate for the post in the Yaml Front Matter. The function aggregate_keywords just puts the category at hand and all the keywords from all posts in one set and returns them as a comma separated list, like so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15


def aggregate_keywords(category,posts)
  keywords = SortedSet.new()
  keywords << category
  posts.each do |post|
    post_data = post.to_liquid
    if post_data.has_key? 'keywords' 
      post_data['keywords'].each do |word|
        keywords << word
      end
    end
  end
  return keywords.to_a.join(',')
end

Next up from my blog setup backlog is a comments facility. If you’d like to use any of the code quoted above fork me on GitHub.