#! /usr/bin/python # vim: ts=4 sw=4 et set fileencoding=utf-8 from datetime import date, timedelta, datetime import StringIO import xml.etree.ElementTree as ET import os.path, errno, sys # CONFIG HTDOCS_DIR="/var/www/comics.spline.de/htdocs" IMAGE_DIR=HTDOCS_DIR + "/imgs/" TARGET_DIR=HTDOCS_DIR + "/sites/" TEMPLATE_DIR="/var/www/comics.spline.de/templates" from jinja2 import Environment, FileSystemLoader env = Environment(loader=FileSystemLoader(TEMPLATE_DIR), autoescape=True) template = env.get_template('base.html') first_date = date(1997,01,01) comics = { "dilbert": { "name": "Dilbert", "url": "http://www.dilbert.com/", "imageformat": "gif" }, "xkcd": { "name": "xkcd", "url": "http://www.xkcd.com", "imageformat": "png" }, "calvin": { "name": "Calvin", "url": "http://www.ucomics.com/calvinandhobbes/viewch.htm", "imageformat": "gif" }, "garfield": { "name": "garfield", "url": "http://garfield.ucomics.com/garfield/gaview.htm", "imageformat": "gif" }, "touche": { "name": u"Touché", "url": "http://www.taz.de/", "imageformat": "gif" }, "userfriendly": { "name": "Userfriendly", "url": "http://www.userfriendly.org/static/", "imageformat": "gif" }, "zits": { "name": "Zits", "url": "http://www.kingfeatures.com/features/comics/zits/about.htm", "imageformat": "gif" }, "nichtlustig": { "name": "Nicht lustig!", "url": "http://www.nichtlustig.de", "imageformat": "jpg" }, "claybennett": { "name": "Clay Bennett", "url": "http://www.csmonitor.com/commentary/index.html", "imageformat": "jpg" }, "geekandpoke": { "name": "Geek and Poke", "url": "http://geekandpoke.typepad.com/", "imageformat": "jpg" }, "snoopy": { "name": "Snoopy", "imageformat": "gif", "url": "http://www.gocomics.com/peanuts" }, "cyanide": { "name": "Cyanide and Happiness", "imageformat": "png", "url": "http://www.explosm.net/comics/" }, "phdcomics": { "name": "PHD comics", "imageformat": "gif", "url": "http://www.phdcomics.com/comics/archive.php" }, "smbc": { "name": "SMBC", "imageformat": "png", "url": "http://www.smbc-comics.com/index.php" }, "nerdragecomic": { "name": "Nerd Rage", "imageformat": "jpg", "url": "http://nerdragecomic.com/index.php" }, "jeremykaye": { "name": "Up and Out", "imageformat": "png", "url": "http://jeremykaye.tumblr.com/" }, "extrafabulous": { "name": "Extra Fabulous Comics", "imageformat": "gif", "url": "http://extrafabulouscomics.com/" }, "MANvsMAGIC": { "name": "MANvsMAGIC", "imageformat": "png", "url": "http://www.manvsmagic.com/" }, "erzaehlmirnix": { "name": "Erzaehlmirnix", "imageformat": "png", "ul": "https://erzaehlmirnix.wordpress.com/" }, } # source: http://www.ianlewis.org/en/python-date-range-iterator def datetimeIterator(from_date=None, to_date=None, delta=timedelta(minutes=1)): from_date = from_date or datetime.now() while to_date is None or from_date <= to_date: yield from_date from_date = from_date + delta return def render_page(date, entries, prev=None, next=None): out_file = date.strftime("%%s/%Y/%m/%d.html") % TARGET_DIR try: os.makedirs(os.path.dirname(out_file)) except OSError as exc: if exc.errno == errno.EEXIST: pass else: raise text = template.render(entries=entries, date=date, next=next, prev=prev) with open(out_file,'w') as f: f.write(text.encode('utf8')) def render_all(): date_range = datetimeIterator(first_date, date.today(), timedelta(days=1)) relevant_dates = [d for d in date_range if exists_any_comic(d)] relevant_dates.append(None) prev_date, cur_date = None, None for next_date in relevant_dates: if cur_date: render_page(cur_date, gather_all_entries(cur_date), prev_date, next_date) prev_date, cur_date = cur_date, next_date def find_surrounding_date(cur, delta): new_date = cur + delta while new_date >= first_date and new_date <= date.today(): if exists_any_comic(new_date): return new_date new_date += delta return None def render_date(cur_date, recurse=True): if not exists_any_comic(cur_date): return prev_date = find_surrounding_date(cur_date, timedelta(days=-1)) next_date = find_surrounding_date(cur_date, timedelta(days=1)) entries = gather_all_entries(cur_date) render_page(cur_date, entries, prev_date, next_date) print "Rendered page for: %s" % cur_date update_feed(entries) if recurse and prev_date: render_date(prev_date, False) if recurse and next_date: render_date(next_date, False) def update_feed(entries): feed_src = os.path.join(HTDOCS_DIR, "atom.xml") if os.path.isfile(feed_src): doc = ET.ElementTree() root = doc.parse(feed_src) else: doc = ET.ElementTree() root = doc.parse(StringIO.StringIO(""" Comics urn:uuid:12349d80-df99-1dea-b91C-0003939e0af6 2003-12-13T18:30:02Z""")) for entry in entries: el = ET.SubElement(root, "{http://www.w3.org/2005/Atom}entry") ET.SubElement(el, "{http://www.w3.org/2005/Atom}title").text = entry["comic"]["name"] path = "https://comics.spline.de/%s" % entry["path"] ET.SubElement(el, "{http://www.w3.org/2005/Atom}link").set("href", path) c = ET.SubElement(el, "{http://www.w3.org/2005/Atom}content") c.set("type", "xhtml") d = ET.SubElement(c, "{http://www.w3.org/1999/xhtml}div") i = ET.SubElement(d, "{http://www.w3.org/1999/xhtml}img") i.set("src", path) ET.SubElement(el, "{http://www.w3.org/2005/Atom}id").text = path ET.SubElement(ET.SubElement(el, "{http://www.w3.org/2005/Atom}author"), "{http://www.w3.org/2005/Atom}name").text = "Spline" fn = os.path.join(HTDOCS_DIR, entry["path"][1:]) try: tm = os.stat(fn) ET.SubElement(el, "{http://www.w3.org/2005/Atom}updated").text = datetime.fromtimestamp(tm.st_mtime).strftime("%Y-%m-%dT%H:%M:%SZ") except: print "Not found: ", fn known = {} for entry in root.iter("{http://www.w3.org/2005/Atom}entry"): eid = entry.find("{http://www.w3.org/2005/Atom}id").text if eid in known: root.remove(entry) known[eid] = True root.find("{http://www.w3.org/2005/Atom}updated").text = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ") doc.write(feed_src) def generate_basename(comic_id, date): return date.strftime("%%s/%%s/%Y-%m/%d") % (IMAGE_DIR, comic_id) def add_support_content(entry, basename, type): if os.path.isfile(basename + "." + type): with open(basename + "." + type) as f: entry[type] = f.read().strip().decode('utf8') def gather_all_entries(my_date): entries = [] for (comic_id, comic) in comics.iteritems(): basename = generate_basename(comic_id, my_date) expected_path = basename + "." + comic["imageformat"] if os.path.isfile(expected_path): if expected_path.startswith(HTDOCS_DIR): expected_path = expected_path[len(HTDOCS_DIR):] entry = { "path": expected_path, "comic": comic, } add_support_content(entry, basename, "title"); add_support_content(entry, basename, "alt"); entries.append(entry) return entries def exists_any_comic(my_date): for (comic_id, comic) in comics.iteritems(): basename = generate_basename(comic_id, my_date) expected_path = basename + "." + comic["imageformat"] if os.path.isfile(expected_path): return True return False if __name__ == "__main__": if len(sys.argv) > 1 and sys.argv[1] == "-a": render_all() else: ago = date.today() try: if len(sys.argv) > 1: ago -= timedelta(days=int(sys.argv[1])) except ValueError: pass render_date(ago)