Skip to content

Commit 478c615

Browse files
committed
refactor: Prepare backlinks support
Issue-153: #153
1 parent a0e888c commit 478c615

File tree

62 files changed

+342
-178
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+342
-178
lines changed

docs/css/mkdocstrings.css

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,48 @@ a.external:hover::after,
2525
a.autorefs-external:hover::after {
2626
background-color: var(--md-accent-fg-color);
2727
}
28+
29+
/* Tree-like output for backlinks. */
30+
.doc-backlink-list {
31+
--tree-clr: var(--md-default-fg-color);
32+
--tree-font-size: 1rem;
33+
--tree-item-height: 1;
34+
--tree-offset: 1rem;
35+
--tree-thickness: 1px;
36+
--tree-style: solid;
37+
display: grid;
38+
list-style: none !important;
39+
}
40+
41+
.doc-backlink-list li > span:first-child {
42+
text-indent: .3rem;
43+
}
44+
.doc-backlink-list li {
45+
padding-inline-start: var(--tree-offset);
46+
border-left: var(--tree-thickness) var(--tree-style) var(--tree-clr);
47+
position: relative;
48+
margin-left: 0 !important;
49+
50+
&:last-child {
51+
border-color: transparent;
52+
}
53+
&::before{
54+
content: '';
55+
position: absolute;
56+
top: calc(var(--tree-item-height) / 2 * -1 * var(--tree-font-size) + var(--tree-thickness));
57+
left: calc(var(--tree-thickness) * -1);
58+
width: calc(var(--tree-offset) + var(--tree-thickness) * 2);
59+
height: calc(var(--tree-item-height) * var(--tree-font-size));
60+
border-left: var(--tree-thickness) var(--tree-style) var(--tree-clr);
61+
border-bottom: var(--tree-thickness) var(--tree-style) var(--tree-clr);
62+
}
63+
&::after{
64+
content: '';
65+
position: absolute;
66+
border-radius: 50%;
67+
background-color: var(--tree-clr);
68+
top: calc(var(--tree-item-height) / 2 * 1rem);
69+
left: var(--tree-offset) ;
70+
translate: calc(var(--tree-thickness) * -1) calc(var(--tree-thickness) * -1);
71+
}
72+
}

docs/insiders/changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## mkdocstrings-python Insiders
44

5+
### 1.10.0 <small>March 10, 2025</small> { id="1.10.0" }
6+
7+
- [Backlinks][backlinks]
8+
59
### 1.9.0 <small>September 03, 2024</small> { id="1.9.0" }
610

711
- [Relative cross-references][relative_crossrefs]

docs/insiders/goals.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,6 @@ goals:
4242
- name: Scoped cross-references
4343
ref: /usage/configuration/docstrings/#scoped_crossrefs
4444
since: 2024/09/03
45+
- name: Backlinks
46+
ref: /usage/configuration/general/#backlinks
47+
since: 2025/03/10

