#!/usr/bin/env python3 """Efterbehandling af Zolas output i public/: - overskrift-id'er som på den gamle side (bevarer æøå, så gamle #ankre stadig virker) - kodeblokke og tabeller ruller inden i sig selv og kan nås med tastatur - sitemap-index.xml + sitemap-0.xml med lastmod fra git og frontmatter - sw.js med precache-liste - PW_PREVIEW=1: hele sitet flyttes ind under /udkast, uden sitemap og service worker """ import hashlib import html import json import os import pathlib import re import subprocess OUT = pathlib.Path("public") SITE = "https://asger.weirsoe.dk" PREVIEW = os.environ.get("PW_PREVIEW") == "1" BASE = "/udkast" def pages(): return sorted(OUT.rglob("*.html")) def slug(text): """Samme regler som github-slugger, som den gamle build brugte, og som Astro uden afsluttende -.""" s = html.unescape(re.sub(r"<[^>]+>", "", text)).strip().lower() s = re.sub(r"[^\w\- ]", "", s) return s.replace(" ", "-").removesuffix("-") def prose(): for f in pages(): src = f.read_text() if 'class="prose' not in src: continue seen = {} def fix(m): s = slug(m[3]) n = seen.get(s, 0) seen[s] = n + 1 return f'' if n else f'' src = re.sub(r']*)>(.*?)(?=)', lambda m: fix(m) + m[3], src, flags=re.S) src = src.replace('
", '
').replace("
", "
") f.write_text(src) def git_date(*files): if int(subprocess.run(["git", "rev-list", "--count", "HEAD"], capture_output=True, text=True).stdout or 0) < 2: return None out = subprocess.run(["git", "log", "-1", "--format=%cI", "--", *files], capture_output=True, text=True).stdout return out.strip() or None def frontmatter(path): text = path.read_text() head = text.split("---\n", 2)[1] return {k: v.strip().strip("'\"") for k, v in re.findall(r"^\s*(\w+):(.*)$", head, re.M)} def iso(date): return f"{date}T00:00:00.000Z" def lastmods(): """Rute -> lastmod: sidste commit for statiske sider, frontmatter for blogindlæg.""" routes = {} for md in pathlib.Path("content").rglob("*.md"): fm = frontmatter(md) if "blog" in md.parts or md.parts[1] == "projekter" or "projects" in md.parts: continue route = "/" + str(md.relative_to("content").with_suffix("")).removesuffix("_index") route = route.rstrip("/") or "/" tpl = fm.get("template") d = git_date(str(md), f"templates/{tpl}") if tpl else git_date(str(md)) if d: routes[route] = d posts = [] for md in pathlib.Path("content/blog").glob("*/index.md"): fm = frontmatter(md) d = iso(fm.get("updated") or fm["date"]) routes[f"/blog/{md.parent.name}"] = d posts.append(d) if posts: routes["/blog"] = max(posts) return routes def sitemap(): skip = ("/tags",) skip_exact = ("/cookies", "/status", "/chat") routes = [] for f in pages(): rel = f.relative_to(OUT) if rel.name != "index.html": continue r = "/" + str(rel.parent).replace(".", "", 1) if str(rel.parent) == "." else f"/{rel.parent}" if r.startswith(skip) or r in skip_exact: continue routes.append(r) routes.append("/tarballz/") mods = lastmods() urls = [] for r in sorted(routes): mod = mods.get(r) urls.append(f"{SITE}{r}" + (f"{mod}" if mod else "") + f"weekly{'1.0' if r == '/' else '0.7'}") ns = 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"' (OUT / "sitemap-0.xml").write_text(f'{"".join(urls)}') newest = max(mods.values(), default=None) (OUT / "sitemap-index.xml").write_text( f'{SITE}/sitemap-0.xml' + (f"{newest}" if newest else "") + "") def service_worker(): """Precache-listen afhænger af buildet, resten af sw.js ligger i scripts/sw.js.""" exts = {".html", ".css", ".js", ".svg", ".png", ".woff2", ".xml", ".txt", ".webmanifest"} files = sorted(p for p in OUT.rglob("*") if p.is_file() and p.suffix in exts and p.name != "sw.js") h = hashlib.sha256() for p in files: h.update(p.read_bytes()) urls = ["/" + str(p.relative_to(OUT)) for p in files] src = pathlib.Path("scripts/sw.js").read_text() src = src.replace("__VERSION__", h.hexdigest()[:12]).replace("__URLS__", json.dumps(urls, ensure_ascii=False)) (OUT / "sw.js").write_text(src) def rebase(): """Rod-relative links og billeder i HTML peger ind under /udkast.""" attr = re.compile(r'\b(href|src|action)="/(?!/|udkast)') srcset = re.compile(r'\bsrcset="([^"]*)"') def fix_set(m): return 'srcset="' + re.sub(r"(^|, )/(?!/)", rf"\1{BASE}/", m[1]) + '"' for f in pages(): src = f.read_text() f.write_text(srcset.sub(fix_set, attr.sub(rf'\1="{BASE}/', src))) def main(): (OUT / "sitemap.xml").unlink(missing_ok=True) prose() if PREVIEW: rebase() else: sitemap() service_worker() if __name__ == "__main__": main()