- Markdown lint via markdownlint-cli2 container - Internal cross-reference checker for See Also sections - Both jobs run on linux runner using podman containers
67 lines
1.9 KiB
Python
67 lines
1.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Check internal cross-references between topic files.
|
|
|
|
Scans all .md files for See Also links to other topics (backtick-quoted names)
|
|
and verifies that matching files exist under topics/.
|
|
|
|
Exit codes:
|
|
0 — all links valid
|
|
1 — broken links found
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
TOPICS_DIR = Path("topics")
|
|
DOCS_DIR = Path("docs")
|
|
SEARCH_DIRS = [Path("."), TOPICS_DIR, DOCS_DIR]
|
|
EXCLUDE = {"docs/TEMPLATE.md"}
|
|
|
|
|
|
def find_md_files() -> list[Path]:
|
|
files = []
|
|
for d in SEARCH_DIRS:
|
|
if d.exists():
|
|
files.extend(d.glob("*.md"))
|
|
return sorted(set(files))
|
|
|
|
|
|
def extract_topic_refs(path: Path) -> list[tuple[int, str]]:
|
|
"""Extract backtick-quoted topic references from See Also sections."""
|
|
refs = []
|
|
in_see_also = False
|
|
for lineno, line in enumerate(path.read_text().splitlines(), start=1):
|
|
if re.match(r"^##\s+See Also", line, re.IGNORECASE):
|
|
in_see_also = True
|
|
continue
|
|
if in_see_also and re.match(r"^##\s+", line):
|
|
break
|
|
if in_see_also:
|
|
for match in re.finditer(r"`([a-z][a-z0-9-]*)`", line):
|
|
refs.append((lineno, match.group(1)))
|
|
return refs
|
|
|
|
|
|
def check_links() -> int:
|
|
md_files = find_md_files()
|
|
topic_names = {p.stem for p in TOPICS_DIR.glob("*.md")} if TOPICS_DIR.exists() else set()
|
|
|
|
errors = 0
|
|
for path in [p for p in md_files if str(p) not in EXCLUDE]:
|
|
for lineno, ref in extract_topic_refs(path):
|
|
if ref not in topic_names:
|
|
print(f" {path}:{lineno} broken ref `{ref}` — no topics/{ref}.md")
|
|
errors += 1
|
|
|
|
if errors:
|
|
print(f"\n {errors} broken link(s) found")
|
|
return 1
|
|
|
|
print(f" {len(md_files)} files checked, all internal links valid")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(check_links())
|