Skip to content

Commit 2f98882

Browse files
JacobCoffeeclaude
andcommitted
fix: rewrite broken attachment anchor links in built HTML
MyST turns [text](attachments/...) into href="#attachments/..." which creates broken fragment anchors instead of file links. Add a second pass to the build-finished hook that rewrites these to proper relative URLs. Also fix the attachment path regex to handle MoinMoin (XX) encoded directory names. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3b9cec1 commit 2f98882

File tree

1 file changed

+19
-3
lines changed

1 file changed

+19
-3
lines changed

conf.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,21 @@
161161
# -- Attachments -------------------------------------------------------------
162162
# MoinMoin wiki pages reference attachments as relative ``attachments/X/file``
163163
# paths. The actual files live in ``{wiki}/_attachments/X/`` which is excluded
164-
# from the Sphinx source tree. This hook copies referenced attachments into
165-
# the build output so the links resolve correctly.
164+
# from the Sphinx source tree. Two things happen at build-finished:
165+
#
166+
# 1. Referenced attachment files are copied into the build output so that
167+
# ``<img src="attachments/...">`` tags (from ``![](attachments/...)``) work.
168+
#
169+
# 2. MyST turns ``[text](attachments/...)`` links into broken anchor refs
170+
# (``href="#attachments/..."``). We rewrite those to proper relative URLs
171+
# so the files can actually be downloaded.
166172

167173
import re as _re
168174
import shutil as _shutil
169175
from urllib.parse import unquote as _unquote
170176

171177
_ATTACH_RE = _re.compile(r'attachments/((?:[^()\"\s]|\(\w+\))+)')
178+
_HREF_ANCHOR_RE = _re.compile(r'href="#(\.?/?attachments/)')
172179

173180

174181
def _copy_wiki_attachments(app, exception):
@@ -183,6 +190,7 @@ def _copy_wiki_attachments(app, exception):
183190
if not attach_src.exists():
184191
continue
185192

193+
# --- pass 1: copy referenced attachment files into build output ---
186194
for md_file in (srcdir / wiki).rglob("*.md"):
187195
if "_attachments" in md_file.parts or "_exclude" in md_file.parts:
188196
continue
@@ -195,7 +203,6 @@ def _copy_wiki_attachments(app, exception):
195203
html_dir = outdir / md_file.relative_to(srcdir).parent
196204

197205
for ref in refs:
198-
# Try URL-decoded name first, then raw
199206
for candidate in (_unquote(ref), ref):
200207
src_file = attach_src / candidate
201208
if src_file.exists() and src_file.is_file():
@@ -205,6 +212,15 @@ def _copy_wiki_attachments(app, exception):
205212
_shutil.copy2(src_file, dst)
206213
break
207214

215+
# --- pass 2: fix href="#attachments/..." → href="attachments/..." ---
216+
for html_file in outdir.rglob("*.html"):
217+
html = html_file.read_text(errors="ignore")
218+
if "#attachments/" not in html:
219+
continue
220+
fixed = _HREF_ANCHOR_RE.sub(r'href="attachments/', html)
221+
if fixed != html:
222+
html_file.write_text(fixed, encoding="utf-8")
223+
208224

209225
def setup(app):
210226
app.connect("build-finished", _copy_wiki_attachments)

0 commit comments

Comments
 (0)