Django Syntax Highlighting with reStructuredText and Pygments
Despite launching my blog only last week using Markdown as my blogging markup language, after reading Eric Holscher's Large Problems in Django, Mostly Solved: Documentation, I was curious if it would be much, if any, work to switch to reStructuredText and still retain the code highlighting support I was getting from Markdown's CodeHilite extension. The actual highlighting comes from Pygments, which is stand alone and should be possible to hook into reStructuredText as well.
Why bother switching? reStructuredText is pretty well loved among the Python community for documentation, and rightfully so. Although ReST is much more extensive than I need for blogging, I see no need to be using two markup languages. Plus, it saves me a module, since I don't have to install markdown.
First, make sure you have Docutils and Pygments installed. I tend to install Python modules with pip:
$ pip install docutils pygments
django.contrib.markup comes with a template filter, |restructuredtext that converts ReST into HTML, and if that works for your purposes, go ahead and use it. I, however, following advice from James Bennett's Practical Django Projects choose to store the rendered content in my database. This way it doesn't have to be parsed every time it is displayed. I peeked into the restructuredtext filter to see how it worked:
1 2 3 4 5 6 7 8 9 10 11 12 | def restructuredtext(value):
try:
from docutils.core import publish_parts
except ImportError:
if settings.DEBUG:
raise template.TemplateSyntaxError("Error in {% restructuredtext %} filter: The Python docutils library isn't installed.")
return force_unicode(value)
else:
docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {})
parts = publish_parts(source=smart_str(value), writer_name="html4css1", settings_overrides=docutils_settings)
return mark_safe(force_unicode(parts["fragment"]))
restructuredtext.is_safe = True
|
So, basically it reads in your RESTRUCTUREDTEXT_FILTER_SETTINGS and renders using docutils.core.publish_parts. Personally, it's a pet peeve of mine when I find a template tag that has built in logic that would be useful outside the context of a template tag, but in this case, the template tag function will work fine if we import it and call it from the model:
1 2 3 4 5 6 7 8 9 10 11 12 13 | from django.db import models
from django.contrib.markup.templatetags.markup import restructuredtext
class Entry(models.Model):
title = models.CharField(max_length=150)
slug = models.SlugField(unique=True)
content = models.TextField(editable=False, blank=True)
content_raw = models.TextField()
def save(self, *args, **kwargs):
if self.content_raw:
self.content = restructuredtext(self.content_raw)
super(Entry, self).save(*args, **kwargs)
|
On save, I render whatever was typed into the content_raw to html, then store it in content. content is hidden in the admin (editable=False) and content|safe is used in the templates.
ReST, as documented, now works in my Entry model. However, it knows nothing about code syntax highlighting. The Pygments website has a brief entry on ReST support that merely left me confused. Here's the gist of it: you need to create a "directive" (aka markup tag) to display source code and pygmentize it. Pygments graciously provides the necessary code in the external folder of the download, or you can view it online. I have included it below, taking the liberty to uncomment the "linenos" variant option:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | # -*- coding: utf-8 -*-
"""
The Pygments reStructuredText directive
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This fragment is a Docutils_ 0.5 directive that renders source code
(to HTML only, currently) via Pygments.
To use it, adjust the options below and copy the code into a module
that you import on initialization. The code then automatically
registers a ``sourcecode`` directive that you can use instead of
normal code blocks like this::
.. sourcecode:: python
My code goes here.
If you want to have different code styles, e.g. one with line numbers
and one without, add formatters with their names in the VARIANTS dict
below. You can invoke them instead of the DEFAULT one by using a
directive option::
.. sourcecode:: python
:linenos:
My code goes here.
Look at the `directive documentation`_ to get all the gory details.
.. _Docutils: http://docutils.sf.net/
.. _directive documentation:
http://docutils.sourceforge.net/docs/howto/rst-directives.html
:copyright: Copyright 2006-2010 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
# Options
# ~~~~~~~
# Set to True if you want inline CSS styles instead of classes
INLINESTYLES = False
from pygments.formatters import HtmlFormatter
# The default formatter
DEFAULT = HtmlFormatter(noclasses=INLINESTYLES)
# Add name -> formatter pairs for every variant you want to use
VARIANTS = {
'linenos': HtmlFormatter(noclasses=INLINESTYLES, linenos=True),
}
from docutils import nodes
from docutils.parsers.rst import directives, Directive
from pygments import highlight
from pygments.lexers import get_lexer_by_name, TextLexer
class Pygments(Directive):
""" Source code syntax hightlighting.
"""
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = dict([(key, directives.flag) for key in VARIANTS])
has_content = True
def run(self):
self.assert_has_content()
try:
lexer = get_lexer_by_name(self.arguments[0])
except ValueError:
# no lexer found - use the text one instead of an exception
lexer = TextLexer()
# take an arbitrary option if more than one is given
formatter = self.options and VARIANTS[self.options.keys()[0]] or DEFAULT
parsed = highlight(u'\n'.join(self.content), lexer, formatter)
return [nodes.raw('', parsed, format='html')]
directives.register_directive('sourcecode', Pygments)
|
But what the heck do you do with this? You need to import it somewhere, anywhere really, where it will get loaded. I saved it as rstdirective.py and played it safe by importing it at the top of my settings.py file. Other people import it in the __init__.py of the app using it.
#settings.py
import rstdirective.py
Now you can outline code by using:
.. sourcecode:: python
import foo #source code!
Or, with line numbers:
1 2 3 4 | .. sourcecode:: python
:linenos:
import foo #source code!
|
The code should be rendered, marked up, and ready for color. Pygments doesn't come with any CSS files, although it does have a tool to create them automatically:
$ pygmentize -f html -S monokai -a .highlight > media/css/pygments.css
Basically: create me CSS under the class .highlight using the theme monokai. You can preview the default Pygments themes here. If you're lazy/confused, richleland has a github repo with the CSS files already generated, you just need to change the class from .codehilite to .highlight. Include the CSS in your template link you would any other CSS file, refresh, and enjoy!
- django
- February 8th, 2010 @ 12:25 p.m.
- 0 comments
