Skip to content

Commit a1ef88d

Browse files
siticfelix-hilden
andauthored
Add DIRHTML builder support (#188)
Co-authored-by: Felix Hildén <[email protected]>
1 parent 47d3293 commit a1ef88d

File tree

8 files changed

+93
-11
lines changed

8 files changed

+93
-11
lines changed

docs/src/about.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ is injected after the ordinary Sphinx build is finished.
1616

1717
Caveats
1818
-------
19-
- **Only works with HTML documentation**, disabled otherwise. If the extension
20-
is off, it silently removes directives that would produce output.
19+
- **Only works with HTML or DIRHTML documentation**, disabled otherwise. If the
20+
extension is off, it silently removes directives that would produce output.
2121
- **Only processes blocks, not inline code**. Sphinx has great tools
2222
for linking definitions inline, and longer code should be in a block anyway.
2323
- **Doesn't run example code**. Therefore all possible resolvable types are not

docs/src/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Caveats
6868
-------
6969
For a more thorough explanation, see :ref:`about`.
7070

71-
- Only works with HTML documentation
71+
- Only works with HTML or DIRHTML documentation
7272
- Only processes blocks, not inline code
7373
- Doesn't run example code
7474
- Parsing and type hint resolving is incomplete

docs/src/release_notes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ These release notes are based on
88
sphinx-codeautolink adheres to
99
`Semantic Versioning <https://semver.org>`_.
1010

11+
Unreleased
12+
----------
13+
- Add support for the DIRHTML Sphinx builder (:issue:`188`)
14+
1115
0.17.2 (2025-03-02)
1216
-------------------
1317
- Support :rst:dir:`testsetup` from ``sphinx.ext.doctest`` as another

src/sphinx_codeautolink/extension/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def __init__(self) -> None:
8989
@print_exceptions()
9090
def build_inited(self, app) -> None:
9191
"""Handle initial setup."""
92-
if app.builder.name != "html":
92+
if app.builder.name not in ("html", "dirhtml"):
9393
self.do_nothing = True
9494
return
9595

@@ -268,6 +268,7 @@ def generate_backref_tables(self, app, doctree, docname):
268268
visitor = CodeRefsVisitor(
269269
doctree,
270270
code_refs=self.code_refs,
271+
builder=app.builder.name,
271272
warn_no_backreference=self.warn_no_backreference,
272273
)
273274
doctree.walk(visitor)
@@ -289,6 +290,7 @@ def apply_links(self, app, exception) -> None:
289290
self.inventory,
290291
self.custom_blocks,
291292
self.search_css_classes,
293+
builder_name=app.builder.name,
292294
)
293295

294296
self.cache.write()

