| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263 |
- #!/usr/bin/env python3
- """Check required lesson sections and local Markdown links."""
- from __future__ import annotations
- import re
- import sys
- from pathlib import Path
- REQUIRED = (
- "本节目标",
- "前置知识",
- "问题动机",
- "核心",
- "实现指南",
- "扩展与联系",
- "精选例题",
- "总结与自检",
- "课后训练建议",
- )
- def main() -> int:
- root = Path(sys.argv[1] if len(sys.argv) > 1 else "课程")
- errors: list[str] = []
- lessons = [
- path
- for path in sorted(root.rglob("*.md"))
- if path.name != "README.md" and not path.name.startswith("00-")
- ]
- for path in lessons:
- text = path.read_text(encoding="utf-8")
- headings = "\n".join(re.findall(r"^#{1,6}\s+(.+)$", text, flags=re.MULTILINE))
- for required in REQUIRED:
- if required not in headings:
- errors.append(f"{path}: missing section containing {required!r}")
- if "<details>" not in text:
- errors.append(f"{path}: no folded hints/self-check content")
- if re.search(r"```(?:cpp|c\+\+)\n.*?\bint\s+main\s*\(", text, flags=re.DOTALL):
- errors.append(f"{path}: standalone C++ implementation must be an attachment")
- if re.search(r"^\\\[\s*$|^\\\]\s*$", text, flags=re.MULTILINE):
- errors.append(f"{path}: display LaTeX must use $$ delimiters, not \\[ or \\]")
- for path in sorted(root.rglob("*.md")):
- text = path.read_text(encoding="utf-8")
- prose = re.sub(r"```.*?```", "", text, flags=re.DOTALL)
- for target in re.findall(r"\[[^]]*\]\(([^)]+)\)", prose):
- target = target.split("#", 1)[0]
- if not target or re.match(r"(?:https?|mailto):", target):
- continue
- resolved = (path.parent / target).resolve()
- if not resolved.exists():
- errors.append(f"{path}: broken local link {target}")
- for error in errors:
- print(error, file=sys.stderr)
- print(f"lessons={len(lessons)} errors={len(errors)}")
- return 1 if errors else 0
- if __name__ == "__main__":
- raise SystemExit(main())
|