About
The following code is described in this blog post. It is available under the MIT license. You can also see the code as a gist.
The Code
"""
Free to use under the MIT license
Builds a static site from a list of Markdown source files. The source
files should have the same directory structure as the desired output.
Files are rendered using Markdown2 and can declare metadata variables:
---
template: index.html
title: My Title
---
# your makdown doc from here on
Site templates are stored in the `templates` folder and should be Jinja2
templates. Apart from the `template` meta variable in markdown documents,
any variables supplied in the Markdown meta will be available in the
template under the same name. The variable `title` given above can therefore
be accessed.
The static site will be exported to the `build` directory
"""
from jinja2 import Environment, FileSystemLoader, TemplateNotFound
import markdown2
import os
import shutil
# include additional markdown 2 extras here (e.g. tables, footnotes etc)
MARKDOWN_EXTRAS = ['metadata']
# folders that should be copied from your `src` directory to the `build` directory
STATIC_DIRS = ['images']
def files_with_extension(dir, ext):
"""
Gets all files in the given directory with the given extension
"""
return [x for x in os.listdir(dir) if x.endswith(ext)]
def copy_directory(src, dst):
"""
Copies all the contents from the source directory to the output directory
"""
print "Copying {0} to {1}".format(src, dst)
try:
shutil.copytree(src, dst)
except OSError as e:
print " > There was an error copying the files from {0} to {1}".format(src, dst)
print " > {0}".format(e)
def get_templates(path):
"""
Compiles all the templates in the template directory and
returns a dictionary of Jinja2 `Template` objects with the
file names as the keys
"""
return Environment(loader=FileSystemLoader(path))
def build_directory(templates, input_path):
"""
Takes all the *.md files in the given directory, builds them into
HTML and renders them using the Jinja templates. The rendered markdown
is available in the Jinja templates as the `content` variable.
::warning:: There can only be one `src` folder in the path, as the script
directly replaces `src` with `build` once to find the output path
"""
print "Looking for Markdown files in {0}".format(input_path)
files = files_with_extension(input_path, ".md")
op_dir = input_path.replace("src","build", 1)
if not os.path.isdir(op_dir):
os.mkdir(op_dir)
os.chmod(op_dir, 0o777)
for f in files:
print "Converting {0}".format(f)
ip_path = os.path.join(input_path, f)
with open(ip_path, 'r') as ip:
raw_html = markdown2.markdown(ip.read(), extras=MARKDOWN_EXTRAS)
try:
template = raw_html.metadata['template']
except KeyError:
print "WARNING: No template specified for {0}, using index.html".format(f)
template = "index.html"
try:
tpl = templates.get_template(template)
except TemplateNotFound:
raise Exception("Unable to locate the template {0} for file {1}. Aborting".format(template, f))
context = raw_html.metadata
context['content'] = raw_html
result = tpl.render(context)
op_path = os.path.join(op_dir, f.replace(".md", ".html"))
print "Writing to {0}".format(op_path)
with open(op_path, 'w+') as op:
op.write(result)
print " > Rendered files at {0} to {1}".format(ip_path, op_path)
print "Folder complete"
def build_site(template_dir, ip_dir, clean=True):
"""
Gets all the markdown files in the `src` directory, renders them using the template
given in metadata (or `index.html`) if no template given, and then throws them in the
same directory structure in the `build` folder.
Additionally everything in the `templates/static` folder is copied to `build/static`
"""
# delete the old build
if clean:
op_dir = ip_dir.replace("src", "build", 1)
print "Cleaning out old files from {0}".format(op_dir)
try:
shutil.rmtree(op_dir)
except Exception as e:
print " > ERROR - Unable to clean the old build directory"
print " > {0}".format(e)
try:
os.mkdir(op_dir)
os.chmod(op_dir, 0o777)
print "Created output directory"
except Exception as e:
print " > ERROR - Unable to create a directory at {0}".format(op_dir)
print " > {0}".format(e)
# copy the `templates/static` folder to `build/static`
src = os.path.join(template_dir, "static")
dst = os.path.join(ip_dir.replace("src", "build", 1), "static")
copy_directory(src, dst)
# copy all the static files
for sd in STATIC_DIRS:
src = os.path.join(ip_dir, sd)
dst = os.path.join(ip_dir.replace("src", "build", 1), sd)
copy_directory(src, dst)
# load the templates
templates = get_templates(template_dir)
# Do the root directory
build_directory(templates, ip_dir)
# get all the source directories
for path, dir, files in os.walk(ip_dir):
for d in dir:
build_directory(templates, os.path.join(path, d))
print "Site build complete"
if __name__ == '__main__':
# get the input and template dir paths
dir = os.getcwd()
template_dir = os.path.join(dir, "templates")
input = os.path.join(dir, "src")
# build the site
build_site(template_dir, input)