Uncodin Logo

Fenced Code Blocks in Nanoc

By Damian Carrillo on Dec 12, 2011
Filed under: Nanoc

Nanoc is a useful utility for generating a static website. It serves as a filter pipeline in which you specify a course of events that should take place based on certain criteria. For example, if you would like to generate HTML from Markdown, you can instruct Nanoc to run Markdown files though a processor such as RDiscount or BlueCloth via its rule specification file. Nanoc offers a great degree of flexibility and customizability. This was its primary selling point next to having good documentation, and ultimately was the reason we chose it over its alternatives.

Nanoc correctly makes few assumptions about its application. Because of this, we had to roll up our sleeves and knock out some custom code in order to get some of the features we wanted. One feature that we found particularly useful in our day-to-day interaction with Github was its support for Fenced Code Blocks within a page of Markdown. Eg:

``` objective-c
- (id)initWithSomething:(Something *)something {
  ...
}
```

I added support for this feature with the following custom Nanoc filter:

Ruby
class FencedCodeBlock < Nanoc3::Filter
  identifier :fenced_code_block
  
  def run(content, params={})
    content.gsub(/(^`{3}\s*(\S*)\s*$([^`]*)^`{3}\s*$)+?/m) {|match|
      lang_spec  = $2
      code_block = $3
      
      replacement = '<div class="code-container">'
      
      if lang_spec && lang_spec.length > 0
        replacement << '<div class="langspec">'
        replacement << lang_spec.capitalize
        replacement << '</div>'
      end
      
      replacement << '<pre class="highlight"><code class="language'
      
      if lang_spec && lang_spec.length > 0
        replacement << '-'
        replacement << lang_spec
      end
      
      replacement << '">'
      
      code_block.gsub!("[:backtick:]", "`")
      
      coder = HTMLEntities.new
      replacement << coder.encode(code_block)
      replacement << '</code></pre></div>'
    }
  end
end

A fenced code block demarcated by three back ticks followed by optional whitespace, and an optional language on a single line. The code block can then be flush against the left margin (which differs from typical Markdown). End the code block with three more backticks on a single line.

The aforementioned filter interprets fenced code blocks with the following algorithm:

  • Locate a fenced code block
  • Isolate the specified programming language
  • Isolate the actual block of code
  • Escape html entities in the block of code
  • Return the following snippet of HTML
Html
<div class="code-container">
  <div class="langspec">objective-c</div>
  <pre class="highlight">
    <code class="language-objective-c">
      ... pygmentized markup ...
    </code>
  </pre>
</div>

Nanoc's ColorizeSyntax filter expects the <pre><code class="language-*"> as indicated in Nanoc's ColorizeSystax filter API documentation.

The reason that there is an additional div around the pre tag is because it is used to lay out and display the language being used. A snippet of CSS that will position the language specification correctly (as illustrated in the top right-hand corner of the code in this post) is:

Css
.code-container {
  position: relative;
}

.code-container .langspec {
  display: inline;
  position: absolute;
  top: 6px;
  right: 10px;
}

Lastly, you need to instruct Nanoc to pass Markdown through the FencedCodeBlock filter before the Markdown filter. You can do this by including something similar to the following in your Rules file:

Ruby
compile '/blog/*/' do
  filter :fenced_code_block                 # the Fenced Code Block filter
  filter :rdiscount                         # the Markdown filter
  filter :colorize_syntax,                  # the Syntax highlighter
         :default_colorizer => :pygmentize  
  layout 'article'
end