Skip to content

Commit ffa06bf

Browse files
committed
refactor: Store actual page instance instead of URL in plugin's current_page attribute
We will need the page instance for the backlinks feature, to build breadcrumbs with titles and URLs of the parents of each backlink.
1 parent b8e480a commit ffa06bf

File tree

5 files changed

+67
-30
lines changed

5 files changed

+67
-30
lines changed

src/mkdocs_autorefs/plugin.py

+33-12
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class AutorefsPlugin(BasePlugin[AutorefsConfig]):
9999
"""
100100

101101
scan_toc: bool = True
102-
current_page: str | None = None
102+
current_page: Page | None = None
103103
# YORE: Bump 2: Remove line.
104104
legacy_refs: bool = True
105105

@@ -138,6 +138,8 @@ def __init__(self) -> None:
138138
self._abs_url_map: dict[str, str] = {}
139139
# YORE: Bump 2: Remove line.
140140
self._get_fallback_anchor: Callable[[str], tuple[str, ...]] | None = None
141+
# YORE: Bump 2: Remove line.
142+
self._url_to_page: dict[str, Page] = {}
141143

142144
self._link_titles: bool | Literal["external"] = True
143145
self._strip_title_tags: bool = False
@@ -162,7 +164,7 @@ def get_fallback_anchor(self, value: Callable[[str], tuple[str, ...]] | None) ->
162164

163165
def register_anchor(
164166
self,
165-
page: str,
167+
page: Page,
166168
identifier: str,
167169
anchor: str | None = None,
168170
*,
@@ -172,18 +174,26 @@ def register_anchor(
172174
"""Register that an anchor corresponding to an identifier was encountered when rendering the page.
173175
174176
Arguments:
175-
page: The relative URL of the current page. Examples: `'foo/bar/'`, `'foo/index.html'`
177+
page: The page where the anchor was found.
176178
identifier: The identifier to register.
177179
anchor: The anchor on the page, without `#`. If not provided, defaults to the identifier.
178180
title: The title of the anchor (optional).
179181
primary: Whether this anchor is the primary one for the identifier.
180182
"""
181-
page_anchor = f"{page}#{anchor or identifier}"
183+
# YORE: Bump 2: Remove block.
184+
if isinstance(page, str):
185+
try:
186+
page = self._url_to_page[page]
187+
except KeyError:
188+
page = self.current_page
189+
190+
url = f"{page.url}#{anchor or identifier}"
182191
url_map = self._primary_url_map if primary else self._secondary_url_map
183192
if identifier in url_map:
184-
if page_anchor not in url_map[identifier]:
185-
url_map[identifier].append(page_anchor)
193+
if url not in url_map[identifier]:
194+
url_map[identifier].append(url)
186195
else:
196+
url_map[identifier] = [url]
187197
if title and url not in self._title_map:
188198
self._title_map[url] = title
189199

@@ -344,7 +354,9 @@ def on_page_markdown(self, markdown: str, page: Page, **kwargs: Any) -> str: #
344354
The same Markdown. We only use this hook to keep a reference to the current page URL,
345355
used during Markdown conversion by the anchor scanner tree processor.
346356
"""
347-
self.current_page = page.url
357+
# YORE: Bump 2: Remove line.
358+
self._url_to_page[page.url] = page
359+
self.current_page = page
348360
return markdown
349361

