Files
firmware/hooks/mkdocs_readme.py
T
Oleksandr ShyshatskyiandClaude Opus 5 7347f56418 docs/ rendered on GitHub and nowhere else. It is a built, versioned site now.
The alternative was a wiki, and a wiki is a separate git repo: a page would
stop moving in the same commit and the same review as the code it describes,
with nothing able to catch the drift, and there would be exactly one live
version of it. This project ships a package per tag and tells owners which
checkboxes have touched hardware; the docs for the build someone flashed have
to be readable, so Read the Docs builds this repo per tag and `latest` off
master, with the version switcher that follows from that.

Nothing moved. The audience split that the pages already have -- owner pages
against build-and-test pages -- is the nav in mkdocs.yml, so a reader lands in
their own section and never falls out of it, while the 30+ relative links
between README.md, docs/*.md and docs/notes/ keep working in the repo view and
the links people have already shared keep resolving. notes/70-error-codes.md
sits in the owner section because README.md was already sending owners there.

Two things had to hold for that to be true, and hooks/mkdocs_readme.py is
both. Links out of docs/ -- `../payload/klipper/extras/ff_tool.py` and 18
others in toolchange.md -- are written relative because that is what works
where a contributor reads them, in the repo; they become GitHub URLs at build
time, and a link whose target does not exist is left alone so the build still
fails on it. README.md is the front door and stays it: docs/index.md is
generated from it on every build and gitignored, rather than being a second
front page free to drift from the first.

Anchors use GitHub's slug rules for the same reason. `#runout--clog` is what
GitHub makes of `## Runout / clog`, python-markdown would have made
`#runout-clog`, and toolchange.md:340 already pointed at the first.

CI builds it with --strict, which is the cheap half of keeping docs true: a
rename in payload/ now breaks the build instead of quietly leaving a page
pointing at nothing. It cannot check that the prose is still right. Only a
reader can.

The site is not up yet -- the project has to be created on readthedocs.org and
its versions activated -- so nothing links to it from README.md. The slug is
free; mkdocs.yml claims it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S3XZh4SqPdXAowVndz6tMr
2026-08-28 22:02:43 +03:00

69 lines
2.8 KiB
Python

"""Two jobs, both about keeping one source of truth.
`on_pre_build` publishes README.md as the site's front page. The README is the
project's front door on GitHub and has to stay that; a hand-copied
docs/index.md would be a second front page, free to drift from the first, so
the build generates index.md on every run and .gitignore keeps it out of the
tree.
`on_page_markdown` rewrites the links that point out of docs/ -- at
`../payload/klipper/extras/ff_tool.py` and friends -- into GitHub URLs. Those
links are written relative because that is what works when the page is read in
the repo, which is where a contributor reads it; on the site they would 404.
Rewriting them here means nobody has to remember two link styles, and a link
whose target does not exist at all is still left alone, so mkdocs --strict can
fail the build over it.
"""
import pathlib
import re
REPO = "https://github.com/Klipper4FlashForge/firmware"
BRANCH = "master"
ROOT = pathlib.Path(__file__).resolve().parent.parent
DOCS = ROOT / "docs"
# A markdown link target that is not absolute, an anchor, or a mail address.
LINK = re.compile(r"\]\((?!https?:|#|mailto:)([^)\s]+)\)")
def on_pre_build(config, **kwargs):
text = (ROOT / "README.md").read_text(encoding="utf-8")
# The README's paths are relative to the repo root, the site's to docs/:
# resolve from the root first, so `gcode/` and `docs/notes/` become GitHub
# URLs, then strip the prefix from what is left, which is the site itself.
text = _rewrite_links(text, ROOT)
text = re.sub(r"\]\(docs/", "](", text)
text = re.sub(r'(src|href)="docs/', r"\1=\"", text)
index = DOCS / "index.md"
# Only write on a real change: `mkdocs serve` watches docs/, and touching
# index.md every build would rebuild forever.
if not index.exists() or index.read_text(encoding="utf-8") != text:
index.write_text(text, encoding="utf-8")
def on_page_markdown(markdown, page, **kwargs):
return _rewrite_links(markdown, (DOCS / page.file.src_path).parent)
def _rewrite_links(markdown, here):
def rewrite(match):
target = match.group(1)
path, _, fragment = target.partition("#")
if not path:
return match.group(0)
resolved = (here / path).resolve()
try:
relative = resolved.relative_to(ROOT)
except ValueError:
return match.group(0) # outside the repo; not ours to fix
if not resolved.exists():
return match.group(0) # broken: let --strict say so
if resolved.is_file() and DOCS in resolved.parents:
return match.group(0) # a page or an image; mkdocs has it
kind = "tree" if resolved.is_dir() else "blob"
suffix = f"#{fragment}" if fragment else ""
return f"]({REPO}/{kind}/{BRANCH}/{relative}{suffix})"
return LINK.sub(rewrite, markdown)