import os from pathlib import Path import shutil import csv import re from datetime import datetime from jinja2 import Environment, PackageLoader, select_autoescape import frontmatter from slugify import slugify import pypandoc import xml.etree.ElementTree as ET # TODO make newsletter URL's absolute to klank.school env = Environment( loader=PackageLoader("src"), autoescape=select_autoescape() ) CONTENT_D = os.path.abspath("src/content") OUTPUT_D = "dist" OUT_ASSETS = "dist/assets" SRC_ASSETS = "src/assets" documents = {} now = datetime.now() # Utils def getParam(params, index): if len(params) > index: return params[index] else: return False # jinja filter that can list documents def listDocuments(params): param = params.split(" ") template = env.select_template(["snippets/list-documents.jinja"]) html = template.render(documents=documents, layout=param[0], type=param[1]) return html # jinja filter that can list events def listEvents(params): param = params.split(" ") tag = getParam(param, 1) if "events" not in documents: return "" events = [] if tag: for event in documents["events"]: if "tags" in event: if tag in event["tags"]: events.append(event) else: events = documents["events"] template = env.select_template(["snippets/list-events.jinja"]) html = template.render(events=events, filter=param[0], tag=getParam(param, 1)) return html # jinja filter to make a slug out of a stirng def slugify_filter(value): return slugify(value) # jinja filter for date formatting def prettydate(value, format='%d/%m/%Y'): return datetime.fromtimestamp(int(value)).strftime(format) # jinja filter to replace shortcodes in HTML def shortcode_filter(value): shortcode_callbacks = { "show": listDocuments, "events": listEvents } def shortcode_replacer(match): shortcode_name = match.group(1).strip() param = match.group(2).strip() if shortcode_name in shortcode_callbacks: return shortcode_callbacks[shortcode_name](param) return match.group(0) pattern = re.compile(r"{{\s*(\w+)\s+([^{}]+?)\s*}}") return pattern.sub(shortcode_replacer, value) env.filters["shortcode"] = shortcode_filter env.filters["slugify"] = slugify_filter env.filters["prettydate"] = prettydate # translate a single file into HTML def render_single_file(page, path, dist, name = False): name = Path(path).stem template = env.select_template([f"{name}.jinja", "index.jinja"]) html = template.render(documents=documents, page=page, name=name) if not os.path.exists(dist): os.makedirs(dist) with open(f"{dist}/{name}.html", "w", encoding="utf-8") as output_file: output_file.write(html) # find a pre-rendered page def get_existing_page(path, slug): stem = Path(path).stem; folder = os.path.basename(os.path.dirname(path)) if stem == "index" and folder != "content": folder = Path(path).parent.parent.name if slug in documents: return documents[slug] if folder == "content": return False for doc in documents[folder]: if doc["slug"] == slug: return doc return False # build a slug including the folder def get_slug(path, folder, filename): if folder == "content": return slugify(filename) else: return slugify(f"{folder}/{filename}") # compile markdown into cited HTML def get_page_data(path, isPreload=False): filename = Path(path).stem folder = os.path.basename(os.path.dirname(path)) slug = get_slug(path, folder, filename) prerendered = get_existing_page(path, slug) if prerendered: return prerendered page = frontmatter.load(path) page['slug'] = slug page.filename = filename page.folder = folder latex = page.content if "start_datetime" in page: page["has_passed"] = datetime.fromtimestamp(page["start_datetime"]) < now if "`include" in page.content: latex = pypandoc.convert_text( page.content, to='md', format='md', extra_args=[ "-N", "--section-divs", "--lua-filter=include-files.lua" ]) page.body = pypandoc.convert_text( latex, to="html", format="md", extra_args=[ "-N", "--section-divs", "--citeproc" ]) return page # Do stuff to the circuit's pcb def save_circuit_svg(filepath, outpath, name): tree = ET.parse(filepath) root = tree.getroot() # Extract current width/height (in pixels) width_px = float(root.get("width", 0)) height_px = float(root.get("height", 0)) DPI = 300 # Convert px to mm width_mm = (width_px * 25.4) / DPI > 15 height_mm = (height_px * 25.4) / DPI # Set new width/height in mm root.set("width", f"{width_px}mm") root.set("height", f"{height_px}mm") os.makedirs(outpath, exist_ok = True) tree.write(f"{outpath}/{name}") # combine HTML & data with Jinja templates def render_posts(path, output_path=OUTPUT_D): name = Path(path).stem for filename in os.listdir(path): file_path = os.path.join(path, filename) if filename.endswith(".md"): page = get_page_data(file_path) render_single_file(page, file_path, f"{output_path}/{name}", name) elif os.path.isdir(file_path): render_posts(file_path, f"{output_path}/{name}") elif filename.endswith(".svg"): save_circuit_svg(file_path, f"{output_path}/{name}", filename) elif Path(filename).suffix in [".jpeg", ".mp3", ".jpg", ".png"]: shutil.copyfile(file_path, f"{output_path}/{name}/{filename}") else: print("doing nothing with", filename) # Pre-load before compiling def preload_documents(): print("preload any needed data") documents["meta"] = {"now": now.strftime("%d %B %Y")} for subdir in os.listdir(CONTENT_D): path = os.path.join(CONTENT_D, subdir) if os.path.isdir(path): name = Path(path).stem documents.setdefault(name, []) for filename in sorted(os.listdir(path)): cpath = os.path.join(path, filename) if filename.endswith(".md"): documents[name].append(get_page_data(cpath, isPreload=True)) elif os.path.isdir(cpath): documents[name].append(get_page_data(os.path.join(cpath, "index.md"), isPreload=True)) elif Path(path).suffix == '.md': documents[Path(path).stem] = get_page_data(path, isPreload=True) def copy_assets(): if os.path.exists(OUT_ASSETS): shutil.rmtree(OUT_ASSETS) shutil.copytree(SRC_ASSETS, OUT_ASSETS) def main(): preload_documents() for subdir in os.listdir(CONTENT_D): path = os.path.join(CONTENT_D, subdir) if os.path.isdir(path): print("rendering posts", path) render_posts(path) elif Path(path).suffix == '.md': render_single_file(get_page_data(path), path, OUTPUT_D) elif Path(path).suffix in [".csv"]: print("not compiling this file!") copy_assets() main()