-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhooks.py
More file actions
189 lines (150 loc) · 6.25 KB
/
hooks.py
File metadata and controls
189 lines (150 loc) · 6.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
"""MkDocs hooks for the HyperCache site.
Two responsibilities:
1. Rewrites repo-relative links to source files (`../pkg/foo.go`,
`../../hypercache.go`, etc.) into canonical GitHub URLs, so the
same markdown source renders correctly both on github.com and on
the GitHub Pages MkDocs build. Without this, the operations
runbook and the RFCs reference dozens of source files via paths
like `../pkg/backend/dist_memory.go`. GitHub renders those as
in-repo links; MkDocs's strict mode flags them as broken
because `pkg/` is not part of the documentation tree. Rewriting
them at build time keeps the source markdown GitHub-friendly
while letting strict mode actually enforce docs-internal
correctness.
2. Injects `cmd/hypercache-server/openapi.yaml` into the docs build
as `api/openapi.yaml`, so the Swagger UI page on the docs site
renders the same spec the binary embeds. The spec lives next to
the binary (Go's `embed` cannot traverse `..`) but the docs
build needs it on its virtual filesystem — an `on_files` hook
adds it without requiring a duplicate file checked into `docs/`.
"""
import os
import re
from pathlib import Path
from typing import Any
from mkdocs.structure.files import File
GITHUB_REPO_BASE = "https://github.com/hyp3rd/hypercache/blob/main"
# File extensions that we treat as "source code, not docs" — links
# to these get rewritten to GitHub URLs. .md is intentionally NOT
# in this list because doc-to-doc links should stay intra-site so
# MkDocs can validate them.
SOURCE_EXTENSIONS = (
".go",
".yaml",
".yml",
".sh",
".rb",
".txt",
".dockerignore",
".gitignore",
".env",
"Dockerfile",
"Makefile",
)
# Paths that are entire directories the docs reference for context
# (e.g. "see internal/cluster/"). These get rewritten to GitHub
# tree URLs — clicking takes the reader to a directory listing.
SOURCE_DIR_PREFIXES = (
"pkg/",
"internal/",
"cmd/",
"chart/",
"scripts/",
"tests/",
"__examples/",
".github/",
"docker/",
"_mkdocs/",
)
LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
def _is_source_link(target: str) -> bool:
"""Return True when the link target looks like a repo source ref
rather than an in-tree docs link."""
# Strip anchor before extension/prefix checks.
clean = target.split("#", 1)[0]
# Source files by extension or basename.
if clean.endswith(SOURCE_EXTENSIONS):
return True
# Directory references (no extension) that match known source
# roots. We resolve `..` segments first so the prefix match
# works against repo-rooted paths.
parts = [p for p in clean.split("/") if p and p != "."]
# Drop leading `..` segments — they all collapse to repo root
# for our purposes (rewrite-target side).
while parts and parts[0] == "..":
parts.pop(0)
if not parts:
return False
repo_path = "/".join(parts)
if any(repo_path.startswith(p) for p in SOURCE_DIR_PREFIXES):
return True
return False
def _resolve_to_repo_root(page_src_path: str, target: str) -> str:
"""Translate a relative target into a repo-rooted path.
Page src_path is relative to docs/ (e.g. `rfcs/0001-foo.md`).
Target is relative to the page (e.g. `../../pkg/foo.go`). The
returned path is relative to the repo root.
"""
# `os.path.normpath` collapses `..` correctly; we anchor at
# `docs/<page_dir>` and resolve from there.
page_dir = os.path.dirname(page_src_path)
docs_anchored = os.path.normpath(os.path.join("docs", page_dir, target))
# The result may still start with `../` if the relative target
# walked above the repo root (it shouldn't in practice). Trim
# any leading `../` defensively.
while docs_anchored.startswith("../"):
docs_anchored = docs_anchored[3:]
return docs_anchored
# Path of the canonical OpenAPI spec the server binary embeds.
# Resolved relative to the repo root (one above `_mkdocs/`), so
# this works whether MkDocs is invoked from the repo root or from
# inside `docs/`.
_REPO_ROOT = Path(__file__).resolve().parent.parent
_OPENAPI_SOURCE = _REPO_ROOT / "cmd" / "hypercache-server" / "openapi.yaml"
# Where the spec appears on the rendered site — Swagger UI on
# `docs/api.md` references this URL.
_OPENAPI_DOCS_PATH = "api/openapi.yaml"
def on_files(files: Any, config: Any, **kwargs: Any) -> Any:
"""Inject the embedded OpenAPI spec as a docs-site asset.
Without this, `docs/api.md`'s Swagger UI tag would reference a
file that does not exist in the docs tree (the spec lives
under `cmd/hypercache-server/`). Adding it as a virtual File
keeps a single source of truth — the binary's embedded spec
is what the docs site renders.
"""
if not _OPENAPI_SOURCE.exists():
# Defensive: if the spec was renamed/moved, fail loud
# rather than silently render a stale asset.
raise FileNotFoundError(
f"OpenAPI spec not found at {_OPENAPI_SOURCE}; update "
f"_mkdocs/hooks.py:_OPENAPI_SOURCE if it moved."
)
files.append(
File(
path=_OPENAPI_SOURCE.name,
src_dir=str(_OPENAPI_SOURCE.parent),
dest_dir=config["site_dir"],
use_directory_urls=False,
dest_uri=_OPENAPI_DOCS_PATH,
)
)
return files
def on_page_markdown(markdown: str, page: Any, **kwargs: Any) -> str:
"""Rewrite source-code links on every page before MkDocs renders it."""
page_src = page.file.src_path
def replace(match: re.Match[str]) -> str:
link_text = match.group(1)
link_target = match.group(2)
# Absolute URLs, mailtos, and pure anchors stay as-is.
if link_target.startswith(("http://", "https://", "mailto:", "#")):
return match.group(0)
if not _is_source_link(link_target):
return match.group(0)
repo_path = _resolve_to_repo_root(page_src, link_target)
# Preserve any anchor on the target (e.g. line ranges like
# `pkg/foo.go#L34-L58`).
if "#" in link_target and "#" not in repo_path:
anchor = "#" + link_target.split("#", 1)[1]
repo_path += anchor
return f"[{link_text}]({GITHUB_REPO_BASE}/{repo_path})"
return LINK_RE.sub(replace, markdown)