§ Using nanoc for blogging

I’ve actually started by trying to roll my own script for easy blogging, but as I were trying to actually write something it occurred to me that it’s not so trivial if you want features like caching and dependency tracking and still have it as a script rather than some abstract framework. So I went back and tried to make nanoc do what I want.

The initial problems are that when building your site you have to make sure that nanoc’s Rules and config, your filters, templates, styles and content pages all work together the way you want. At this development stage I found it really useful to have both nanoc view and nanoc watch running at the same time. It makes it easy to iteratively develop the site and integrate all the parts.

Before we go on let me define some goals, which should help you decide if you’ll find anything useful here:

If you wish you may instead jump right to the source code for this blog here.

§ Basic setup

As I said earlier the official nanoc documentation doesn’t provide any coherent example (just random snippets), so I’ve run into some problems. The key is to understand that nanoc uses identifiers and not paths. What this means in practice is that you’ll have to put similar items into sub-folders: so CSS styles go in one folder, articles/post into another, files yet another etc.

§ Compilation stage

## COMPILE
##

compile '/articles/*' do
  filter :source_inserter
  filter :kramdown
  filter :colorize_syntax
  filter :section_linker
  layout 'article'
end

compile '/articles/*', :rep => :text do 
  filter :html_killer
  filter :source_inserter
end

compile '/assets/*' do
  if %w{ jpg jpeg png gif }.member? item[:extension]
    filter :image_resampler, :width => 640
  end
end

compile '/errors/*' do
  filter :kramdown
  
  layout 'error'
end

compile '/feed/' do
  filter :erb
end

compile '*' do
  case item[:extension]
  when 'css'
    filter :rainpress
  when 'haml'
    filter :haml
  end
end

As you can see I have a couple of my own filters there, which will be described later. I use custom error pages as I serve this blog through a vhost, and the ATOM feed is provided by Nanoc::Helpers::Blogging. I’ve also decided that as I’m writing the content in a text-friendly format it won’t hurt to actually include the text versions (so I can claim you can read the articles here on any device). Note that the order of execution of filters is important.

§ Routing stage

## ROUTE
##

route '/articles/*', :rep => :text do
  item.identifier.chop + '.txt'
end

route '/assets/*' do
  item.identifier.chop + '.' + item[:extension]
end

route '/styles/*' do
  item.identifier.chop + '.' + item[:extension]
end

route '/' do
  '/index.html'
end

route '/feed/' do
  '/feed.xml'
end

route '*' do
  if item.binary?
    item.identifier.chop + '.' + item[:extension]
  else
    item.identifier.chop + '.html'
  end
end

This should hopefully by self-explanatory.

§ Filters - making life easy

This is where using nanoc really makes a difference. You can make filters to do all sorts of content formatting and converting. You are quite free to put your filters (along with helpers, or in fact any other code) into any file that goes into lib/ folder.

I hate to have to manually resize any images when I’m putting them online, so I’ll start with the image resampler.

class ImageResamplerFilter < Nanoc::Filter
  identifier :image_resampler
  type :binary

  def run(filename, params = {})
    system('convert', '-resize', "#{params[:width]}x#{params[:width]}>",\
            filename, output_filename)
  end
end

This is actually identical to what you’d find in the nanoc’s documentation (I’m using ImageMagick’s convert here).

Another simple yet useful filter is for automatic addition of section links to article pages. Kramdown has an option to automatically generate id’s for every section tag (h1, h2, …), but it doesn’t generate any links, so your id’s are more or less useless. This simple filters uses some regexp ‘magic’, so that the links are inserted in just one substitution.

class SectionLinkerFilter < Nanoc::Filter
  identifier :section_linker
  type :text

  def run(content, params = {})
    content.gsub(/\<(h\d) id="(.*?)".*?\>/,\
            '<\1 id="\2"><a class="section" href="#\2">&sect;</a> ')
  end
end

Last one of small filters is a one that removes any HTML tags. By having this I have the freedom to use some inline styling in my Makdown files if I need to and still have a really clean text-only representation generated by nanoc.

