Skip to content

Instantly share code, notes, and snippets.

@qznc
Created December 8, 2015 15:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save qznc/6fbdc37ad33e6853911a to your computer and use it in GitHub Desktop.
Save qznc/6fbdc37ad33e6853911a to your computer and use it in GitHub Desktop.
Snapshot of my static website generator
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os.path
import shutil
import email # using MIME format for src files
import configparser
from datetime import datetime
from glob import glob
import dateutil.parser
import jinja2
import markdown
from docutils.core import publish_parts
OUTDIR = "out"
SRCDIR = "src"
TMPLDIR = "template"
def ensure_dir(name):
try:
os.makedirs(name)
except:
pass
TMPL_ENV = jinja2.Environment(loader=jinja2.FileSystemLoader(TMPLDIR))
ensure_dir(OUTDIR)
EXTENSION_MAP = dict()
def for_extension(ext):
def decorate(func):
EXTENSION_MAP[ext] = func
return func
return decorate
@for_extension("css")
@for_extension("png")
@for_extension("jpg")
@for_extension("js")
@for_extension("html")
def hardlink_file(infile):
assert (infile.startswith(SRCDIR))
outfile = os.path.join(OUTDIR,infile[len(SRCDIR)+1:])
os.makedirs(os.path.dirname(outfile), exist_ok=True)
try:
os.link(infile,outfile)
except OSError:
try:
shutil.copy(infile,outfile)
except shutil.SameFileError:
# os.link above previously succeeded
pass
def load_infile(infile):
with open(infile) as fh:
raw = email.message_from_file(fh)
assert(not raw.is_multipart())
vars = dict()
for k,v in raw.items():
vars[k] = v
vars['infile'] = infile
vars = refurbish_vars(vars)
contents = raw.get_payload()
return contents, vars
def outfile_with_ext(infile,new_ext):
base = os.path.basename(infile)
rel_dir = os.path.dirname(infile)[len(SRCDIR)+1:]
root,ext = os.path.splitext(base)
outfile = os.path.join(OUTDIR, rel_dir, root) + new_ext
dir = os.path.dirname(outfile)
ensure_dir(dir)
return outfile
def render(vars, outfile, tmpl_name="page.html"):
vars["base_url"] = _CONFIG['base']['base_url']
template = TMPL_ENV.get_template(tmpl_name)
with open(outfile, "w") as out:
out.write(template.render(vars))
# Validate
os.system("xmllint --noout " + outfile)
def cd_once(path):
"""Remove the first directory element of path"""
i = path.find("/")
if i > 0:
return path[i+1:]
return path
assert cd_once("bla/foo/baz.txt") == "foo/baz.txt"
def relative_prefix(infile):
"""Return relative path to root from infile"""
assert (infile.startswith(SRCDIR+"/"))
path = infile[len(SRCDIR)+1:]
dir = os.path.dirname(path)
depth = path.count("/")
return "../" * depth
def refurbish_vars(vars):
infile = vars['infile']
vars["title"] = vars.get("title", "No Title?!")
vars["author"] = vars.get("author", "unknown")
vars["ROOT"] = relative_prefix(infile)
if not "date" in vars:
dt = datetime.fromtimestamp(os.path.getmtime(infile))
vars['date_parsed'] = dt
vars["date"] = dt.strftime("%Y-%m-%d")
else:
vars['date_parsed'] = dateutil.parser.parse(vars['date'])
return vars
@for_extension("md")
@for_extension("mdwn")
@for_extension("markdown")
def markdown_handler(infile):
contents, vars = load_infile(infile)
vars["contents"] = markdown.markdown(contents)
outfile = outfile_with_ext(infile,".html")
render(vars, outfile)
def rst2html(txt):
return publish_parts(txt, writer_name='html')['html_body']
@for_extension("htm")
def htm_handler(infile):
contents, vars = load_infile(infile)
vars["contents"] = contents
outfile = outfile_with_ext(infile,".html")
render(vars, outfile)
@for_extension("rst")
def rst_handler(infile):
contents, vars = load_infile(infile)
vars["contents"] = rst2html(contents)
outfile = outfile_with_ext(infile,".html")
render(vars, outfile)
@for_extension("collection")
def collection_handler(infile):
contents, vars = load_infile(infile)
items = list()
for filename in contents.split("\n"):
f = os.path.join(SRCDIR,filename)
if not os.path.isfile(f):
continue
_, f_vars = load_infile(f)
f_outfile = outfile_with_ext(f,".html")
items.append(dict(
title = f_vars['title'],
link = cd_once(f_outfile),
date = f_vars['date'],
date_parsed = f_vars['date_parsed'],
tldr = f_vars.get('tldr', ""),
image_src = f_vars.get('image_src', ""),
));
items.sort(key = lambda x: x["date"], reverse=True)
atom_outfile = outfile_with_ext(infile,".atom")
vars["collection"] = items
vars["atomlink"] = cd_once(atom_outfile)
outfile = outfile_with_ext(infile,".html")
render(vars, outfile, tmpl_name="collection.html")
render(vars, atom_outfile, tmpl_name="collection.atom")
def all_infiles():
for root, dirnames, filenames in os.walk(SRCDIR):
for filename in filenames:
yield os.path.join(root, filename)
_CONFIG = configparser.ConfigParser()
if __name__ == "__main__":
_CONFIG.read("config.ini")
for infile in all_infiles():
if not os.path.isfile(infile):
continue
root,ext = os.path.splitext(os.path.basename(infile))
if not ext[1:] in EXTENSION_MAP.keys():
print("unknown extension:", infile)
continue
EXTENSION_MAP[ext[1:]](infile)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment