Files
openmlsys-zh/tests/test_image_references.py
Yeqi Huang 9c0439f424 fix(v1-en): repair missing figures and section pages in English edition (#506)
- Remap 129 broken image paths in v1/en_chapters to the files that exist
  under v1/img (old chapter numbering, renamed files, and .pdf figures
  that browsers cannot display inline).
- Restore ch07/7.5/distribute.png and ch08/distillation.png, which were
  dropped in #499 because the `dist*` .gitignore rule matched them;
  narrow the rule to `/dist/`.
- Fix the English toc blocks: chapter entries now point at real files and
  the root toc has part headings, so section pages are built again
  (previously only chapter index pages were generated).
- Add tests/test_image_references.py to CI to catch missing or
  unrenderable figure references.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 01:57:57 +00:00

60 lines
2.3 KiB
Python

from __future__ import annotations
import os
import re
import unittest
from pathlib import Path
from tools.ensure_book_resources import RESOURCE_NAMES
REPO_ROOT = Path(__file__).resolve().parent.parent
CHAPTER_DIRS = (
REPO_ROOT / "v1" / "en_chapters",
REPO_ROOT / "v1" / "zh_chapters",
)
SHARED_RESOURCES = REPO_ROOT / "v1"
MARKDOWN_IMAGE_RE = re.compile(r"!\[[^\]]*\]\(\s*([^)\s]+)")
HTML_IMAGE_RE = re.compile(r"<img[^>]*?\ssrc=\"([^\"]+)\"")
# Browsers cannot display these inline, so mdBook renders a broken image.
UNRENDERABLE_SUFFIXES = {".pdf", ".eps", ".tif", ".tiff"}
def iter_markdown_files(chapter_dir: Path):
for path in sorted(chapter_dir.rglob("*.md")):
relative = path.relative_to(chapter_dir)
if relative.parts[0] in RESOURCE_NAMES:
continue
yield path
def resolve_reference(chapter_dir: Path, markdown_file: Path, reference: str) -> Path:
# Resource dirs are symlinks created at build time, so resolve them manually.
target = Path(os.path.normpath(markdown_file.parent / reference))
relative = target.relative_to(chapter_dir) if target.is_relative_to(chapter_dir) else None
if relative is not None and relative.parts and relative.parts[0] in RESOURCE_NAMES:
return SHARED_RESOURCES / relative
return target
class ImageReferenceTests(unittest.TestCase):
def test_chapter_images_exist_and_are_renderable(self) -> None:
problems: list[str] = []
for chapter_dir in CHAPTER_DIRS:
for markdown_file in iter_markdown_files(chapter_dir):
text = markdown_file.read_text(encoding="utf-8")
references = MARKDOWN_IMAGE_RE.findall(text) + HTML_IMAGE_RE.findall(text)
for reference in references:
if reference.startswith(("http://", "https://", "data:")):
continue
location = f"{markdown_file.relative_to(REPO_ROOT)}: {reference}"
if Path(reference).suffix.lower() in UNRENDERABLE_SUFFIXES:
problems.append(f"unrenderable format {location}")
elif not resolve_reference(chapter_dir, markdown_file, reference).is_file():
problems.append(f"missing file {location}")
self.assertEqual(problems, [], "\n".join(problems))
if __name__ == "__main__":
unittest.main()