Files
firmware/hooks/mkdocs_readme.py
Oleksandr ShyshatskyiandClaude Opus 5 0af15a549a The front page shipped with broken images and literal [x] boxes.
Three defects, all in the site machinery rather than the prose, all visible
on the first page anyone opens.

`r"\1=\""` is not an escaped quote. In a raw string that backslash is a
character, so every `src="docs/…"` the README hook rewrote came out as
`src=\"helixscreen-print-status.webp"` and the browser asked for a file whose
name began with a quote. Both screenshots on the front page 404'd.

`- [x]` is python-markdown's plain list syntax unless pymdownx.tasklist is
enabled, so the Status checkboxes -- the list this project leads with, and
the thing a reader decides on -- rendered as the literal text `[x]`. Enabled,
with custom_checkbox so they get the theme's own mark. The checklists in
is-this-for-you.md and after-the-flash.md were the same bug.

And mkdocs rewrites markdown links but not raw HTML attributes, which is
invisible until a page with an `<img>` in it is not the front page: with
directory URLs, is-this-for-you.md is served from /is-this-for-you/, one level
below the image it names. The hook now rewrites relative src/href in raw HTML
too, prefixing `../` per level of the page's own URL, so an `<img>` works from
wherever the page ends up -- and it points at a file that must exist, so a
typo stays a broken link the build can still catch.

Found by serving the site and looking at it, which is the only way any of
these three surface: --strict validates markdown links, and every one of
these was in HTML or in text it had no reason to read.

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

92 lines
3.7 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]+)\)")
# The same, for a raw HTML attribute: mkdocs rewrites markdown links but not
# these, and with directory URLs a page sits one level below its own images.
ATTR = re.compile(r'\b(src|href)="(?!https?:|#|mailto:|/|\.\./)([^"]+)"')
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):
markdown = _rewrite_links(markdown, (DOCS / page.file.src_path).parent)
return _rewrite_html_paths(markdown, page)
def _rewrite_html_paths(markdown, page):
"""Point a raw <img src="x.jpg"> at the file, from wherever the page is.
`page.url` is "" for the front page and "is-this-for-you/" for a page one
level down, so the number of slashes in it is how far the image is above
the reader.
"""
up = "../" * page.url.count("/")
here = (DOCS / page.file.src_path).parent
def rewrite(match):
attr, target = match.group(1), match.group(2)
if not (here / target).exists():
return match.group(0)
return f'{attr}="{up}{target}"'
return ATTR.sub(rewrite, markdown)
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)