Skip to content

Commit

Permalink
feat: Support backlinks
Browse files Browse the repository at this point in the history
Issue-153: #153
  • Loading branch information
pawamoy committed Feb 28, 2025
1 parent cfa9848 commit 5ab3405
Show file tree
Hide file tree
Showing 25 changed files with 234 additions and 56 deletions.
14 changes: 14 additions & 0 deletions src/mkdocstrings_handlers/python/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import os
import posixpath
import sys
from collections.abc import Iterable
from contextlib import suppress
from dataclasses import asdict
from pathlib import Path
Expand All @@ -25,6 +26,7 @@
from mkdocstrings.handlers.base import BaseHandler, CollectionError, CollectorItem, HandlerOptions
from mkdocstrings.inventory import Inventory
from mkdocstrings.loggers import get_logger
from mkdocs_autorefs.plugin import BacklinkCrumb

from mkdocstrings_handlers.python import rendering
from mkdocstrings_handlers.python.config import PythonConfig, PythonOptions
Expand All @@ -33,6 +35,7 @@
from collections.abc import Iterator, Mapping, MutableMapping, Sequence

from mkdocs.config.defaults import MkDocsConfig
from mkdocs_autorefs.plugin import Backlink


if sys.version_info >= (3, 11):
Expand Down Expand Up @@ -280,6 +283,16 @@ def render(self, data: CollectorItem, options: PythonOptions) -> str: # noqa: D
},
)

def render_backlinks(self, backlinks: Mapping[str, Iterable[Backlink]]) -> str: # noqa: D102 (ignore missing docstring)
template = self.env.get_template("backlinks.html.jinja")
verbose_type = {key: key.capitalize().replace("-by", " by") for key in backlinks.keys()}
return template.render(
backlinks=backlinks,
config=self.get_options({}),
verbose_type=verbose_type,
default_crumb=BacklinkCrumb(title="", url=""),
)