350362
def on_page_content(self, html: str, page: Page, **kwargs: Any) -> str: # noqa: ARG002
@@ -363,24 +375,33 @@ def on_page_content(self, html: str, page: Page, **kwargs: Any) -> str: # noqa:
363375
Returns:
364376
The same HTML. We only use this hook to map anchors to URLs.
365377
"""
378+
self.current_page = page
379+
# Collect `std`-domain URLs.
366380
if self.scan_toc:
367381
log.debug("Mapping identifiers to URLs for page %s", page.file.src_path)
368382
for item in page.toc.items:
369-
self.map_urls(page.url, item)
383+
self.map_urls(page, item)
370384
return html
371385

372-
def map_urls(self, base_url: str, anchor: AnchorLink) -> None:
386+
def map_urls(self, page: Page, anchor: AnchorLink) -> None:
373387
"""Recurse on every anchor to map its ID to its absolute URL.
374388
375389
This method populates `self._primary_url_map` by side-effect.
376390
377391
Arguments:
378-
base_url: The base URL to use as a prefix for each anchor's relative URL.
392+
page: The page containing the anchors.
379393
anchor: The anchor to process and to recurse on.
380394
"""
381-
self.register_anchor(base_url, anchor.id, title=anchor.title, primary=True)
395+
# YORE: Bump 2: Remove block.
396+
if isinstance(page, str):
397+
try:
398+
page = self._url_to_page[page]
399+
except KeyError:
400+
page = self.current_page
401+
402+
self.register_anchor(page, anchor.id, title=anchor.title, primary=True)
382403
for child in anchor.children:
383-
self.map_urls(base_url, child)
404+
self.map_urls(page, child)
384405

385406
@event_priority(-50) # Late, after mkdocstrings has finished loading inventories.
386407
def on_env(self, env: Environment, /, *, config: MkDocsConfig, files: Files) -> Environment: # noqa: ARG002

src/mkdocs_autorefs/references.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ def __init__(self, plugin: AutorefsPlugin, md: Markdown | None = None) -> None:
541541

542542
def run(self, root: Element) -> None: # noqa: D102
543543
if self.plugin.current_page is not None:
544-
pending_anchors = _PendingAnchors(self.plugin, self.plugin.current_page)
544+
pending_anchors = _PendingAnchors(self.plugin)
545545
self._scan_anchors(root, pending_anchors)
546546
pending_anchors.flush()
547547

@@ -579,18 +579,18 @@ def _scan_anchors(self, parent: Element, pending_anchors: _PendingAnchors, last_
579579
class _PendingAnchors:
580580
"""A collection of HTML anchors that may or may not become aliased to an upcoming heading."""
581581

582-
def __init__(self, plugin: AutorefsPlugin, current_page: str):
582+
def __init__(self, plugin: AutorefsPlugin):
583583
self.plugin = plugin
584-
self.current_page = current_page
585584
self.anchors: list[str] = []
586585

587586
def append(self, anchor: str) -> None:
588587
self.anchors.append(anchor)
589588

590589
def flush(self, alias_to: str | None = None, title: str | None = None) -> None:
591-
for anchor in self.anchors:
592-
self.plugin.register_anchor(self.current_page, anchor, alias_to, title=title, primary=True)
593-
self.anchors.clear()
590+
if page := self.plugin.current_page:
591+
for anchor in self.anchors:
592+
self.plugin.register_anchor(page, anchor, alias_to, title=title, primary=True)
593+
self.anchors.clear()
594594

595595

596596
@lru_cache

tests/helpers.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Helper functions for the tests."""
2+
3+
from mkdocs.config.defaults import MkDocsConfig
4+
from mkdocs.structure.files import File
5+
from mkdocs.structure.pages import Page
6+
7+
8+
def create_page(url: str) -> Page:
9+
"""Create a page with the given URL."""
10+
return Page(
11+
title=url,
12+
file=File(url, "docs", "site", use_directory_urls=False),
13+
config=MkDocsConfig(),
14+
)

tests/test_plugin.py

+10-9
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111

1212
from mkdocs_autorefs.plugin import AutorefsConfig, AutorefsPlugin
1313
from mkdocs_autorefs.references import fix_refs
14+
from tests.helpers import create_page
1415

1516

1617
def test_url_registration() -> None:
1718
"""Check that URLs can be registered, then obtained."""
1819
plugin = AutorefsPlugin()
19-
plugin.register_anchor(identifier="foo", page="foo1.html", primary=True)
20+
plugin.register_anchor(identifier="foo", page=create_page("foo1.html"), primary=True)
2021
plugin.register_url(identifier="bar", url="https://example.org/bar.html")
2122

2223
assert plugin.get_item_url("foo") == ("foo1.html#foo", None)
@@ -28,7 +29,7 @@ def test_url_registration() -> None:
2829
def test_url_registration_with_from_url() -> None:
2930
"""Check that URLs can be registered, then obtained, relative to a page."""
3031
plugin = AutorefsPlugin()
31-
plugin.register_anchor(identifier="foo", page="foo1.html", primary=True)
32+
plugin.register_anchor(identifier="foo", page=create_page("foo1.html"), primary=True)
3233
plugin.register_url(identifier="bar", url="https://example.org/bar.html")
3334

3435
assert plugin.get_item_url("foo", from_url="a/b.html") == ("../foo1.html#foo", None)
@@ -41,7 +42,7 @@ def test_url_registration_with_from_url() -> None:
4142
def test_url_registration_with_fallback() -> None:
4243
"""Check that URLs can be registered, then obtained through a fallback."""
4344
plugin = AutorefsPlugin()
44-
plugin.register_anchor(identifier="foo", page="foo1.html", primary=True)
45+
plugin.register_anchor(identifier="foo", page=create_page("foo1.html"), primary=True)
4546
plugin.register_url(identifier="bar", url="https://example.org/bar.html")
4647

