feat: flyt sitet fra Astro til Zola
Samme indhold, URL'er og udseende, bygget af en enkelt zola-binær og Tailwind standalone-CLI uden Node. Kanonisk domæne er asger.weirsoe.dk. Blogindlæg ligger som page bundles med frontmatter Zola forstår, så de gamle stempler er ugyldige, indtil scripts/omstempl.py er kørt med PUBLISH_KEY. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7e13068dd2
commit
e58a11c2b4
Executable
+157
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Efterbehandling af Zolas output i public/:
|
||||
|
||||
- overskrift-id'er som på den gamle side (bevarer æøå, så gamle #ankre stadig virker)
|
||||
- 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."""
|
||||
s = html.unescape(re.sub(r"<[^>]+>", "", text)).strip().lower()
|
||||
s = re.sub(r"[^\w\- ]", "", s)
|
||||
return s.replace(" ", "-")
|
||||
|
||||
|
||||
def heading_ids():
|
||||
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]}>'
|
||||
|
||||
f.write_text(re.sub(r'<h([1-6]) id="[^"]*"([^>]*)>(.*?)(?=</h\1>)',
|
||||
lambda m: fix(m) + m[3], src, flags=re.S))
|
||||
|
||||
|
||||
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 not in ("sw.js", "mermaid.min.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)
|
||||
heading_ids()
|
||||
if PREVIEW:
|
||||
rebase()
|
||||
else:
|
||||
sitemap()
|
||||
service_worker()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user