#!/usr/bin/env python import argparse import os import sys import xml.etree.ElementTree as ET from io import StringIO from datetime import datetime import markdown import yaml from bs4 import BeautifulSoup from jinja2 import Environment, FunctionLoader def _load_sitemap(path, current_path): by_name = dict() flattened = [] with open(path, "r") as fp: _sitemap = yaml.safe_load(fp.read()) if not _sitemap["enabled"]: return None variables = dict() if "variables" in _sitemap: variables = _sitemap["variables"] def _filter_entries(entries): entries = list( filter( lambda entry: "enabled" not in entry or entry["enabled"] is True, entries, ) ) for entry in entries: if "others" in entry: entry["others"] = _filter_entries(entry["others"]) return entries entries = _filter_entries(_sitemap["entries"]) def _link(entries, parent=None): for entry in entries: flattened.append(entry) entry["parent"] = parent if "others" in entry: _link(entry["others"], parent=entry) def _make_url(link): path = [link["name"]] parent = link["parent"] while parent is not None: path.append(parent["name"]) parent = parent["parent"] path.reverse() return "/".join(path) _link(entries, parent=None) for entry in flattened: target = "/" + _make_url(entry) if target == current_path: entry["active"] = True else: entry["active"] = False entry["url"] = target by_name[target] = entry return dict(by_name=by_name, variables=variables, links=entries) def _make_tree(links): def _populate(root, links): for link in links: elm = ET.Element("li") if "directory" in link and link["directory"]: elm.text = link["name"] else: lref = ET.Element("a", href=link["url"]) link_text = link["name"] if "date" in link: link_text = link_text + " [" + str(link["date"]) + "]" lref.text = link_text if link["active"]: lref.text = lref.text + " <" elm.append(lref) if "others" in link: others = link["others"] ul = ET.Element("ul") elm.append(ul) _populate(root=ul, links=others) root.append(elm) params = {"class": "tree"} root = ET.Element("ul", **params) _populate(root=root, links=links) return ET.tostring(root).decode() def load_content(path): with open(path) as fp: text = fp.read() content = markdown.markdown( text, extensions=["fenced_code", "tables"], ) return content def generate(template, content_path, stylesheet, link, sitemap, devmode=False): tree = _make_tree(sitemap["links"]) with open(template, "r") as fp: template = fp.read() env = Environment(loader=FunctionLoader(lambda name: template)) base = env.get_template(template) with open(stylesheet) as fp: style = fp.read() content = load_content(content_path) env_content = Environment(loader=FunctionLoader(lambda name: content)) out = base.render( { "content": env_content.get_template("").render( { "sitemap": sitemap, "tree": tree, "variables": sitemap["variables"] } ), "variables": sitemap["variables"], "is_index": False, "link": link, "style": style, "sitemap": sitemap, "tree": tree, "devmode": devmode, "current_time": datetime.now(), } ) sys.stdout.write(out) def generate_rss(sitemap): sitemap = _load_sitemap(sitemap, "/") rss = ET.Element("rss", attrib={"version": "2.0", "encoding": "UTF-8"}) channel = ET.SubElement(rss, "channel") title = ET.SubElement(channel, "title") title.text = "Blog of Kevin Schoon" link = ET.SubElement(channel, "link") link.attrib["href"] = "https://kevinschoon.com" description = ET.SubElement(channel, "description") description.text = "Blog of Kevin Schoon" for entry in sitemap["by_name"]["/blog"]["others"]: absolute_url = "https://kevinschoon.com/blog/" + entry["name"] item = ET.SubElement(channel, "item") title_item = ET.SubElement(item, "title") link_item = ET.SubElement(item, "link") guid_item = ET.SubElement(item, "guid") guid_item.text = absolute_url description_item = ET.SubElement(item, "description") title_item.text = entry["name"] link_item.attrib["href"] = absolute_url description_item.text = entry["tagline"] xml_str = ET.tostring(rss, xml_declaration=True, encoding="utf-8", method="xml") sys.stdout.buffer.write(xml_str) if __name__ == "__main__": parser = argparse.ArgumentParser(description="static renderer") parser.add_argument("-rss", help="if rendering the rss feed", action="store_true") parser.add_argument("-content", help="markdown file with content") parser.add_argument( "-template", default="index.html.jinja", help="jinja template file" ) parser.add_argument("-stylesheet", default="./assets/main.css", help="style sheet") parser.add_argument("-sitemap", default="sitemap.yaml", help="sitemap") parser.add_argument("-link", default="/", help="current url") parser.add_argument( "-devmode", action="store_true", help="developer mode (websocket page refresh)" ) args = parser.parse_args() sitemap = _load_sitemap(args.sitemap, args.link) if args.rss: generate_rss(args.sitemap) else: generate( template=args.template, content_path=args.content, stylesheet=args.stylesheet, link=args.link, sitemap=sitemap, devmode=args.devmode, )