Tabeller ruller nu inden i sig selv, så siden ikke bliver bredere end skærmen ved 390 px. Kodeblokke og tabeller kan igen nås med tastatur. Kolofon, uses, humans.txt og banner beskriver den nye stak. Mermaid er fjernet, da intet indlæg bruger det. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
161 lines
5.5 KiB
Python
Executable File
161 lines
5.5 KiB
Python
Executable File
#!/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'<h{m[1]} id="{s}-{n}"{m[2]}>' if n else f'<h{m[1]} id="{s}"{m[2]}>'
|
|
|
|
src = re.sub(r'<h([1-6]) id="[^"]*"([^>]*)>(.*?)(?=</h\1>)', lambda m: fix(m) + m[3], src, flags=re.S)
|
|
src = src.replace('<pre class="giallo"', '<pre tabindex="0" class="giallo"')
|
|
src = src.replace("<table>", '<div class="tabel" tabindex="0"><table>').replace("</table>", "</table></div>")
|
|
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", "/peppol")
|
|
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"<url><loc>{SITE}{r}</loc>" + (f"<lastmod>{mod}</lastmod>" if mod else "")
|
|
+ f"<changefreq>weekly</changefreq><priority>{'1.0' if r == '/' else '0.7'}</priority></url>")
|
|
ns = 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"'
|
|
(OUT / "sitemap-0.xml").write_text(f'<?xml version="1.0" encoding="UTF-8"?><urlset {ns}>{"".join(urls)}</urlset>')
|
|
newest = max(mods.values(), default=None)
|
|
(OUT / "sitemap-index.xml").write_text(
|
|
f'<?xml version="1.0" encoding="UTF-8"?><sitemapindex {ns}><sitemap><loc>{SITE}/sitemap-0.xml</loc>'
|
|
+ (f"<lastmod>{newest}</lastmod>" if newest else "") + "</sitemap></sitemapindex>")
|
|
|
|
|
|
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()
|