|
2 | 2 |
|
3 | 3 | from __future__ import annotations
|
4 | 4 |
|
| 5 | +from collections.abc import Iterable |
5 | 6 | import random
|
6 | 7 | import re
|
7 | 8 | import string
|
|
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,
|
|
26 | 27 | DocstringSectionModules,
|
27 | 28 | Object,
|
28 | 29 | )
|
| 30 | +from collections import defaultdict |
| 31 | +from typing import Optional, Sequence, Union |
| 32 | + |
29 | 33 | from jinja2 import TemplateNotFound, pass_context, pass_environment
|
30 | 34 | from markupsafe import Markup
|
31 |
| -from mkdocs_autorefs import AutorefsHookInterface |
| 35 | +from mkdocs_autorefs import AutorefsHookInterface, Backlink, BacklinkCrumb |
32 | 36 | from mkdocstrings.loggers import get_logger
|
33 | 37 |
|
34 | 38 | if TYPE_CHECKING:
|
@@ -210,10 +214,15 @@ def do_format_attribute(
|
210 | 214 |
|
211 | 215 | signature = str(attribute_path).strip()
|
212 | 216 | if annotations and attribute.annotation:
|
213 |
| - annotation = template.render(context.parent, expression=attribute.annotation, signature=True) |
| 217 | + annotation = template.render( |
| 218 | + context.parent, |
| 219 | + expression=attribute.annotation, |
| 220 | + signature=True, |
| 221 | + backlink_type="returned-by", |
| 222 | + ) |
214 | 223 | signature += f": {annotation}"
|
215 | 224 | if attribute.value:
|
216 |
| - value = template.render(context.parent, expression=attribute.value, signature=True) |
| 225 | + value = template.render(context.parent, expression=attribute.value, signature=True, backlink_type="used-by") |
217 | 226 | signature += f" = {value}"
|
218 | 227 |
|
219 | 228 | signature = do_format_code(signature, line_length)
|
@@ -725,3 +734,52 @@ def get_context(self) -> AutorefsHookInterface.Context:
|
725 | 734 | filepath=str(filepath),
|
726 | 735 | lineno=lineno,
|
727 | 736 | )
|
| 737 | + |
| 738 | + |
| 739 | +T = TypeVar("T") |
| 740 | +Tree = dict[T, "Tree"] |
| 741 | +CompactTree = dict[tuple[T, ...], "CompactTree"] |
| 742 | +_rtree = lambda: defaultdict(_rtree) |
| 743 | + |
| 744 | + |
| 745 | +def _tree(data: Iterable[tuple[T, ...]]) -> Tree: |
| 746 | + new_tree = _rtree() |
| 747 | + for nav in data: |
| 748 | + *path, leaf = nav |
| 749 | + node = new_tree |
| 750 | + for key in path: |
| 751 | + node = node[key] |
| 752 | + node[leaf] = _rtree() |
| 753 | + return new_tree |
| 754 | + |
| 755 | + |
| 756 | +def print_tree(tree: Tree, level: int = 0) -> None: |
| 757 | + for key, value in tree.items(): |
| 758 | + print(" " * level + str(key)) |
| 759 | + if value: |
| 760 | + print_tree(value, level + 1) |
| 761 | + |
| 762 | + |
| 763 | +def _compact_tree(tree: Tree) -> CompactTree: |
| 764 | + new_tree = _rtree() |
| 765 | + for key, value in tree.items(): |
| 766 | + child = _compact_tree(value) |
| 767 | + if len(child) == 1: |
| 768 | + child_key, child_value = next(iter(child.items())) |
| 769 | + new_key = (key, *child_key) |
| 770 | + new_tree[new_key] = child_value |
| 771 | + else: |
| 772 | + new_tree[(key,)] = child |
| 773 | + return new_tree |
| 774 | + |
| 775 | + |
| 776 | +def do_backlink_tree(backlinks: list[Backlink]) -> CompactTree[BacklinkCrumb]: |
| 777 | + """Build a tree of backlinks. |
| 778 | +
|
| 779 | + Parameters: |
| 780 | + backlinks: The list of backlinks. |
| 781 | +
|
| 782 | + Returns: |
| 783 | + A tree of backlinks. |
| 784 | + """ |
| 785 | + return _compact_tree(_tree((backlink.crumbs for backlink in backlinks))) |
0 commit comments