Coverage for src/mkdocs_autorefs/_internal/backlinks.py: 90.62%
48 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-24 13:40 +0100
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-24 13:40 +0100
1# Backlinks module.
3from __future__ import annotations
5import logging
6from dataclasses import dataclass
7from typing import TYPE_CHECKING, ClassVar
9from markdown.core import Markdown
10from markdown.treeprocessors import Treeprocessor
12if TYPE_CHECKING:
13 from xml.etree.ElementTree import Element
15 from markdown import Markdown
17 from mkdocs_autorefs._internal.plugin import AutorefsPlugin
19try:
20 from mkdocs.plugins import get_plugin_logger
22 _log = get_plugin_logger(__name__)
23except ImportError:
24 # TODO: remove once support for MkDocs <1.5 is dropped
25 _log = logging.getLogger(f"mkdocs.plugins.{__name__}") # type: ignore[assignment]
28@dataclass(eq=True, frozen=True, order=True)
29class BacklinkCrumb:
30 """A navigation breadcrumb for a backlink."""
32 title: str
33 """The title of the page."""
34 url: str
35 """The URL of the page."""
38@dataclass(eq=True, frozen=True, order=True)
39class Backlink:
40 """A backlink (list of breadcrumbs)."""
42 crumbs: tuple[BacklinkCrumb, ...]
43 """The list of breadcrumbs."""
46class BacklinksTreeProcessor(Treeprocessor):
47 """Enhance autorefs with `backlink-type` and `backlink-anchor` attributes.
49 These attributes are then used later to register backlinks.
50 """
52 name: str = "mkdocs-autorefs-backlinks"
53 """The name of the tree processor."""
54 initial_id: str | None = None
55 """The initial heading ID."""
57 _htags: ClassVar[set[str]] = {"h1", "h2", "h3", "h4", "h5", "h6"}
59 def __init__(self, plugin: AutorefsPlugin, md: Markdown | None = None) -> None:
60 """Initialize the tree processor.
62 Parameters:
63 plugin: A reference to the autorefs plugin, to use its `register_anchor` method.
64 """
65 super().__init__(md)
66 self._plugin = plugin
67 self._last_heading_id: str | None = None
69 def run(self, root: Element) -> None:
70 """Run the tree processor.
72 Arguments:
73 root: The root element of the document.
74 """
75 if self._plugin.current_page is not None: 75 ↛ exitline 75 didn't return from function 'run' because the condition on line 75 was always true
76 self._last_heading_id = self.initial_id
77 self._enhance_autorefs(root)
79 def _enhance_autorefs(self, parent: Element) -> None:
80 for el in parent:
81 if el.tag == "a": # Markdown anchor.
82 if not (el.text or el.get("href") or (el.tail and el.tail.strip())) and (anchor_id := el.get("id")): 82 ↛ 80line 82 didn't jump to line 80 because the condition on line 82 was always true
83 self._last_heading_id = anchor_id
84 elif el.tag in self._htags: # Heading.
85 self._last_heading_id = el.get("id")
86 elif el.tag == "autoref":
87 if "backlink-type" not in el.attrib: 87 ↛ 89line 87 didn't jump to line 89 because the condition on line 87 was always true
88 el.set("backlink-type", "referenced-by")
89 if "backlink-anchor" not in el.attrib and self._last_heading_id: 89 ↛ 80line 89 didn't jump to line 80 because the condition on line 89 was always true
90 el.set("backlink-anchor", self._last_heading_id)
91 else:
92 self._enhance_autorefs(el)