All Unkept

reStructuredText, HsColour and Pygments

Posted in: Python, Blogging and bloggers, Haskell  — 

I like to write blog postings in reStructuredText, and I use rst2html from Python's docutils to turn them into HTML before pasting into my blog software.

One thing missing is source highlighting for Haskell, Python etc. Thankfully, Both reST and Python's docutils are written to be extensible. Below is a replacement 'rst2html' which includes support for Haskell colouring using HsColour, and just about everything else using Pygments.

Example usage:

.. code-block:: python

   import os
   # Standard hello world stuff
   class Hello()
       def do_it(self)
           print "Hello world"

   if __name__ == '__main__':

     def main()
         print "Hello world"


import os
# Standard hello world stuff
class Hello()
    def do_it(self)
        print "Hello world"

if __name__ == '__main__':

Some sample Haskell:

class Show a where
    show      :: a -> String
    showsPrec :: Int -> a -> ShowS
    showList  :: [a] -> ShowS

    -- Minimal complete definition: show or showsPrec
    show x          = showsPrec 0 x ""
    showsPrec _ x s = show x ++ s
    showList []     = showString "[]"
    showList (x:xs) = showChar '[' . shows x . showl xs
                      where showl []     = showChar ']'
                            showl (x:xs) = showChar ',' . shows x . showl xs

class Eq a where
    (==), (/=) :: a -> a -> Bool

    -- Minimal complete definition: (==) or (/=)
    x == y      = not (x/=y)
    x /= y      = not (x==y)

Here is the code:



A minimal front end to the Docutils Publisher, producing HTML,
with an extension for colouring code-blocks

    import locale
    locale.setlocale(locale.LC_ALL, '')

from docutils import nodes, parsers
from docutils.parsers.rst import states, directives
from docutils.core import publish_cmdline, default_description

import tempfile, os

def getCommandOutput2(command):
    child_stdin, child_stdout = os.popen2(command)
    data =
    err = child_stdout.close()
    if err:
        raise RuntimeError, '%s failed w/ exit code %d' % (command, err)
    return data

def highlight_haskell(text):
    fh, path = tempfile.mkstemp()
    os.write(fh, text)
    output = getCommandOutput2(["HsColour", "-css", "-partial", path])
    return output

def get_highlighter(language):
    if language == 'haskell':
        return highlight_haskell

    from pygments import lexers, util, highlight, formatters
    import StringIO

        lexer = lexers.get_lexer_by_name(language)
    except util.ClassNotFound:
        return None

    formatter = formatters.get_formatter_by_name('html')
    def _highlighter(code):
        outfile = StringIO.StringIO()
        highlight(code, lexer, formatter, outfile)
        return outfile.getvalue()
    return _highlighter

# Docutils directives:
def code_block(name, arguments, options, content, lineno,
               content_offset, block_text, state, state_machine):
    The code-block directive provides syntax highlighting for blocks
    of code.  It is used with the the following syntax::

    .. code-block:: python

       import sys
       def main():
           sys.stdout.write("Hello world")

    Currently support languages: python (requires pygments),
    haskell (requires HsColour), anything else supported by pygments

    language = arguments[0]
    highlighter = get_highlighter(language)
    if highlighter is None:
        error = state_machine.reporter.error(
            'The "%s" directive does not support language "%s".' % (name, language),
            nodes.literal_block(block_text, block_text), line=lineno)

    if not content:
        error = state_machine.reporter.error(
            'The "%s" block is empty; content required.' % (name),
            nodes.literal_block(block_text, block_text), line=lineno)
        return [error]

    include_text = highlighter("\n".join(content))
    html = '<div class="codeblock %s">\n%s\n</div>\n' % (language, include_text)
    raw = nodes.raw('',html, format='html')
    return [raw]

code_block.arguments = (1,0,0)
code_block.options = {'language' : parsers.rst.directives.unchanged }
code_block.content = 1

# Register
directives.register_directive( 'code-block', code_block )

description = ('Generates (X)HTML documents from standalone reStructuredText '
               'sources.  ' + default_description)

# Command line
publish_cmdline(writer_name='html', description=description)

I borrowed some things from this recipe, thanks. I also discovered Using Pygments in ReST documents after I wrote this.

Comments §

blog comments powered by Disqus