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

1# Backlinks module. 

2 

3from __future__ import annotations 

4 

5import logging 

6from dataclasses import dataclass 

7from typing import TYPE_CHECKING, ClassVar 

8 

9from markdown.core import Markdown 

10from markdown.treeprocessors import Treeprocessor 

11 

12if TYPE_CHECKING: 

13 from xml.etree.ElementTree import Element 

14 

15 from markdown import Markdown 

16 

17 from mkdocs_autorefs._internal.plugin import AutorefsPlugin 

18 

19try: 

20 from mkdocs.plugins import get_plugin_logger 

21 

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] 

26 

27 

28@dataclass(eq=True, frozen=True, order=True) 

29class BacklinkCrumb: 

30 """A navigation breadcrumb for a backlink.""" 

31 

32 title: str 

33 """The title of the page.""" 

34 url: str 

35 """The URL of the page.""" 

36 

37 

38@dataclass(eq=True, frozen=True, order=True) 

39class Backlink: 

40 """A backlink (list of breadcrumbs).""" 

41 

42 crumbs: tuple[BacklinkCrumb, ...] 

43 """The list of breadcrumbs.""" 

44 

45 

46class BacklinksTreeProcessor(Treeprocessor): 

47 """Enhance autorefs with `backlink-type` and `backlink-anchor` attributes. 

48 

49 These attributes are then used later to register backlinks. 

50 """ 

51 

52 name: str = "mkdocs-autorefs-backlinks" 

53 """The name of the tree processor.""" 

54 initial_id: str | None = None 

55 """The initial heading ID.""" 

56 

57 _htags: ClassVar[set[str]] = {"h1", "h2", "h3", "h4", "h5", "h6"} 

58 

59 def __init__(self, plugin: AutorefsPlugin, md: Markdown | None = None) -> None: 

60 """Initialize the tree processor. 

61 

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 

68 

69 def run(self, root: Element) -> None: 

70 """Run the tree processor. 

71 

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) 

78 

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)