Markdown in Cactus

Small code cleanup

Why Cactus?

When I started thinking about building this site I wanted something simple, something which would not have a lot of moving parts. Also, something which I could host as a set of static files. I don’t really need a database and dynamic language to serve what I assumed would be mostly a set of static content.

There’s a lot of static site generators. I decided to go with Cactus because it was very easy to setup and it’s using Django, which I know very well.

Why markdown?

Although HTML is not a bad markup language it can be verbose. It can be written manually, but the experience is not the best.

Instead, there’s a lot of markup languages which convert very easily to HTML and are much nicer to write by hand.

One markup language which seems to be everywhere nowadays is markdown. I was already using it a lot so it would only be natural to use it to write articles here.

Cactus and markdown

Cactus by default comes by default with the markdown support. It depends on django-markwhat. With it using markdown in the templates is a simple matter of adding a filter. It looks something like that:

{% filter markdown %}
# Title
Paragraph with **bold** in it.
{% endfilter %}

It was a good start.

I had a snippet which would help me with creating articles with big markdown filter in the middle. I could continue using that, but I wanted to step up the ease of use.

Cactus and markdown files

The Older version of cactus come with a nice markdown plugin. Only thing you would need is to put markdown file with .md extension in the right directory and the plugin would convert it automatically into an HTML. That’s one less step to worry about.

Problem with the original plugin

There were two problems with the original plugin:

  • it was using older markdown parser, one which was not included in the current cactus version,
  • it lacked proper escaping, the plugin would fail if any of the content contained Django template tags (ironically I find out about it when writing this article)

Those are not really big problems and fixing didn’t require a lot of work.

Here’s the updated version (also available as a github gist).


from __future__ import unicode_literals

import codecs

from os import remove
from markdown2 import Markdown
from cactus.utils.filesystem import fileList

# requires markdown2 package, to install it run
# pip install markdown2

template = """
{metadata}
{{% extends "{extends}" %}}
{{% block {block} %}}
{{% verbatim %}}
{html}
{{% endverbatim %}}
{{% endblock %}}
"""

CLEANUP = []
DEFAULT_EXTENDS = 'post.html'
DEFAULT_BLOCK = 'body'

def preBuild(site):
    for path in fileList(site.paths['pages']):

        if not path.endswith('.md'):
            continue

        md = Markdown(extras=[
            'fenced-code-blocks',
            'footnotes',
            'header-ids',
            'metadata',
            'smarty-pants',
            'tables',
        ])

        with codecs.open(path, 'r', 'utf-8') as f:
            html = md.convert(f.read())

        metadata = ['{}: {}'.format(k, v)
                    for k, v in md.metadata.items()]
        out_path = path.replace('.md', '.html')

        extends = md.metadata.get('extends',
                                  DEFAULT_EXTENDS)
        block = md.metadata.get('block', DEFAULT_BLOCK)

        with codecs.open(out_path, 'w', 'utf-8') as f:
            data = template.format(
                metadata='\n'.join(metadata),
                html=html,
                extends=extends,
                block=block,
            )
            f.write(data)

        CLEANUP.append(out_path)

def postBuild(site):
    global CLEANUP
    for path in CLEANUP:
        remove(path)
    CLEANUP = []

How the plugin works

It’s interesting to know how simple the plugin is. Both the original and my modified version work in the same fashion.

  1. Look for files with .md extension in the pages directory.
  2. For each file
    1. Extract metadata
    2. Convert markdown into HTML
    3. Write down Django template file with both metadata and converted markdown content to the filesystem
    4. Add the generated file to be cleaned up later
  3. Once the build finishes remove all the generated file.

The rest of the work of turning those template files into static content is handled by the blog plugin.

Ordering problem

To work, the system requires two plugins to work together: markdown and blog. The order of those is important. The markdown plugin needs to run first, to create Django template files. Only then blog plugin can properly convert those template files into the static content of a blog and create proper entries in the index and in the archive.

By default cactus orders plugins alphabetically, which meant that markdown would only run after blog plugin. That was too late.

Fortunately, the problem has a trivial solution, just rename markdown plugin file to something which will be ordered before blog. I went with 0_markdown.py but anything starting with a or a number would do.

Summary

Although you, as a reader cannot tell the difference, several of the articles here are already powered by plain markdown files. It works exactly as expected.

To be honest I didn’t do it to speed up the posting speed (which doesn’t really matter). What I gained from the process is the experience of digging into an unknown codebase and fixing the problems I found on the way. Now I’m much better equipped to modify any other part of the system.