From 5bf17310cf3399f6b61f1fdc459a79ccf0f3e4d0 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Sun, 21 Jun 2026 21:51:27 -0500 Subject: [PATCH] docs: preserve legacy manual links Generate XHTML-safe redirect shims that route old single-page manual anchors to their mdBook pages. --- docs/home-manager-manual.nix | 26 +----- docs/mdbook/legacy-redirects.py | 132 +++++++++++++++++++++++++++ tests/modules/misc/manual/manual.nix | 14 +++ 3 files changed, 147 insertions(+), 25 deletions(-) create mode 100644 docs/mdbook/legacy-redirects.py diff --git a/docs/home-manager-manual.nix b/docs/home-manager-manual.nix index fc6ab6602..99910ffb3 100644 --- a/docs/home-manager-manual.nix +++ b/docs/home-manager-manual.nix @@ -67,31 +67,7 @@ stdenv.mkDerivation { mkdir -p out cp -r book/* out/ - makeRedirect() { - local target=$1 - local destination=$2 - printf '%s\n' \ - '' \ - '' \ - ' ' \ - ' ' \ - " " \ - " " \ - ' Redirecting...' \ - ' ' \ - ' ' \ - "

Redirecting to $destination.

" \ - ' ' \ - '' \ - > "out/$target" - } - - makeRedirect index.xhtml index.html - makeRedirect options.html options/home-manager/index.html - makeRedirect options.xhtml options/home-manager/index.html - makeRedirect nixos-options.xhtml options/nixos/index.html - makeRedirect nix-darwin-options.xhtml options/nix-darwin/index.html - makeRedirect release-notes.xhtml release-notes/release-notes.html + python3 ${./mdbook/legacy-redirects.py} source out runHook postBuild ''; diff --git a/docs/mdbook/legacy-redirects.py b/docs/mdbook/legacy-redirects.py new file mode 100644 index 000000000..19bea1b4e --- /dev/null +++ b/docs/mdbook/legacy-redirects.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 + +import html +import json +import re +import sys +from pathlib import Path + + +ANCHOR = re.compile(r'') +SPECIAL_PAGES = { + Path("manual.md"): "index.html", + Path("preface.md"): "index.html", + Path("options.md"): "options/home-manager/index.html", + Path("nixos-options.md"): "options/nixos/index.html", + Path("nix-darwin-options.md"): "options/nix-darwin/index.html", +} +OPTION_PREFIXES = { + "opt-": "options/home-manager", + "nixos-opt-": "options/nixos", + "nix-darwin-opt-": "options/nix-darwin", +} + +ROUTER = """ +(function () { + var h = location.hash || "", a = h.slice(1), t = __DEFAULT__; + var m = __ANCHORS__, p = __PREFIXES__; + function o(a) { + for (var k in p) { + if (a.indexOf(k) !== 0) continue; + var s = a.slice(k.length).replace(/[<>]/g, "_").split("."), q = s[0]; + if ((q === "programs" || q === "services") && s.length > 1) q += "/" + s[1]; + return p[k] + "/" + q + ".html"; + } + return null; + } + if (a) t = Object.prototype.hasOwnProperty.call(m, a) ? m[a] : o(a) || t; + if (t === null) return; + var n = t + (location.search || "") + h; + var c = location.pathname.split("/").pop() + location.search + h; + if (n !== c) location.replace(n); +}()); +""".strip() + + +def page_for(source, path): + relative = path.relative_to(source) + return SPECIAL_PAGES.get(relative, relative.with_suffix(".html").as_posix()) + + +def anchors_for(source, roots): + anchors = {} + for root in roots: + files = [root] if root.is_file() else sorted(root.rglob("*.md")) + for path in files: + page = page_for(source, path) + for anchor in ANCHOR.findall(path.read_text(encoding="utf-8")): + anchors.setdefault(anchor, page) + return anchors + + +def script(default, anchors): + js = ( + ROUTER.replace("__DEFAULT__", json.dumps(default)) + .replace( + "__ANCHORS__", json.dumps(anchors, sort_keys=True, separators=(",", ":")) + ) + .replace("__PREFIXES__", json.dumps(OPTION_PREFIXES, separators=(",", ":"))) + ) + return "\n".join( + [ + " ", + ] + ) + + +def write_redirect(output, name, destination, anchors=None): + escaped = html.escape(destination, quote=True) + (output / name).write_text( + f""" + + + +{script(destination, anchors or {})} + + + Redirecting... + + +

Redirecting to {escaped}.

+ + +""", + encoding="utf-8", + ) + + +def inject_root_router(output, anchors): + index = output / "index.html" + text = index.read_text(encoding="utf-8") + index.write_text(text.replace("", f"\n{script(None, anchors)}", 1)) + + +def main(): + source, output = map(Path, sys.argv[1:]) + + manual_roots = [ + path + for path in source.iterdir() + if path.name != "options" and (path.is_dir() or path.suffix == ".md") + ] + manual_anchors = anchors_for(source, manual_roots) + release_anchors = anchors_for(source, [source / "release-notes"]) + + inject_root_router(output, manual_anchors) + for name, destination, anchors in [ + ("index.xhtml", "index.html", manual_anchors), + ("options.html", "options/home-manager/index.html", {}), + ("options.xhtml", "options/home-manager/index.html", {}), + ("nixos-options.xhtml", "options/nixos/index.html", {}), + ("nix-darwin-options.xhtml", "options/nix-darwin/index.html", {}), + ("release-notes.xhtml", "release-notes/release-notes.html", release_anchors), + ]: + write_redirect(output, name, destination, anchors) + + +if __name__ == "__main__": + main() diff --git a/tests/modules/misc/manual/manual.nix b/tests/modules/misc/manual/manual.nix index 6ac6c0b53..fc47faf65 100644 --- a/tests/modules/misc/manual/manual.nix +++ b/tests/modules/misc/manual/manual.nix @@ -16,6 +16,20 @@ assertFileExists home-path/share/doc/home-manager/nixos-options.xhtml assertFileExists home-path/share/doc/home-manager/nix-darwin-options.xhtml assertFileExists home-path/share/doc/home-manager/release-notes.xhtml + assertFileContains home-path/share/doc/home-manager/index.xhtml \ + '' + assertFileContains home-path/share/doc/home-manager/index.xhtml \ + '' + assertFileContains home-path/share/doc/home-manager/index.xhtml \ + '"ch-contributing":"contributing.html"' + assertFileContains home-path/share/doc/home-manager/index.xhtml \ + '"sec-news":"contributing/news.html"' + assertFileContains home-path/share/doc/home-manager/index.xhtml \ + 'location.replace(n);' + assertFileContains home-path/share/doc/home-manager/options.xhtml \ + '"opt-":"options/home-manager"' + assertFileContains home-path/share/doc/home-manager/index.html \ + '"sec-news":"contributing/news.html"' assertFileExists home-path/share/man/man1/home-manager.1 assertFileExists home-path/share/man/man5/home-configuration.nix.5 '';