diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-23 13:48:07 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-23 13:48:07 -0400 |
| commit | 681494a79e6c0e084354eec0229386a696a314c0 (patch) | |
| tree | f0d69e9583cd6ed752da7e40c8167df24e228a9a /scripts/check-docs-links.py | |
| parent | bfcb856e09207d8edb714373d775537165d06b05 (diff) | |
| download | open-iov-681494a79e6c0e084354eec0229386a696a314c0.tar.gz open-iov-681494a79e6c0e084354eec0229386a696a314c0.zip | |
docs: merge the legacy flat documents, add the CUDA deep-dive and link checks
* Merge and remove the pre-reboot flat documents, preserving their still-useful
material in the structured tree: debugging workflows and the EPT-livelock
case study (05-operations/02), performance microbenchmarks and A/B
methodology (05-operations/01), the version-upgrade runbook
(05-operations/02 section 4, with corrected host/guest framing), kernel-code
pitfalls (07-developers) and the OpenRM build/load order
(03-architecture/06 section 7). The remaining legacy files were verified
redundant and removed.
* docs/README now records where each migrated topic lives.
* Portability: the config YAML files are called out as design targets that no
code reads yet, with what adding support actually requires today.
* Getting started: the OpenRM step is marked optional (native vGPU-management
workflows only; the emulated path uses the distro driver).
* tools docs: rmtool/rmtop marked as planned, pointing at the implemented
tools/rmq subset.
* wiki: new CUDA-Userspace-Driver deep-dive page (public-safe, links the
canonical userspace/cuda analysis) and index update.
* scripts/check-docs-links.py plus a CI step so relative links cannot rot.
* STATE.md references to removed documents repointed.
Diffstat (limited to 'scripts/check-docs-links.py')
| -rwxr-xr-x | scripts/check-docs-links.py | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/scripts/check-docs-links.py b/scripts/check-docs-links.py new file mode 100755 index 0000000..fcb39ca --- /dev/null +++ b/scripts/check-docs-links.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +"""Check relative links in the project's tracked markdown documentation. + +Walks docs/, wiki/ and userspace/cuda/, extracts markdown links, and fails if +a relative target does not exist. External links (http/https/mailto/tel/ftp/ +data/javascript), pure anchors and links inside fenced code blocks are +skipped. + +The raw imported wiki HTML dumps and the archived wiki copy are excluded: +their markup is not ours and their link shape is not representative. + +Usage: + python3 scripts/check-docs-links.py +""" + +import os +import re +import subprocess +import sys +from urllib.parse import unquote + +REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +WATCHED_ROOTS = ("docs", "wiki", "userspace/cuda") + +EXCLUDED_FILES = { + "wiki/GPU_Driver_Internals.md", + "wiki/GPU_Firmware.md", + "wiki/GPU_SBOM.md", + "wiki/Virtual_IO_Internals.md", +} +EXCLUDED_PREFIXES = ("wiki/open-iov.org/",) + +SKIPPED_SCHEMES = ( + "http://", + "https://", + "mailto:", + "tel:", + "ftp://", + "ftps://", + "data:", + "javascript:", +) + +LINK_RE = re.compile(r"!?\[[^\]]*\]\(\s*([^)]+?)\s*\)") + + +def tracked_markdown_files(): + """Return repo-relative paths of tracked markdown files (git first).""" + try: + out = subprocess.run( + ["git", "ls-files", "--", *WATCHED_ROOTS], + cwd=REPO_ROOT, + check=True, + capture_output=True, + text=True, + ).stdout + except (OSError, subprocess.CalledProcessError): + out = "" + if out.strip(): + return [line for line in out.splitlines() if line.endswith(".md")] + + files = [] + for root in WATCHED_ROOTS: + for dirpath, _dirnames, filenames in os.walk(os.path.join(REPO_ROOT, root)): + for name in filenames: + if name.endswith(".md"): + full = os.path.join(dirpath, name) + files.append(os.path.relpath(full, REPO_ROOT)) + return files + + +def excluded(relpath): + if relpath in EXCLUDED_FILES: + return True + return relpath.startswith(EXCLUDED_PREFIXES) + + +def strip_code_blocks(text): + """Blank out fenced code blocks so example links are not checked.""" + lines = [] + fence = None + for line in text.splitlines(): + stripped = line.lstrip() + marker = stripped[:3] + if fence is None: + if marker in ("```", "~~~"): + fence = marker + lines.append("") + continue + lines.append(line) + else: + if stripped.startswith(fence): + fence = None + lines.append("") + return "\n".join(lines) + + +def normalize_target(raw): + target = raw.strip() + if target.startswith("<") and target.endswith(">"): + target = target[1:-1].strip() + if not target: + return None + if target.startswith("#"): + return None + if target.lower().startswith(SKIPPED_SCHEMES): + return None + # Drop an anchor and any markdown title, then percent-decode. + target = target.split()[0] if " " in target else target + target = target.split("#", 1)[0] + target = unquote(target) + if not target: + return None + return target + + +def check_file(relpath): + full = os.path.join(REPO_ROOT, relpath) + with open(full, "r", encoding="utf-8", errors="replace") as handle: + text = strip_code_blocks(handle.read()) + + problems = [] + in_fence = False + for lineno, line in enumerate(text.splitlines(), start=1): + if line.lstrip()[:3] in ("```", "~~~"): + in_fence = not in_fence + continue + if in_fence: + continue + for raw in LINK_RE.findall(line): + target = normalize_target(raw) + if target is None: + continue + if target.startswith("/"): + resolved = os.path.join(REPO_ROOT, target.lstrip("/")) + else: + resolved = os.path.normpath( + os.path.join(REPO_ROOT, os.path.dirname(relpath), target) + ) + if not os.path.exists(resolved): + problems.append((lineno, target, os.path.relpath(resolved, REPO_ROOT))) + return problems + + +def main(): + files = sorted( + relpath + for relpath in tracked_markdown_files() + if not excluded(relpath) + ) + failures = 0 + for relpath in files: + for lineno, target, resolved in check_file(relpath): + failures += 1 + print(f"{relpath}:{lineno}: broken link: {target} (resolved: {resolved})") + + if failures: + print(f"\n{failures} broken relative link(s) in {len(files)} file(s).") + return 1 + print(f"OK: {len(files)} markdown file(s) checked, no broken relative links.") + return 0 + + +if __name__ == "__main__": + sys.exit(main())
\ No newline at end of file |
