diff options
Diffstat (limited to 'tools/deploystatic/deploystatic.py')
-rwxr-xr-x | tools/deploystatic/deploystatic.py | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/tools/deploystatic/deploystatic.py b/tools/deploystatic/deploystatic.py new file mode 100755 index 00000000..8a425ee3 --- /dev/null +++ b/tools/deploystatic/deploystatic.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Deploy a static site based on jinja2 templates (sanxboxed) + +import argparse +import sys +import os +import os.path +import filecmp +import shutil +import json +import random + +import jinja2 +import jinja2.sandbox + +# +# Some useful filters. We include them inline in this file to make it +# standalone useful. +# +# Like |groupby, except support grouping by objects and not just by values, and sort by +# attributes on the grouped objects. +def filter_groupby_sort(objects, keyfield, sortkey): + group = [(key, list(group)) for key, group in groupby(objects, lambda x: getattr(x, keyfield))] + return sorted(group, key=lambda y: y[0] and getattr(y[0], sortkey) or None) + +# Shuffle the order in a list, for example to randomize the order of sponsors +def filter_shuffle(l): + try: + r = list(l) + random.shuffle(r) + return r + except: + return l + +global_filters = { + 'groupby_sort': filter_groupby_sort, + 'shuffle': filter_shuffle, +} + + +# Optionally load a JSON context +def load_context(jsonfile): + if os.path.isfile(jsonfile): + with open(jsonfile) as f: + return json.load(f) + else: + return {} + +# Locaate which git revision we're on +def find_git_revision(path): + while path != '/': + if os.path.exists(os.path.join(path, ".git/HEAD")): + # Found it! + with open(os.path.join(path, '.git/HEAD')) as f: + ref = f.readline().strip() + if not ref.startswith('ref: refs/heads/'): + print "Invalid git reference {0}".format(ref) + return None + refname = os.path.join(path, ".git/", ref[5:]) + if not os.path.isfile(refname): + print "Could not find git ref {0}".format(refname) + return None + with open(refname) as f: + fullref = f.readline() + return fullref[:7] + # Else step up one level + path = os.path.dirname(path) + return None + +# Actual deployment function +def deploy_template(env, template, destfile, context): + t = env.get_template(template) + s = t.render(**context).encode('utf8') + + # Only write the file if it has actually changed + if os.path.isfile(destfile): + with open(destfile) as f: + if f.read() == s: + return + + with open(destfile, 'w') as f: + f.write(s) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Deploy jinja based static site') + parser.add_argument('sourcepath', type=str, help='Source path') + parser.add_argument('destpath', type=str, help='Destination path') + + args = parser.parse_args() + + if not os.path.isabs(args.sourcepath): + print "Source path is not absolute!" + sys.exit(1) + if not os.path.isabs(args.destpath): + print "Destination path isn ot absolute!" + sys.exit(1) + + if not os.path.isdir(args.sourcepath): + print "Source directory does not exist!" + sys.exit(1) + + if not os.path.isdir(args.destpath): + print "Destination directory does not exist!" + sys.exit(1) + + for d in ('templates', 'templates/pages', 'static'): + if not os.path.isdir(os.path.join(args.sourcepath, d)): + print "'{0}' subdirectory does not exist in source!".format(d) + sys.exit(1) + + staticroot = os.path.join(args.sourcepath, 'static/') + staticdest = os.path.join(args.destpath, 'static/') + + # Set up jinja environment + env = jinja2.sandbox.SandboxedEnvironment(loader=jinja2.FileSystemLoader([os.path.join(args.sourcepath, 'templates/'),])) + env.filters.update(global_filters) + + # If there is a context json, load it as well + context = load_context(os.path.join(args.sourcepath, 'templates', 'context.json')) + + # Fetch the current git revision if this is coming out of a git repository + context['githash'] = find_git_revision(args.sourcepath) + + # Load a context that can override everything, including static hashes + context.update(load_context(os.path.join(args.sourcepath, 'templates', 'context.override.json'))) + + + knownfiles = [] + # We could use copytree(), but we need to know which files are there so we can + # remove old files, so we might as well do the full processing this way. + for dn, subdirs, filenames in os.walk(staticroot): + relpath = os.path.relpath(dn, staticroot) + if not os.path.isdir(os.path.join(staticdest, relpath)): + os.makedirs(os.path.join(staticdest, relpath)) + + for fn in filenames: + fullsrc = os.path.join(staticroot, relpath, fn) + fulldest = os.path.join(staticdest, relpath, fn) + if (not os.path.exists(fulldest)) or (not filecmp.cmp(fullsrc, fulldest)): + shutil.copy2(fullsrc, fulldest) + knownfiles.append(os.path.join('static', relpath, fn)) + + pagesroot = os.path.join(args.sourcepath, 'templates/pages') + for fn in os.listdir(pagesroot): + # We don't use subdirectories yet, so don't bother even looking at that + if os.path.splitext(fn)[1] != '.html': + continue + + if fn == 'index.html': + destdir = '' + else: + destdir = os.path.splitext(fn)[0] + + if not os.path.isdir(os.path.join(args.destpath, destdir)): + os.mkdir(os.path.join(args.destpath, destdir)) + + context['page'] = destdir + + deploy_template(env, os.path.join('pages', fn), + os.path.join(args.destpath, destdir, 'index.html'), + context) + + knownfiles.append(os.path.join(destdir, 'index.html')) + + # Look for things to remove + for dn, subdirs, filenames in os.walk(args.destpath): + relpath = os.path.relpath(dn, args.destpath) + if relpath == '.': + relpath = '' + for fn in filenames: + f = os.path.join(relpath, fn) + if not f in knownfiles: + os.unlink(os.path.join(args.destpath, f)) |