def update_env(self, config: Any) -> None: # noqa: ARG002
"""Update the Jinja environment with custom filters and tests.
Expand All @@ -303,6 +316,7 @@ def update_env(self, config: Any) -> None: # noqa: ARG002
self.env.filters["as_functions_section"] = rendering.do_as_functions_section
self.env.filters["as_classes_section"] = rendering.do_as_classes_section
self.env.filters["as_modules_section"] = rendering.do_as_modules_section
self.env.filters["backlink_tree"] = rendering.do_backlink_tree
self.env.globals["AutorefsHook"] = rendering.AutorefsHook
self.env.tests["existing_template"] = lambda template_name: template_name in self.env.list_templates()

Expand Down
66 changes: 62 additions & 4 deletions src/mkdocstrings_handlers/python/rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

from collections.abc import Iterable
import random
import re
import string
Expand All @@ -12,7 +13,7 @@
from functools import lru_cache
from pathlib import Path
from re import Match, Pattern
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Literal
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Literal, TypeVar

from griffe import (
Alias,
Expand All @@ -26,9 +27,12 @@
DocstringSectionModules,
Object,
)
from collections import defaultdict
from typing import Optional, Sequence, Union

from jinja2 import TemplateNotFound, pass_context, pass_environment
from markupsafe import Markup
from mkdocs_autorefs import AutorefsHookInterface
from mkdocs_autorefs import AutorefsHookInterface, Backlink, BacklinkCrumb
from mkdocstrings.loggers import get_logger

if TYPE_CHECKING:
Expand Down Expand Up @@ -210,10 +214,15 @@ def do_format_attribute(

signature = str(attribute_path).strip()
if annotations and attribute.annotation:
annotation = template.render(context.parent, expression=attribute.annotation, signature=True)
annotation = template.render(
context.parent,
expression=attribute.annotation,
signature=True,
backlink_type="returned-by",
)
signature += f": {annotation}"
if attribute.value:
value = template.render(context.parent, expression=attribute.value, signature=True)
value = template.render(context.parent, expression=attribute.value, signature=True, backlink_type="used-by")
signature += f" = {value}"

signature = do_format_code(signature, line_length)
Expand Down Expand Up @@ -725,3 +734,52 @@ def get_context(self) -> AutorefsHookInterface.Context:
filepath=str(filepath),
lineno=lineno,
)


T = TypeVar("T")
Tree = dict[T, "Tree"]
CompactTree = dict[tuple[T, ...], "CompactTree"]
_rtree = lambda: defaultdict(_rtree)


def _tree(data: Iterable[tuple[T, ...]]) -> Tree:
new_tree = _rtree()
for nav in data:
*path, leaf = nav
node = new_tree
for key in path:
node = node[key]
node[leaf] = _rtree()
return new_tree


def print_tree(tree: Tree, level: int = 0) -> None:
for key, value in tree.items():
print(" " * level + str(key))
if value:
print_tree(value, level + 1)


def _compact_tree(tree: Tree) -> CompactTree:
new_tree = _rtree()
for key, value in tree.items():
child = _compact_tree(value)
if len(child) == 1:
child_key, child_value = next(iter(child.items()))
new_key = (key, *child_key)
new_tree[new_key] = child_value
else:
new_tree[(key,)] = child
return new_tree


def do_backlink_tree(backlinks: list[Backlink]) -> CompactTree[BacklinkCrumb]:
"""Build a tree of backlinks.
Parameters:
backlinks: The list of backlinks.
Returns:
A tree of backlinks.
"""
return _compact_tree(_tree((backlink.crumbs for backlink in backlinks)))
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ Context:
{% include "docstring"|get_template with context %}
{% endwith %}
{% endblock docstring %}

<backlinks identifier="{{ html_id }}" handler="python" />
{% endblock contents %}
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{#- Template for backlinks.
This template renders backlinks.
Context:
backlinks (Mapping[str, Iterable[str]]): The backlinks to render.
config (dict): The configuration options.
-#}

{% block logs scoped %}
{#- Logging block.
This block can be used to log debug messages, deprecation messages, warnings, etc.
-#}
{{ log.debug("Rendering backlinks") }}
{% endblock logs %}

{% macro render_crumb(crumb) %}
<span class="doc doc-backlink-crumb">
{% if crumb.url and crumb.title %}
<a href="{{ crumb.url }}">{{ crumb.title | safe }}</a>
{% elif crumb.title %}
<span>{{ crumb.title | safe }}</span>
{% endif %}
</span>
{% endmacro %}

{% macro render_tree(tree) %}
<ul class="doc doc-backlink-list">
{% for node, child in tree | dictsort %}
<li class="doc doc-backlink">
{% for crumb in node %}
{{ render_crumb(crumb) }}
{% endfor %}
{% if child %}
{{ render_tree(child) }}
{% endif %}
</li>
{% endfor %}
</ul>
{% endmacro %}

{% if config.backlinks %}
<div class="doc doc-backlinks">
{% if config.backlinks == "tree" %}
{% for backlink_type, backlink_list in backlinks | dictsort %}
<b class="doc doc-backlink-type">{{ verbose_type[backlink_type] }}:</b>
{{ render_tree(backlink_list|backlink_tree) }}
{% endfor %}
{% elif config.backlinks == "flat" %}
{% for backlink_type, backlink_list in backlinks | dictsort %}
<b class="doc doc-backlink-type">{{ verbose_type[backlink_type] }}:</b>
<ul class="doc doc-backlink-list">
{% for backlink in backlink_list | sort(attribute="crumbs") %}
<li class="doc doc-backlink">
{% for crumb in backlink.crumbs %}
{{ render_crumb(crumb) }}
{% endfor %}
</li>
{% endfor %}
</ul>
{% endfor %}
{% endif %}
</div>
{% endif %}
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,11 @@ Context:
{% if config.show_bases and class.bases %}
<p class="doc doc-class-bases">
Bases: {% for expression in class.bases -%}
<code>{% include "expression"|get_template with context %}</code>{% if not loop.last %}, {% endif %}
<code>
{%- with backlink_type = "subclassed-by" -%}
{%- include "expression"|get_template with context -%}
{%- endwith -%}
</code>{% if not loop.last %}, {% endif %}
{% endfor -%}
</p>
{% endif %}
Expand Down Expand Up @@ -159,6 +163,8 @@ Context:
{% endif %}
{% endblock docstring %}

<backlinks identifier="{{ html_id }}" handler="python" />

{% block summary scoped %}
{#- Summary block.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Context:
<td><code>{{ parameter.name }}</code></td>
<td>
{% if parameter.annotation %}
{% with expression = parameter.annotation %}
{% with expression = parameter.annotation, backlink_type = "used-by" %}
<code>{% include "expression"|get_template with context %}</code>
{% endwith %}
{% endif %}
Expand All @@ -60,7 +60,7 @@ Context:
<li class="doc-section-item field-body">
<b><code>{{ parameter.name }}</code></b>
{% if parameter.annotation %}
{% with expression = parameter.annotation %}
{% with expression = parameter.annotation, backlink_type = "used-by" %}
(<code>{% include "expression"|get_template with context %}</code>)
{% endwith %}
{% endif %}
Expand Down Expand Up @@ -94,7 +94,7 @@ Context:
{% if parameter.annotation %}
<span class="doc-param-annotation">
<b>{{ lang.t("TYPE:") }}</b>
{% with expression = parameter.annotation %}
{% with expression = parameter.annotation, backlink_type = "used-by" %}
<code>{% include "expression"|get_template with context %}</code>
{% endwith %}
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Context:
</td>
<td>
{% if parameter.annotation %}
{% with expression = parameter.annotation %}
{% with expression = parameter.annotation, backlink_type = "used-by" %}
<code>{% include "expression"|get_template with context %}</code>
{% endwith %}
{% endif %}
Expand All @@ -63,7 +63,7 @@ Context:
</td>
<td>
{% if parameter.default %}
{% with expression = parameter.default %}
{% with expression = parameter.default, backlink_type = "used-by" %}
<code>{% include "expression"|get_template with context %}</code>
{% endwith %}
{% else %}
Expand Down Expand Up @@ -96,10 +96,10 @@ Context:
<b><code>{{ parameter.name }}</code></b>
{% endif %}
{% if parameter.annotation %}
{% with expression = parameter.annotation %}
{% with expression = parameter.annotation, backlink_type = "used-by" %}
(<code>{% include "expression"|get_template with context %}</code>
{%- if parameter.default %}, {{ lang.t("default:") }}
{% with expression = parameter.default %}
{% with expression = parameter.default, backlink_type = "used-by" %}
<code>{% include "expression"|get_template with context %}</code>
{% endwith %}
{% endif %})
Expand Down Expand Up @@ -149,15 +149,15 @@ Context:
{% if parameter.annotation %}
<span class="doc-param-annotation">
<b>{{ lang.t("TYPE:") }}</b>
{% with expression = parameter.annotation %}
{% with expression = parameter.annotation, backlink_type = "used-by" %}
<code>{% include "expression"|get_template with context %}</code>
{% endwith %}
</span>
{% endif %}
{% if parameter.default %}
<span class="doc-param-default">
<b>{{ lang.t("DEFAULT:") }}</b>
{% with expression = parameter.default %}
{% with expression = parameter.default, backlink_type = "used-by" %}
<code>{% include "expression"|get_template with context %}</code>
{% endwith %}
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Context:
<tr class="doc-section-item">
<td>
{% if raises.annotation %}
{% with expression = raises.annotation %}
{% with expression = raises.annotation, backlink_type = "raised-by" %}
<code>{% include "expression"|get_template with context %}</code>
{% endwith %}
{% endif %}
Expand All @@ -57,7 +57,7 @@ Context:
{% for raises in section.value %}
<li class="doc-section-item field-body">
{% if raises.annotation %}
{% with expression = raises.annotation %}
{% with expression = raises.annotation, backlink_type = "raised-by" %}
<code>{% include "expression"|get_template with context %}</code>
{% endwith %}
Expand All @@ -84,7 +84,7 @@ Context:
<tr class="doc-section-item">
<td>
<span class="doc-raises-annotation">
{% with expression = raises.annotation %}
{% with expression = raises.annotation, backlink_type = "raised-by" %}
<code>{% include "expression"|get_template with context %}</code>
{% endwith %}
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Context:
{% if name_column %}<td>{% if receives.name %}<code>{{ receives.name }}</code>{% endif %}</td>{% endif %}
<td>
{% if receives.annotation %}
{% with expression = receives.annotation %}
{% with expression = receives.annotation, backlink_type = "received-by" %}
<code>{% include "expression"|get_template with context %}</code>
{% endwith %}
{% endif %}
Expand All @@ -61,7 +61,7 @@ Context:
<li class="doc-section-item field-body">
{% if receives.name %}<b><code>{{ receives.name }}</code></b>{% endif %}
{% if receives.annotation %}
{% with expression = receives.annotation %}
{% with expression = receives.annotation, backlink_type = "received-by" %}
{% if receives.name %} ({% endif %}
<code>{% include "expression"|get_template with context %}</code>
{% if receives.name %}){% endif %}
Expand Down Expand Up @@ -93,7 +93,7 @@ Context:
<code>{{ receives.name }}</code>
{% elif receives.annotation %}
<span class="doc-receives-annotation">
{% with expression = receives.annotation %}
{% with expression = receives.annotation, backlink_type = "received-by" %}
<code>{% include "expression"|get_template with context %}</code>
{% endwith %}
</span>
Expand All @@ -107,7 +107,7 @@ Context:
<p>
<span class="doc-receives-annotation">
<b>{{ lang.t("TYPE:") }}</b>
{% with expression = receives.annotation %}
{% with expression = receives.annotation, backlink_type = "received-by" %}
<code>{% include "expression"|get_template with context %}</code>
{% endwith %}
</span>
Expand Down
Loading

0 comments on commit 5ab3405

Please sign in to comment.