4748
# URL map will be updated with baz -> foo1.html#foo
@@ -60,7 +61,7 @@ def test_url_registration_with_fallback() -> None:
6061
def test_dont_make_relative_urls_relative_again() -> None:
6162
"""Check that URLs are not made relative more than once."""
6263
plugin = AutorefsPlugin()
63-
plugin.register_anchor(identifier="foo.bar.baz", page="foo/bar/baz.html", primary=True)
64+
plugin.register_anchor(identifier="foo.bar.baz", page=create_page("foo/bar/baz.html"), primary=True)
6465

6566
for _ in range(2):
6667
assert plugin.get_item_url("foo.bar.baz", from_url="baz/bar/foo.html") == (
@@ -96,7 +97,7 @@ def test_find_closest_url(base: str, urls: list[str], expected: str) -> None:
9697
def test_register_secondary_url() -> None:
9798
"""Test registering secondary URLs."""
9899
plugin = AutorefsPlugin()
99-
plugin.register_anchor(identifier="foo", page="foo.html", primary=False)
100+
plugin.register_anchor(identifier="foo", page=create_page("foo.html"), primary=False)
100101
assert plugin._secondary_url_map == {"foo": ["foo.html#foo"]}
101102

102103

@@ -105,8 +106,8 @@ def test_warn_multiple_urls(caplog: pytest.LogCaptureFixture, primary: bool) ->
105106
"""Warn when multiple URLs are found for the same identifier."""
106107
plugin = AutorefsPlugin()
107108
plugin.config = AutorefsConfig()
108-
plugin.register_anchor(identifier="foo", page="foo.html", primary=primary)
109-
plugin.register_anchor(identifier="foo", page="bar.html", primary=primary)
109+
plugin.register_anchor(identifier="foo", page=create_page("foo.html"), primary=primary)
110+
plugin.register_anchor(identifier="foo", page=create_page("bar.html"), primary=primary)
110111
url_mapper = functools.partial(plugin.get_item_url, from_url="/hello")
111112
# YORE: Bump 2: Replace `, _legacy_refs=False` with `` within line.
112113
fix_refs('<autoref identifier="foo">Foo</autoref>', url_mapper, _legacy_refs=False)
@@ -120,8 +121,8 @@ def test_use_closest_url(caplog: pytest.LogCaptureFixture, primary: bool) -> Non
120121
plugin = AutorefsPlugin()
121122
plugin.config = AutorefsConfig()
122123
plugin.config.resolve_closest = True
123-
plugin.register_anchor(identifier="foo", page="foo.html", primary=primary)
124-
plugin.register_anchor(identifier="foo", page="bar.html", primary=primary)
124+
plugin.register_anchor(identifier="foo", page=create_page("foo.html"), primary=primary)
125+
plugin.register_anchor(identifier="foo", page=create_page("bar.html"), primary=primary)
125126
url_mapper = functools.partial(plugin.get_item_url, from_url="/hello")
126127
# YORE: Bump 2: Replace `, _legacy_refs=False` with `` within line.
127128
fix_refs('<autoref identifier="foo">Foo</autoref>', url_mapper, _legacy_refs=False)

tests/test_references.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from mkdocs_autorefs.plugin import AutorefsPlugin
1212
from mkdocs_autorefs.references import AutorefsExtension, AutorefsHookInterface, fix_refs, relative_url
13+
from tests.helpers import create_page
1314

1415
if TYPE_CHECKING:
1516
from collections.abc import Mapping
@@ -311,7 +312,7 @@ def test_register_markdown_anchors() -> None:
311312
"""Check that Markdown anchors are registered when enabled."""
312313
plugin = AutorefsPlugin()
313314
md = markdown.Markdown(extensions=["attr_list", "toc", AutorefsExtension(plugin)])
314-
plugin.current_page = "page"
315+
plugin.current_page = create_page("page")
315316
md.convert(
316317
dedent(
317318
"""
@@ -372,7 +373,7 @@ def test_register_markdown_anchors_with_admonition() -> None:
372373
"""Check that Markdown anchors are registered inside a nested admonition element."""
373374
plugin = AutorefsPlugin()
374375
md = markdown.Markdown(extensions=["attr_list", "toc", "admonition", AutorefsExtension(plugin)])
375-
plugin.current_page = "page"
376+
plugin.current_page = create_page("page")
376377
md.convert(
377378
dedent(
378379
"""
@@ -439,7 +440,7 @@ def test_mark_identifiers_as_exact(markdown_ref: str, exact_expected: bool) -> N
439440
"""Mark code and explicit identifiers as exact (no `slug` attribute in autoref elements)."""
440441
plugin = AutorefsPlugin()
441442
md = markdown.Markdown(extensions=["attr_list", "toc", AutorefsExtension(plugin)])
442-
plugin.current_page = "page"
443+
plugin.current_page = create_page("page")
443444
output = md.convert(markdown_ref)
444445
if exact_expected:
445446
assert "slug=" not in output

0 commit comments

Comments
 (0)