src/sphinx_codeautolink/extension/backref.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Backreference tables implementation."""
22

33
from dataclasses import dataclass
4+
from pathlib import Path
45

56
from docutils import nodes
67

@@ -62,11 +63,13 @@ def __init__(
6263
self,
6364
*args,
6465
code_refs: dict[str, list[CodeExample]],
66+
builder: str,
6567
warn_no_backreference: bool = False,
6668
**kwargs,
6769
) -> None:
6870
super().__init__(*args, **kwargs)
6971
self.code_refs = code_refs
72+
self.builder = builder
7073
self.warn_no_backreference = warn_no_backreference
7174

7275
def unknown_departure(self, node) -> None:
@@ -79,7 +82,10 @@ def unknown_visit(self, node) -> None:
7982

8083
items = []
8184
for ref in self.code_refs.get(node.ref, []):
82-
link = ref.document + ".html"
85+
if self.builder == "dirhtml" and Path(ref.document).name != "index":
86+
link = ref.document + "/index.html"
87+
else:
88+
link = ref.document + ".html"
8389
if ref.ref_id is not None:
8490
link += f"#{ref.ref_id}"
8591
items.append((link, " / ".join(ref.headings)))

src/sphinx_codeautolink/extension/block.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,9 +375,14 @@ def link_html(
375375
inventory: dict,
376376
custom_blocks: dict,
377377
search_css_classes: list,
378+
builder_name: str = "html",
378379
) -> None:
379380
"""Inject links to code blocks on disk."""
380-
html_file = Path(out_dir) / (document + ".html")
381+
if builder_name == "dirhtml" and Path(document).name != "index":
382+
html_file = Path(out_dir) / document / "index.html"
383+
else:
384+
html_file = Path(out_dir) / (document + ".html")
385+
381386
text = html_file.read_text("utf-8")
382387
soup = BeautifulSoup(text, "html.parser")
383388

tests/extension/__init__.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ def test_parallel_build(tmp_path: Path):
278278

279279
for file in subfiles:
280280
assert_links(result_dir / (file + ".html"), links)
281+
281282
assert check_link_targets(result_dir) == n_subfiles * len(links)
282283

283284

@@ -312,14 +313,70 @@ def test_skip_identical_code(tmp_path: Path):
312313
assert "sphinx-codeautolink-a" in str(blocks[1])
313314

314315

316+
def test_dirhtml_builder(tmp_path: Path):
317+
index = """
318+
Test project
319+
============
320+
321+
.. toctree::
322+
:maxdepth: 2
323+
324+
page1/index
325+
page2
326+
subdir/page3
327+
328+
Index Page Code
329+
---------------
330+
331+
.. code:: python
332+
333+
import test_project
334+
test_project.bar()
335+
336+
.. automodule:: test_project
337+
"""
338+
339+
page = """
340+
Page {idx}
341+
===========
342+
343+
.. code:: python
344+
345+
import test_project
346+
test_project.bar()
347+
348+
.. autolink-examples:: test_project.bar
349+
"""
350+
351+
files = {
352+
"conf.py": default_conf,
353+
"index.rst": index,
354+
"page1/index.rst": page.format(idx=1),
355+
"page2.rst": page.format(idx=2),
356+
"subdir/page3.rst": page.format(idx=3),
357+
}
358+
links = ["test_project", "test_project.bar"]
359+
360+
result_dir = _sphinx_build(tmp_path, "dirhtml", files)
361+
362+
assert_links(result_dir / "index.html", links)
363+
assert_links(result_dir / "page1/index.html", links)
364+
assert_links(result_dir / "page2/index.html", links)
365+
assert_links(result_dir / "subdir/page3/index.html", links)
366+
367+
assert check_link_targets(result_dir) == len(links) * 4
368+
369+
315370
def _sphinx_build(
316371
folder: Path, builder: str, files: dict[str, str], n_processes: int | None = None
317372
) -> Path:
318373
"""Build Sphinx documentation and return result folder."""
319374
src_dir = folder / "src"
320375
src_dir.mkdir(exist_ok=True)
321376
for name, content in files.items():
322-
(src_dir / name).write_text(content, "utf-8")
377+
path = src_dir / name
378+
path.parent.mkdir(exist_ok=True, parents=True)
379+
path.write_text(content, "utf-8")
323380

324381
build_dir = folder / "build"
325382
args = ["-M", builder, str(src_dir), str(build_dir), "-W"]

tests/extension/_check.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
def check_link_targets(root: Path) -> int:
1313
"""Validate links in HTML site at root, return number of links found."""
1414
site_docs = {
15-
p.relative_to(root): BeautifulSoup(p.read_text("utf-8"), "html.parser")
15+
p: BeautifulSoup(p.read_text("utf-8"), "html.parser")
1616
for p in root.glob("**/*.html")
1717
}
1818
site_ids = {k: gather_ids(v) for k, v in site_docs.items()}
@@ -27,10 +27,18 @@ def check_link_targets(root: Path) -> int:
2727
external_site_ids[base] = gather_ids(sub_soup)
2828
ids = external_site_ids[base]
2929
else:
30-
ids = site_ids[Path(base)]
30+
target_path = (doc.parent / base).resolve()
31+
if target_path.is_dir():
32+
target_path /= "index.html"
33+
assert target_path.exists(), (
34+
f"Target path {target_path!s} not found while validating"
35+
f" link for `{link.string}` in {doc.relative_to(root)!s}!"
36+
)
37+
ids = site_ids[target_path]
38+
3139
assert id_ in ids, (
32-
f"ID {id_} not found in {base}"
33-
f" while validating link for `{link.string}` in {doc!s}!"
40+
f"ID {id_} not found in {base} while validating link"
41+
f" for `{link.string}` in {doc.relative_to(root)!s}!"
3442
)
3543
total += 1
3644
return total

0 commit comments

Comments
 (0)