Custom ‘nanoc’ Filters

I have been using nanoc to create and manage content on this website since February this year. It’s taken me nine months to chance upon nanoc’s custom filters. Doh!

This discovery has allowed me to address the only two irritants I had with nanoc.

Irritant № 1

The first of these niggles stems from how nanoc or, more correctly, the Kramdown markup filter, handles acronyms. We all know about the dreaded TLA, a string of upper-case letters like NSA or FBI. These represent larger strings like “National Security Agency” and “Federal Bureau of Investigation” respectively. That’s great, a useful shorthand… when you know what the acronym represents.

In my world however, I write about topics that some of my readers might just be getting started with and my world is full of TLA‍s. Terms like RAM, CPU, RAID… I use these and more with reckless abandon.

But there’s an ace up my sleeve. You might notice that the TLA‍s on this website have a dotted underline. When you see that, it indicates that the expansion of the acronym is available as a tooltip, so you need never scratch your head wondering what the heck I’m going on about. Thus when I write about the CLI you can just position your mouse pointer over the TLA and a little pop-up will tell you that CLI is an acronym of “Command Line Interface.”1 Cool eh?

In my Kramdown-centric markup I write TLA‍s without adornment. I then have to issue a specific instruction to Kramdown for each one:

*[TLA]: Three Letter Acronym

This expansion does not have to be anywhere near the actual TLA itself. But it has to be in the document somewhere else Kramdown won’t do anything with the respective TLA. I got into the habit of grouping these at the bottom of each document.

I highlighted “each document” in the previous paragraph for a reason. Because therein lies the rub. The acronym expansion has to be on every document an acronym appears on. That’s not too much of a problem if you rarely use them. But in my articles TLA‍s are commonplace, my industry thrives on them.

That then, in true first world problems fashion, was my irritant: having to deal with the tedium of rewriting (actually copy and pasting) the acronym markup on virtually every article I wrote. I dreamed of a system that would allow me to maintain a single list of expansion markup, that Kramdown would then use for each document I feed through it. This is where the custom nanoc filter comes onto the stage:

class AcronymsFilter < Nanoc::Filter
  identifier :acronyms
  type :text
  def run(content, params={})
    acronyms = File.read('/path/to/acronyms.md')
    content.sub(/\n$/, "\n" + acronyms)
  end
end

Apply this filter via the nanoc compiler rules:

compile '/weblog/*' do
  filter :acronyms
  filter :kramdown
  filter :colorize_syntax, :default_colorizer => :pygmentsrb
  filter :typogruby
  layout 'post'
end

The acronyms filter reads in my global acronyms.md file and appends it to the end of the document (content). I apply this filter to content before I apply the Kramdown filter. Therefore, by the time Kramdown gets it, content has a full list of acronym expansions, just as if I had typed them myself.

Irritant № 2

All my internal links and image src attributes use relative URL‍s. This is a good behaviour. It brings a degree of flexibility that I want and need. For example, it was invaluable when I switched from my old domain name of darkblue.sdf.org to the current one: perpetual-beta.org, since I meant that I didn’t have to go through all my content and templates to apply the same change.

However, it caused a problem with my site’s Atom feed. The relative URL‍s meant that my internal links and inline images where all broken in the feed. I needed to be able to retain the relative links on the website, but replace them with absolute ones in the feed. Custom filters and a RegEx to the rescue:

class AbsoluteAtom < Nanoc::Filter
  identifier :absoluteurls
  type :text
  def run(content, params={})
    content.gsub(/((href|src)=")\//, '\1https://www.perpetual-beta.org/')
  end
end

Problem solved!

custom.rb

So where, you may ask, do the custom filters go? In lib/custom.rb of course, inside the Custom module:

module Custom
  # Your filters go here!
end

I’m still kicking myself for not having learned about nanoc’s custom filters sooner. I keep thinking of new filters I might write, to further enhance the magic of nanoc. I’ll be sure to document them here.

  1. In HTML parlance, Kramdown wraps the TLA with an <abbr> tag which has a title attribute that contains the text of the expansion. ↩︎