Smartening up haml in nanoc

I’ve been tidying up around the place. Do you like it? It feels a lot cleaner to me.

(RSS subscribers are encouraged at this point to follow the link and read the article on the website proper.)

For a while I’ve wanted to swap out my technically quite nice but aesthetically rather sterile sans-serif body font for a more friendly serif with a bit more readability, so with this slight redesign I’ve now gone and done that. However, I quickly got annoyed with markdown’s generic straight quotes, and started working out how to fix this.

The default answer for turning boring straight quotes into nice typographical quote marks, especially in a markdown-heavy environment, is to pull out John Gruber’s SmartyPants. I’m using Redcarpet to render markdown on my site, so it’s not too hard to build in SmartyPants support.

Markdown + SmartyPants

Redcarpet’s documentation shows you how to include smartypants: you just include Redcarpet::Render::SmartyPants in your renderer and everything works fine. Since I already have a custom renderer in my nanoc implementation, I simply added SmartyPants to that1:

1
2
3
4
5
6
7
module Redcarpet::Render
  class CustomRenderer
    include SmartyPants

    #...
  end
end

To use this renderer in nanoc, you just need to modify your Rules file slightly. Let’s say I want to render all my blog posts in markdown with SmartyPants:

1
2
3
4
5
compile "/posts/*" do
  if !item.binary?
    filter :redcarpet, renderer: Redcarpet::Render::CustomRenderer
  end
end

Done!

Haml + SmartyPants

There is a problem, and the problem looks like this:

1
2
3
4
5
6
7
8
# sample_document.haml

%h1 Haml page

%p This is a haml page, with a haml paragraph.

:markdown
  Now I'm filtering a section through markdown, but "these quotes" won't run through SmartyPants.

In cases like this, Haml has its own little markdown-rendering party and doesn’t invite Redcarpet or its custom renderer along for the ride. Worse, Haml ends up using Tilt as a templating engine, adding a further layer between us the metal. How can we get into the guts of the rendering process, telling Haml to tell Tilt to tell whatever it’s using to render this markdown inside the filter that it should be using SmartyPants?

Thankfully, this is a recurring problem, at least with Sinatra. Cameron Daigle, in the linked post, uses the following code in Sinatra as a means of getting Haml to play nice with SmartyPants:

1
2
3
4
5
6
7
class Tilt::HamlTemplate
  module ::Haml::Filters::Markdown
    def render(text)
      RDiscount.new(text, :smart).to_html
    end
  end
end

We can fix this up for nanoc and Redcarpet nice and easy. My code looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
require "tilt"

class Tilt::HamlTemplate
  module ::Haml::Filters::Markdown
    def render(text)
      @renderer ||= Redcarpet::Markdown.new(
        Redcarpet::Render::CustomRenderer,
        fenced_code_blocks: true,
                 footnotes: true
      )
      @renderer.render(text)
    end
  end
end

I don’t even have to tell Redcarpet to use SmartyPants here - its CustomRenderer knows to use it, and Redcarpet is perfectly happy letting the renderer do whatever it wants. This little code snippet goes into the lib/ folder along with the custom renderer, and you don’t need to do anything else. Haml will now route all its markdown-filtered blocks through Redcarpet, and the rest is magic.


  1. This file is stored in lib/, in the root of your nanoc site. Files in lib/ are auto-loaded, making this the perfect place to screw around with your nanoc install.