GitHub style Pygments syntax highlighting in Jade

Since I started blogging a few months ago, I fought with syntax highlighting on every step. I played around with manually highlighting my code for best results, but I very soon realized I simply do not have the patience. The need for a syntax highlighter was born. I believe in rendering as much as possible just once and server side. After some poking around I went with the Pygments syntax highlighter. If it's good enough for GitHub, it must be good enough for me...

My original idea was to create a custom filter for Jade that would accept the language as an argument. This proved unpractical since Jade filters don't support arguments and it still left the markdown fenced code blocks plain. Thus an easier solution was born. Highlight the fenced blocks and use the :markdown filter.

The code

Jade lets you specify (override) the markdown parser easily and the marked parser supports fenced code blocks, but a bit of tinkering is still needed to add the Pygments.

jade = require 'jade'
marked = require 'marked'
{execSync} = require 'child_process' # node >= 0.11.12

renderer = new marked.Renderer
renderer.code = (code, lexer = 'text') ->
    result = execSync "pygmentize -l #{lexer} -f html",
        input: code
    return result.toString()

marked.setOptions
    renderer: renderer

jade.filters.markdown = marked

marked exposes the renderer.code function that handles the markdown code blocks. The default implementation calls renderer.options.highlight if the fenced code block specifies a language, but it also (correctly) wraps the result in <pre><code>. Since the Pygments highlighter already wraps its output in <div class="highlight"><pre>, it is not enough to override the renderer.options.highlight. We must override the renderer.code function instead to get rid of the clutter.

To actually highlight the code, I use the pygmentize command line utility. Since Jade doesn't support async filters, we are limited to synchronous calls and must use the child_process.execSync to call pygmentize.

With marked.setOptions we override the renderer to our custom one and then override the Jade markdown filter to use our customized marked instance.

Usage

Whenever you need a highlighted code block in jade, use the :markdown filter with a fenced code block.

Highlighting CoffeeScript*:

:markdown
```coffee-script
foods = ['broccoli', 'spinach', 'chocolate']
eat food for food in foods when food isnt 'chocolate'
```
foods = ['broccoli', 'spinach', 'chocolate']
eat food for food in foods when food isnt 'chocolate'
<div class="highlight"><pre><span class="nv">foods = </span><span class="p">[</span><span class="s">'broccoli'</span><span class="p">,</span> <span class="s">'spinach'</span><span class="p">,</span> <span class="s">'chocolate'</span><span class="p">]</span>
<span class="nx">eat</span> <span class="nx">food</span> <span class="k">for</span> <span class="nx">food</span> <span class="nx">infoods</span> <span class="nx">whenfoodisnt</span> <span class="s">'chocolate'</span>
</pre></div>

Highlighting Bash:

:markdown
```bash
if test -r "${HOME}/.profile"; then
source "${HOME}/.profile"
fi
```
if test -r "${HOME}/.profile"; then
    source "${HOME}/.profile"
fi
<div class="highlight"><pre><span class="k">if </span><span class="nb">test</span> -r <span class="s2">"${HOME}/.profile"</span>; <span class="k">then</span>
<span class="k">    </span><span class="nb">source</span> <span class="s2">"${HOME}/.profile"</span>
<span class="k">fi</span>
</pre></div>

Further reading

* When highlighting CoffeeScript in fenced blocks in Jade remember to escape variables expansion in strings. Since Jade uses the same syntax, undefined variables may raise exceptions or go unnoticed with an empty output. Always change "#{coffeeVariable}" to "\#{coffeeVariable}".