Created
December 8, 2015 15:54
-
-
Save qznc/6fbdc37ad33e6853911a to your computer and use it in GitHub Desktop.
Snapshot of my static website generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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