class HTMLKillerFilter < Nanoc::Filter
  identifier :html_killer
  type :text

  def run(content, params = {})
    content.gsub(/\<.*?\>/, '')
  end
end

The last filter is the most complex one, as it includes a specified file as a code block. It is aware of whether you want a HTML or text-only representation, and you can force language for CodeRay’s highlighting. You can also include only a specified range of lines. Motivation for writing this helper is that copying & pasting code can be painful, as for code blocks each line has to start with a tab or four spaces, and it also messes up my Markdown syntax highlighting in VIM. A downside is that the articles don’t really have the code inside, so it seems to be a good idea to copy the files you link to into a separate folder. That way you have a backup of the version you talk about in an article.

class SourceInserterFilter < Nanoc::Filter
  identifier :source_inserter
  type :text

  def run(content, params = {})
    ncontent = ''

    content.each_line do |line|
      if m = line.match(/^\[src:(.*?)(:lang=.*?)?(:lines=.*?)?\]/)
        opts = { :lines => false, :lang => false }
        path, *nopts = m.captures

        nopts.each do |o|
          next if o.nil?
        
          cmd, arg = o.match(/:(.*?)=(.*)/).captures
        
          case cmd
          when 'lang'
            opts[:lang] = arg
          when 'lines'
            lines = arg.split(',').collect{|e| e.to_i}
            lines[1] -= lines[0] - 1
            lines[0] -= 1
            opts[:lines] = lines
          end
        end

        data = File.read(path)
        data = data.lines.to_a.slice(*opts[:lines]).join if opts[:lines]

        if @item_rep.name == :text
          ncontent += "--------- CODE BEGIN ---------\n" 
          ncontent += data
          ncontent += "--------- CODE END -----------\n" 
        else
          if opts[:lang]
            ncontent += "<pre><code>\n\#!#{opts[:lang]}\n"
          else
            ncontent += "<pre><code>\n"
          end
          ncontent += html_escape(data)
          ncontent += "</code></pre>\n"
        end
      else
        ncontent += line
      end
    end
    
    ncontent
  end
end

Note that I’m completely not caring about any errors here. I think that this is the right choice, as if anything were to go bad I’ll have a nice error message including stack trace from nanoc, or I’ll notice that something is not right when previewing the page locally. Usage is dead simple, you just write:

[src:Rules:lang=ruby:lines=1,10]

To include the first 10 lines of Rules file.

§ Syntax highlighting

CodeRay has the benefit of being pure-Ruby, so there is no need for any external utilities (and I personally find it very messy to have some Ruby wrapper around some Python wrapper around…). However it doesn’t include support for Bash sources, but fortunately there is a gem for that (which you should require in any of your lib/ files).

The (currently) only real problem with CodeRay is the separation of presentation from the content. I actually haven’t noticed this immediately, but when you install CodeRay’s gem you’ll get a coderay command which can output the default stylesheet (just run coderay stylesheet). Now, if you happen to be happy with the colour scheme you’re done. If not however, I’ve written a very dumb script to tweak that default stylesheet. It needs color-tools gem, and will go through the CSS, read colour definitions, adjust each colour’s brightness and saturation, and spit out the changed line. Here’s a quick how to use it:

$ coderay stylesheet | ruby csscolors.rb 0.8 0.9 > code.css

This will adjust each colour to have 0.8 brightness and 0.9 saturation and write the new CSS to code.css. Be aware that you may still need to adjust some colours (those defined by their names and hsl function). This script is meant to only do the bulk work for you.

§ Comments framework

As you probably can see, I use Disqus for comments. Well, except for the fact that it is dead easy to integrate I really can’t say more, as this is the first time I’m actually having any comments system on my site :) I am aware that this means I do not really ‘own’ the comments, but for now I’m quite comfortable with that.

§ Final thoughts

Getting everything to work from scratch can be a bit painful with nanoc, but it seems that it’s worth the effort. With the above infrastructure in place writing articles should be easy and distraction-free (hopefully).

comments powered by Disqus
Last update: 2012-08-06. » LINK » TXT » ATOM. Go to the top. Licence CC-BY-NC-SA.
If you find this interesting go ahead and leave a comment or add a star.
Any and all feedback is appreciated!