|
8 | 8 | import subprocess
|
9 | 9 | import sys
|
10 | 10 | import warnings
|
| 11 | +from collections import defaultdict |
11 | 12 | from dataclasses import replace
|
12 | 13 | from functools import lru_cache
|
13 | 14 | from pathlib import Path
|
14 | 15 | from re import Match, Pattern
|
15 |
| -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Literal |
| 16 | +from typing import TYPE_CHECKING, Any, Callable, ClassVar, Literal, TypeVar |
16 | 17 |
|
17 | 18 | from griffe import (
|
18 | 19 | Alias,
|
|
28 | 29 | )
|
29 | 30 | from jinja2 import TemplateNotFound, pass_context, pass_environment
|
30 | 31 | from markupsafe import Markup
|
31 |
| -from mkdocs_autorefs import AutorefsHookInterface |
| 32 | +from mkdocs_autorefs import AutorefsHookInterface, Backlink, BacklinkCrumb |
32 | 33 | from mkdocstrings import get_logger
|
33 | 34 |
|
34 | 35 | if TYPE_CHECKING:
|
35 |
| - from collections.abc import Iterator, Sequence |
| 36 | + from collections.abc import Iterable, Iterator, Sequence |
36 | 37 |
|
37 | 38 | from griffe import Attribute, Class, Function, Module
|
38 | 39 | from jinja2 import Environment, Template
|
@@ -210,10 +211,15 @@ def do_format_attribute(
|
210 | 211 |
|
211 | 212 | signature = str(attribute_path).strip()
|
212 | 213 | if annotations and attribute.annotation:
|
213 |
| - annotation = template.render(context.parent, expression=attribute.annotation, signature=True) |
| 214 | + annotation = template.render( |
| 215 | + context.parent, |
| 216 | + expression=attribute.annotation, |
| 217 | + signature=True, |
| 218 | + backlink_type="returned-by", |
| 219 | + ) |
214 | 220 | signature += f": {annotation}"
|
215 | 221 | if attribute.value:
|
216 |
| - value = template.render(context.parent, expression=attribute.value, signature=True) |
| 222 | + value = template.render(context.parent, expression=attribute.value, signature=True, backlink_type="used-by") |
217 | 223 | signature += f" = {value}"
|
218 | 224 |
|
219 | 225 | signature = do_format_code(signature, line_length)
|
@@ -725,3 +731,45 @@ def get_context(self) -> AutorefsHookInterface.Context:
|
725 | 731 | filepath=str(filepath),
|
726 | 732 | lineno=lineno,
|
727 | 733 | )
|
| 734 | + |
| 735 | + |
| 736 | +T = TypeVar("T") |
| 737 | +Tree = dict[T, "Tree"] |
| 738 | +CompactTree = dict[tuple[T, ...], "CompactTree"] |
| 739 | +_rtree = lambda: defaultdict(_rtree) # type: ignore[has-type,var-annotated] # noqa: E731 |
| 740 | + |
| 741 | + |
| 742 | +def _tree(data: Iterable[tuple[T, ...]]) -> Tree: |
| 743 | + new_tree = _rtree() |
| 744 | + for nav in data: |
| 745 | + *path, leaf = nav |
| 746 | + node = new_tree |
| 747 | + for key in path: |
| 748 | + node = node[key] |
| 749 | + node[leaf] = _rtree() |
| 750 | + return new_tree |
| 751 | + |
| 752 | + |
| 753 | +def _compact_tree(tree: Tree) -> CompactTree: |
| 754 | + new_tree = _rtree() |
| 755 | + for key, value in tree.items(): |
| 756 | + child = _compact_tree(value) |
| 757 | + if len(child) == 1: |
| 758 | + child_key, child_value = next(iter(child.items())) |
| 759 | + new_key = (key, *child_key) |
| 760 | + new_tree[new_key] = child_value |
| 761 | + else: |
| 762 | + new_tree[(key,)] = child |
| 763 | + return new_tree |
| 764 | + |
| 765 | + |
| 766 | +def do_backlink_tree(backlinks: list[Backlink]) -> CompactTree[BacklinkCrumb]: |
| 767 | + """Build a tree of backlinks. |
| 768 | +
|
| 769 | + Parameters: |
| 770 | + backlinks: The list of backlinks. |
| 771 | +
|
| 772 | + Returns: |
| 773 | + A tree of backlinks. |
| 774 | + """ |
| 775 | + return _compact_tree(_tree(backlink.crumbs for backlink in backlinks)) |
0 commit comments