After using PmWiki (PHP wiki software) for nearly five years to maintain my web site I was a bit tired of fiddling with it. I then rolled out the following very simple and crummy Python script. Besides a basic Python distribution it only requires Python Markdown and Pygments.

There are no external templates or any other niceties–although it deals with unicode–but it just works.

# coding: utf-8

import codecs, getopt, markdown, os, os.path, sys 

# Some important constants
base_path = '/Users/luis/Dropbox/website/'
site_path = ''

# Basic inside-code template, making
# script self-contained
def inline_template():
    template = u'''
    <!DOCTYPE html>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta name='author' content='Luis A. Apiolaza' />
        <meta name='keywords' content='<!--tags-->' />
        <link rel='stylesheet' href='' type='text/css' />
        <link rel='stylesheet' href='' type='text/css' />
        <div id='container'>
            <div id='page-header'>
            <h1><a href=''>Sketchbook</a></h1>
                <div id='page-menu'>
                <li><a href='' title='Ego sum qui sum'>about</a></li>
                <li><a href='' title='asreml-r cookbook'>asreml-r</a></li>
                <li><a href='' title='Quantum Forest blog'>blog</a></li>
                <li><a href='' title='software'>code</a></li>
                <li><a href='' title='Gory technical details'>colophon</a></li>
                <li><a href=''>publications</a></li>
                <li><a href=''>writings</a></li>
            <div id='page-meta'>
                <p>This page was updated on <!--date--> (NZST) and is tagged 
            <div id='page-body'>
            <div id='footer'>
                <p>All bits sowed, harvested and baked in Christchurch, New 
                Zealand—43º31'S, 172º32'E—by <a href=''>Luis 
                Apiolaza</a> with 
                <a href=''>some rights reserved</a>.
                A longer blurb and gory technical details can be found in the 
                <a href=''>colophon</a>.</p>

# In case of using external template file
def read_template(filename = 'page-template.html'):
        f =, mode = 'r', encoding = 'utf-8')
    except IOError:
        print 'Template file not found'
    text =

# Obtains body and meta values from single page
def process_page(text):
    # Instance of class Markdown with two extensions
    md = markdown.Markdown(extensions = ['meta', 'codehilite', 'footnotes'])
    body = md.convert(text)
    meta = md.Meta
    return([body, meta])

# Combines body, meta information and template
def build_page(body, meta, template):    
    for key in meta.keys():
        lookfor = '<!--' + key + '-->'
        template = template.replace(lookfor, meta[key][0])

    template = template.replace('<!--body-->', body)

# Creates location bar
def location(filename, base_path, site_path):
    full_name = os.path.abspath(filename)
    base_length = len(base_path)
    nav = u'<p>Document tree: <a href="' + site_path + u'">home</a>'

    if full_name.find(base_path) >=0:
        rel_path = full_name[base_length:-5] + 'html'
        rel_path_parts = rel_path.rsplit('/')
        print 'File is not in site folder'

    for i in range(len(rel_path_parts)):
        nav = nav + u' « ' + u'<a href="' + \
              (site_path + u'/'.join(rel_path_parts[:1+i])) + \
              u'">' + rel_path_parts[i] + u'</a>'

    nav = nav + '</p>'

# Writes page encoded as utf8
def write_page(page, name):
    (root, ext) = os.path.splitext(name)
    newname = root + '.html'
    f =, 'w', encoding = 'utf8')

# Help function
def usage():
    print 'Usage:'
    print 'python -d file.markdown (draft default) OR'
    print 'python -p file.markdown (publish)'

# Dictionary defining Castilian replacements
castilian = {u'á': '&aacute;', u'é': '&eacute;', u'í': '&iacute;',
             u'ó': '&oacute;', u'ú': '&uacute;', u'ñ': '&ntilde', 
             u'¿': '&iquest;', u'¡': '&iexcl;', u'ü': '&uuml;'}

# Convert castilian code to html entities
def convert_castilian(text, dictionary):
    for key in dictionary:
        text = text.replace(key, dictionary[key])


if __name__ == '__main__':
    # Processing command line options and arguments
        opts, args = getopt.getopt(sys.argv[1:], 'dp', ['draft', 'publish'])
    except  getopt.GetoptError, err:
        print 'Error detected: ', err

    # Reading file
    filename = args[0]
        f =, mode = 'r', encoding = 'utf-8')
    except IOError:
        print 'Markdown file not found'

    text =
    html, meta = process_page(text)
    template = inline_template()
    navbar = location(filename, base_path, site_path)
    meta[u'navbar'] = [navbar]
    page = build_page(html, meta, template)
    write_page(page, filename)

And that’s it.