docs/usage/configuration/general.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,38 @@ plugins:
6060
////
6161
///
6262
63+
[](){#option-backlinks}
64+
## `backlinks`
65+
66+
[:octicons-heart-fill-24:{ .pulse } Sponsors only](../../insiders/index.md){ .insiders } &mdash;
67+
[:octicons-tag-24: Insiders 1.10.0](../../insiders/changelog.md#1.10.0)
68+
69+
- **:octicons-package-24: Type <code><autoref identifier="typing.Literal" optional>Literal</autoref>["flat", "tree", False]</code> :material-equal: `False`{ title="default value" }**
70+
71+
The `backlinks` option enables rendering of backlinks within your API documentation.
72+
73+
When an arbitrary section of your documentation links to an API symbol, this link will be collected as a backlink, and rendered below your API symbol. In short, the API symbol will link back to the section that links to it. Such backlinks will help your users navigate the documentation, as they will immediately which functions return a specific symbol, or where a specific symbol is accepted as parameter, etc..
74+
75+
Each backlink is a list of breadcrumbs that represent the navigation, from the root page down to the given section.
76+
77+
The available styles for rendering backlinks are **`flat`** and **`tree`**.
78+
79+
- **`flat`** will render backlinks as a single-layer list. This can lead to repetition of breadcrumbs.
80+
- **`tree`** will combine backlinks into a tree, to remove repetition of breadcrumbs.
81+
82+
WARNING: **Global-only option.** For now, the option only works when set globally in `mkdocs.yml`.
83+
84+
```yaml title="in mkdocs.yml (global configuration)"
85+
plugins:
86+
- mkdocstrings:
87+
handlers:
88+
python:
89+
options:
90+
backlinks: tree
91+
```
92+
93+
<!-- TODO: Add screenshots! -->
94+
6395
[](){#option-extensions}
6496
## `extensions`
6597

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ plugins:
160160
- https://mkdocstrings.github.io/griffe/objects.inv
161161
- https://python-markdown.github.io/objects.inv
162162
options:
163+
backlinks: tree
163164
docstring_options:
164165
ignore_init_summary: true
165166
docstring_section_style: list

src/mkdocstrings_handlers/python/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,14 @@ class PythonInputOptions:
386386
),
387387
] = "brief"
388388

389+
backlinks: Annotated[
390+
Literal["flat", "tree", False],
391+
Field(
392+
group="general",
393+
description="Whether to render backlinks, and how.",
394+
),
395+
] = False
396+
389397
docstring_options: Annotated[
390398
GoogleStyleOptions | NumpyStyleOptions | SphinxStyleOptions | AutoStyleOptions | None,
391399
Field(

src/mkdocstrings_handlers/python/handler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ def update_env(self, config: Any) -> None: # noqa: ARG002
301301
self.env.filters["as_functions_section"] = rendering.do_as_functions_section
302302
self.env.filters["as_classes_section"] = rendering.do_as_classes_section
303303
self.env.filters["as_modules_section"] = rendering.do_as_modules_section
304+
self.env.filters["backlink_tree"] = rendering.do_backlink_tree
304305
self.env.globals["AutorefsHook"] = rendering.AutorefsHook
305306
self.env.tests["existing_template"] = lambda template_name: template_name in self.env.list_templates()
306307

src/mkdocstrings_handlers/python/rendering.py

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88
import subprocess
99
import sys
1010
import warnings
11+
from collections import defaultdict
1112
from dataclasses import replace
1213
from functools import lru_cache
1314
from pathlib import Path
1415
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
1617

1718
from griffe import (
1819
Alias,
@@ -28,11 +29,11 @@
2829
)
2930
from jinja2 import TemplateNotFound, pass_context, pass_environment
3031
from markupsafe import Markup
31-
from mkdocs_autorefs import AutorefsHookInterface
32+
from mkdocs_autorefs import AutorefsHookInterface, Backlink, BacklinkCrumb
3233
from mkdocstrings import get_logger
3334

3435
if TYPE_CHECKING:
35-
from collections.abc import Iterator, Sequence
36+
from collections.abc import Iterable, Iterator, Sequence
3637

3738
from griffe import Attribute, Class, Function, Module
3839
from jinja2 import Environment, Template
@@ -210,10 +211,15 @@ def do_format_attribute(
210211

211212
signature = str(attribute_path).strip()
212213
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+
)
214220
signature += f": {annotation}"
215221
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")
217223
signature += f" = {value}"
218224

219225
signature = do_format_code(signature, line_length)
@@ -725,3 +731,45 @@ def get_context(self) -> AutorefsHookInterface.Context:
725731
filepath=str(filepath),
726732
lineno=lineno,
727733
)
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))

src/mkdocstrings_handlers/python/templates/material/_base/attribute.html.jinja

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ Context:
113113
{% include "docstring"|get_template with context %}
114114
{% endwith %}
115115
{% endblock docstring %}
116+
117+
{% if config.backlinks %}
118+
<backlinks identifier="{{ html_id }}" handler="python" />
119+
{% endif %}
116120
{% endblock contents %}
117121
</div>
118122

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{#- Template for backlinks.
2+
3+
This template renders backlinks.
4+
5+
Context:
6+
backlinks (Mapping[str, Iterable[str]]): The backlinks to render.
7+
config (dict): The configuration options.
8+
verbose_type (Mapping[str, str]): The verbose backlink types.
9+
default_crumb (BacklinkCrumb): A default, empty crumb.
10+
-#}
11+
12+
{% block logs scoped %}
13+
{#- Logging block.
14+
15+
This block can be used to log debug messages, deprecation messages, warnings, etc.
16+
-#}
17+
{% endblock logs %}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy