Skip to content

Commit

Permalink
Fix: Use relative URIs for backreference links (#190)
Browse files Browse the repository at this point in the history
  • Loading branch information
sitic authored Mar 7, 2025
1 parent 92d3a63 commit f0e1fb4
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 8 deletions.
4 changes: 4 additions & 0 deletions docs/src/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ These release notes are based on
sphinx-codeautolink adheres to
`Semantic Versioning <https://semver.org>`_.

Unreleased
----------
- Fix backreference links using relative URIs (:issue:`190`)

0.17.3 (2025-03-06)
-------------------
- Fix Sphinx InventoryItem deprecation warning (:issue:`173`)
Expand Down
3 changes: 2 additions & 1 deletion src/sphinx_codeautolink/extension/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,8 @@ def generate_backref_tables(self, app, doctree, docname):
visitor = CodeRefsVisitor(
doctree,
code_refs=self.code_refs,
builder=app.builder.name,
docname=docname,
builder=app.builder,
warn_no_backreference=self.warn_no_backreference,
)
doctree.walk(visitor)
Expand Down
11 changes: 5 additions & 6 deletions src/sphinx_codeautolink/extension/backref.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""Backreference tables implementation."""

from dataclasses import dataclass
from pathlib import Path

from docutils import nodes
from sphinx.builders import Builder

from sphinx_codeautolink.warn import logger, warn_type

Expand Down Expand Up @@ -63,12 +63,14 @@ def __init__(
self,
*args,
code_refs: dict[str, list[CodeExample]],
builder: str,
docname: str,
builder: Builder,
warn_no_backreference: bool = False,
**kwargs,
) -> None:
super().__init__(*args, **kwargs)
self.code_refs = code_refs
self.docname = docname
self.builder = builder
self.warn_no_backreference = warn_no_backreference

Expand All @@ -82,10 +84,7 @@ def unknown_visit(self, node) -> None:

items = []
for ref in self.code_refs.get(node.ref, []):
if self.builder == "dirhtml" and Path(ref.document).name != "index":
link = ref.document + "/index.html"
else:
link = ref.document + ".html"
link = self.builder.get_relative_uri(self.docname, ref.document)
if ref.ref_id is not None:
link += f"#{ref.ref_id}"
items.append((link, " / ".join(ref.headings)))
Expand Down
54 changes: 53 additions & 1 deletion tests/extension/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from bs4 import BeautifulSoup
from sphinx.cmd.build import main as sphinx_main

from ._check import check_link_targets
from ._check import check_link_targets, check_reference_targets_exist

# Insert test package root to path for all tests
sys.path.insert(0, str(Path(__file__).parent / "src"))
Expand Down Expand Up @@ -365,6 +365,58 @@ def test_dirhtml_builder(tmp_path: Path):
assert_links(result_dir / "subdir/page3/index.html", links)

assert check_link_targets(result_dir) == len(links) * 4
check_reference_targets_exist(result_dir)


def test_html_subdir_reference(tmp_path: Path):
index = """
Test project
============
.. toctree::
subdir/page1
subdir/subdir2/page2
Index Page
----------
.. code:: python
import test_project
test_project.bar()
.. automodule:: test_project
"""

page = """
Page {idx}
===========
.. code:: python
import test_project
test_project.bar()
.. autolink-examples:: test_project.bar
"""

files = {
"conf.py": default_conf,
"index.rst": index,
"subdir/page1.rst": page.format(idx=1),
"subdir/subdir2/page2.rst": page.format(idx=2),
}
links = ["test_project", "test_project.bar"]

result_dir = _sphinx_build(tmp_path, "html", files)

assert_links(result_dir / "index.html", links)
assert_links(result_dir / "subdir/page1.html", links)
assert_links(result_dir / "subdir/subdir2/page2.html", links)

assert check_link_targets(result_dir) == len(links) * 3
check_reference_targets_exist(result_dir)


def _sphinx_build(
Expand Down
19 changes: 19 additions & 0 deletions tests/extension/_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@ def check_link_targets(root: Path) -> int:
return total


def check_reference_targets_exist(root: Path):
site_docs = {
p: BeautifulSoup(p.read_text("utf-8"), "html.parser")
for p in root.glob("**/*.html")
}
for doc, soup in site_docs.items():
for link in soup.find_all("a", attrs={"class": "reference internal"}):
base = link["href"].split("#")[0]
if any(base.startswith(s) for s in ("http://", "https://")):
continue
target_path = doc if base == "" else (doc.parent / base).resolve()
if target_path.is_dir():
target_path /= "index.html"
assert target_path.exists(), (
f"Target path {target_path!s} not found while validating"
f" link for `{link.string}` in {doc.relative_to(root)!s}!"
)


def gather_ids(soup: BeautifulSoup) -> set:
"""Gather all HTML IDs from a given page."""
return {tag["id"] for tag in soup.find_all(id=True)}

0 comments on commit f0e1fb4

Please sign in to comment.