My Blogofile Hacks

December 06, 2010 at 07:26 PM | Code, Mako/Pylons

Update: - Spurred on by Daniel Nouri, configuration examples have been upgraded to 0.7.

I'm having a completely great time with Blogofile. Publishing the whole site static and in one step, letting Disqus handle all the community is so much better than worrying about upgrades and spam. I've noticed some other folks using Blogofile and I wanted to share the key changes I used to make it work the way I want. Ideally, Blogofile itself could provide these features since they are pretty basic; I'm being somewhat lazy by just posting them here rather than requesting features via the BF mailing list, but the changes aren't generic to a plain Blogofile install so they would need to be "featurized" in order to be part of Blogofile's default setup. Until then, these adjustments work right now for an 0.6 installation.

Getting Syntax Highlighting to work with ReST

We're all using Sphinx for our docs now so we've all become experts at Restructured Text. I know this because I can actually type out `[text] <[hyperlink]>`_ from memory. Blogofile supports .rst but the syntax highlighting that's included appears to be tailored towards Markdown. My approach here to allow .rst highlighting is not as nice as that of Sphinx since I continue to be very mystified by Docutils, but it gets the job done.

Step 1 - Put the RST Filter First

We'll be using Docutils' built in system of :: followed by indentation to establish a code block, so the syntax highlighter will detect the HTML generated, instead of Blogofile's default approach of using a special tag $$code(lang=python) which doesn't make it through the rst parser in any case (in fact it's the Pygments HTML the filter generates that doesn't). Change the order in _config.py as follows:

blog.post_default_filters = {
    "rst": "rst, syntax_highlight"
}

Step 2 - New Syntax Filter

I don't need a lot of options in my code blocks other than what language is in use. Below is a simplified syntax_highlight.py that looks for a language name using a comment of the form #!<language name>:

import re
import os

from pygments import util, formatters, lexers, highlight
import blogofile_bf as bf

css_files_written = set()

code_block_re = re.compile(
    r"<pre class=\"literal-block\">\n"
    r"(?:#\!(?P<lang>\w+)\n)?"
    r"(?P<code>.*?)"
    r"</pre>", re.DOTALL
)

def highlight_code(code, language, formatter):
    try:
        lexer = lexers.get_lexer_by_name(language)
    except util.ClassNotFound:
        lexer = lexers.get_lexer_by_name("text")
    highlighted = "\n\n" + \
                  highlight(code, lexer, formatter) + \
                  "\n\n"
    return highlighted

def write_pygments_css(style, formatter, location="/css"):
    path = bf.util.path_join("_site",bf.util.fs_site_path_helper(location))
    bf.util.mkdir(path)
    css_path = os.path.join(path,"pygments_"+style+".css")
    if css_path in css_files_written:
        return #already written, no need to overwrite it.
    f = open(css_path,"w")
    f.write(formatter.get_style_defs(".pygments_"+style))
    f.close()
    css_files_written.add(css_path)

from mako.filters import html_entities_unescape

def run(src):

    style = bf.config.filters.syntax_highlight.style
    css_class = "pygments_"+style
    formatter = formatters.HtmlFormatter(
            linenos=False, cssclass=css_class, style=style)
    write_pygments_css(style,formatter)

    def repl(m):
        lang = m.group('lang')
        code = m.group('code')

        code = html_entities_unescape(code)

        return highlight_code(code,lang,formatter)

    return code_block_re.sub(repl, src)

That's it. All you do now when writing a code example:

This is some blog text.  Some code::

    #!python
    print "hello world"