15 @brief Parse command-line arguments.
16 @return Value returned by `parse_args()`.
18 parser = argparse.ArgumentParser(
20 "Check local markdown links for README.md plus docs/examples trees.\n"
21 "HTTP(S), mailto, and in-page anchor links are skipped."
23 formatter_class=argparse.RawTextHelpFormatter,
26 " python3 scripts/check_markdown_links.py\n"
27 " python3 scripts/check_markdown_links.py --repo-root . --docs-dir docs --examples-dir examples\n"
28 " python3 scripts/check_markdown_links.py --no-readme"
34 default=Path(__file__).resolve().parents[1],
35 help=
"Repository root directory (default: parent of this script).",
40 help=
"Docs subtree under repo root to scan recursively for *.md (default: docs).",
45 help=
"Examples subtree under repo root to scan recursively for *.md (default: examples).",
50 help=
"Skip README.md in scan set.",
52 return parser.parse_args()
57 @brief Yield markdown files from configured roots.
58 @param[in] repo_root Argument passed to `iter_markdown_files()`.
59 @param[in] docs_dir Argument passed to `iter_markdown_files()`.
60 @param[in] examples_dir Argument passed to `iter_markdown_files()`.
61 @param[in] include_readme Argument passed to `iter_markdown_files()`.
64 yield repo_root /
"README.md"
66 docs_root = repo_root / docs_dir
67 examples_root = repo_root / examples_dir
69 for md
in sorted(docs_root.rglob(
"*.md")):
70 if "docs_build" in md.parts:
73 for md
in sorted(examples_root.rglob(
"*.md")):
104 @brief Entry point for this script.
105 @return Value returned by `main()`.
108 repo_root = args.repo_root.resolve()
113 docs_dir=args.docs_dir,
114 examples_dir=args.examples_dir,
115 include_readme=
not args.no_readme,
117 if not md_file.is_file():
118 failures.append((str(md_file),
"<file>",
"Markdown file not found"))
120 content = md_file.read_text(encoding=
"utf-8", errors=
"replace")
121 for match
in LINK_PATTERN.findall(content):
122 target = match[0]
or match[1]
128 resolved = (md_file.parent / normalized).resolve()
130 exists = resolved.exists()
131 except OSError
as exc:
132 failures.append((str(md_file.relative_to(repo_root)), target, f
"{resolved} ({exc})"))
135 failures.append((str(md_file.relative_to(repo_root)), target, str(resolved)))
138 print(
"Broken markdown links detected:")
139 for src, target, resolved
in failures:
140 print(f
" - {src}: '{target}' -> missing '{resolved}'")
144 if not args.no_readme:
145 scope.append(
"README.md")
146 scope.append(f
"{args.docs_dir}/**/*.md")
147 scope.append(f
"{args.examples_dir}/**/*.md")
148 print(f
"Markdown link check passed for {', '.join(scope)}")