Zameer Manji

Rocking and Rolling with Nanoc

Created On:

When I began to design this website I decided that it should be a static site. The benefits of a static site are numerous:

Essentially, I can use the tools I use every day while programming to manage my blog which is very appealing.

I had a lot of options on choosing the framework to power my site and after I evaluated all of them, I settled on nanoc, a Ruby powered static site compiler. I selected nanoc over popular alternatives such as, jekyll and octopress because nanoc gives me a high degree of customizability. For something that is personal as my blog I preferred something that allowed me to customize everything.

By adopting nanoc I was able to get a static site where I can name and layout however I want. I can also create files in any format that I want. Currently I have all of my blog posts written in pandoc inside content/posts with arbitrary file names like blog-post-about-nanoc.pandoc. I didn't have to adopt a foreign convention or monkey patch anything to get what I wanted. Other features that I have:

I was able to do this in nanoc because it is extremely easy to extend and ships with a large set of features.

Details on Nanoc

Nanoc is definitely one of the best maintained static site compilers I could find. The author, Denis Defreyne, uses it for his personal website and regularly answers questions on the mailing list and IRC channel. It also has very comprehensive documentation, something that many other alternatives lack.

Nanoc Terminology

A nanoc site is composed of the following key folder's and files:

The core of my site is writing blog posts in pandoc and adding syntax highlighting to those posts using pygments. I am going to show the code which results in this to show how easy it is to customize nanoc.

Compile Rule

For this blog I had to define multiple compile steps in order to get HAML -> HTML, Pandoc -> HTML, SASS -> CSS, CoffeeScript -> JavaScript and LaTeX -> PDF transformations. These transformations are a result of the filters that nanoc ships with or filters that I wrote.

The compile rule for blog posts:

compile '/posts/*/' do
  ext = item[:extension].nil? ? nil : item[:extension].split('.').last
  if ext == 'pandoc'
    filter :pandoc
    filter :pygments
  else
    raise "Unknown ext: #{ext} with item: #{item.attributes}"
  end

  layout 'default'
end

This rule operates on all the files that are in the content/posts/ and for each item I pass it through two filters, the pandoc filter and the pygments filter. The first transforms the post in to HTML, and the second adds syntax highlighting to the code bocks in the post. At the end I just apply the default layout to the item. This produces the final HTML of the page.

Filters

In the above rule I used two filters that did not ship with Nanoc. I had to create them because Nanoc does not ship with a Pandoc filter and I had to then create my own syntax highlighting filter because I could not get the built-in one to work. Lets look at the pandoc filter first:

require 'pandoc-ruby'

class PandocFilter < Nanoc3::Filter
  identifier :pandoc
  type :text

  def run(content, params = {})
    ::PandocRuby.convert(content, 'smart')
  end
end

Nanoc filters are stupidly easy to write. All you need to do is create a class that inherits from Nanoc3::Filter, give it a unique identifier and define a run method which takes in a string for you to transform. You just need to return the result of the transformation. To write my pandoc filter I used the pandoc-ruby gem which wraps around the pandoc executable.

Of course not all filters are going to be this simple. Adding syntax highlighting was significantly more complex. My pygments filter:

require 'pygments.rb'
require 'hpricot'
require 'cgi'

class PygmentsFilter < Nanoc3::Filter
  identifier :pygments
  type :text

  def run(content, params = {})
    post = Hpricot(content)
    code_blocks = post.search('pre.ruby code')
    code_blocks.each do |code_block|
      code = code_block.inner_html
      code = CGI.unescapeHTML(code)
      code = ::Pygments.highlight(code, :lexer => 'ruby', :options => {:encoding => 'utf-8'})
      code_block.parent.swap code
    end

    post.to_html

  end
end

This filter assumes that the input is produced by my pandoc filter. It simply tries to find code blocks with a class of ruby and pipe the portion though pygments. It would be pretty easy to add support all languages that pygments supports.

Routing Rules

After compiling items the next rule applied is the route rules. The route rules define where an item should be placed in the output directory and what filename it should have. Since the Rules file is pure Ruby you can leverage all of ruby to create the URL scheme required. Here is the route rule:

route '/posts/*/' do
  date = item[:created_at]
  raise "No Posted Date!" if date.nil?

  slug = item[:title].to_slug

  "/blog/#{date.year}/#{date.month}/#{date.day}/#{slug}/index.html"

end

This rule simply returns the desired path and filename of the item in the output directory. Here I use the to_slug gem to help me generate a slug in the URL from the post title. Of course if I change my post title, the slug changes so I have to be careful not to break links to my site.

Conclusion

Using nanoc has let me customize my blog to my liking with a little bit of work. I would not have been able to achieve this kind of setup with more popular solutions. If you like having this kind of control over your site then you should check nanoc out.

Like this post? Follow me on Twitter.