benlowery.co.uk

Statomatic Static Site Engine

Overview

I needed a quick way to turn a bunch of markdown files into a blog in exactly the way that I wanted and then rsync them to a plain webroot, so I threw (hacked) this together.

This site generator uses markdown along with pygments (for code highlighting) to generate a site recursively from a nested set of markdown files (it will walk an arbitrary depth nested directory tree structure).

Any file with an extension of '.md' not starting with '_' will be rendered to the output structure in the same location but as HTML i.e. /content/foo.md becomes /deploy/foo.html in the output tree, note: you can override the output location via a YAML header directive if required.

Each file is opened any meta data (if present) parsed and applied and then rendered using the jinja template library.

The use of jinja allows us to use nested templates and blocks like so

{% extends "base.html" %}

{% block title %}{{ title }}{% endblock %}

{% block content %}

<article>

    <section>
        {{ page_content }}
    </section>

</article>

{% endblock %}

The heavy lifting is all done by the fantastic Python Markdown package which handles the integration of Pygments and some other extensions automatically as well as allowing pure YAML config to be (optionally) embedded into the top of each raw markdown file.

The full listing for statomatic is below (note: Python 3 not 2):

statomatic.py

import markdown
import os
import shutil

from jinja2 import Environment, PackageLoader

env = Environment(loader=PackageLoader('statomatic', 'templates'))

def format_title(filename):
    basename = os.path.basename(filename)
    filename = str(os.path.splitext(basename)[0]).replace("_", " ")
    final_name = filename.title()
    return final_name

def handle_assets():
    shutil.copytree('./templates/css', './deploy/css/')
    shutil.copytree('./templates/et-book', './deploy/css/et-book')
    shutil.copytree('./content/images/', './deploy/images/')
    shutil.copytree('./content/other/', './deploy/other/')

def get_tree(path):
    filelist = []
    for dirName, subdirList, fileList in os.walk(path):
        for fname in fileList:
            filelist.append('%s/%s' % (dirName, fname))

    return filelist

def process_file(mdfile, source_dir, target_dir, template):
    outname = os.path.splitext(mdfile)
    filename = os.path.basename(mdfile)

    if outname[1] == '.md' and not filename.startswith('_'):

        outputfile = target_dir + outname[0].replace(source_dir, '') + ".html"

        md = markdown.Markdown(
            extensions=['markdown.extensions.fenced_code',
                        'markdown.extensions.codehilite',
                        'markdown.extensions.meta',
                        'markdown.extensions.toc',
                        'markdown.extensions.tables'],
            extension_configs={
                'markdown.extensions.meta': {'yaml': 'true'},
                'markdown.extensions.toc': {'baselevel': '1',
                                            'anchorlink': 'true'},
                'markdown.extensions.codehilite': {
                    'linenums': 'None'}})

        html = md.convert(open(mdfile).read())

        # print(mdfile + "-" + str(md.Meta))

        title = format_title(outputfile)

        data = {'page_content': html, 'title': title,
                'filename': outputfile, 'template': template}

        if md.Meta:
            data.update(md.Meta)

        template_file = data.get('template')

        template = env.get_template(template_file)

        if not os.path.exists(os.path.dirname(data.get('filename'))):
            os.makedirs(os.path.dirname(data.get('filename')))

        with open(data.get('filename'), 'w') as f:
            f.write(template.render(data))

def render_files(source_dir, targetdir, template):
    markdown_files = get_tree(source_dir)

    if os.path.exists(targetdir):
        shutil.rmtree(targetdir)

    for mdfile in markdown_files:
        process_file(mdfile, source_dir, targetdir, template)

def main():
    render_files('./content', './deploy', 'standard.html')
    handle_assets()

if __name__ == "__main__":
    main()

Other much feature complete static site generators exist but this one solves the problem the way I want in a way that I can simply extend if I need to.

Sometimes it is just good to scratch that itch.