Source code for sphinx_uml.pyreverse.dot_printer
"""
Based on the :py:mod:`pyreverse.dot_printer` module.
Under Debian, see the
``/usr/lib/python3/dist-packages/pylint/pyreverse/dot_printer.py``
file.
"""
# Inherited imports
from pylint.pyreverse.dot_printer import (
NodeProperties,
nodes,
get_annotation_label,
NodeType,
SHAPES
)
# Custom imports
from pylint.pyreverse.dot_printer import DotPrinter as _DotPrinter
from .sphinx_html_proxy import SphinxHtmlProxy
[docs]
class DotPrinter(_DotPrinter):
"""
Overloads the :py:class:`pylint.pyreverse.dot_printer.DotPrinter` class
to export an UML diagram using Graphviz and crafting links in the output
file to map each class/method/attribute name with the corresponding Sphinx
HTML page/anchor.
"""
def _open_graph(self) -> None:
super()._open_graph()
self.emit("bgcolor=transparent")
def _build_label_for_node( # noqa: C901 func too-complex
self,
properties: NodeProperties
) -> str:
if not properties.label:
return ""
# Attribute and method display
# HTML tags supported by Graphviz:
# https://graphviz.org/doc/info/shapes.html#html
# NB: A node can embed at most one <table> tag.
# Several links in a single node: https://stackoverflow.com/a/48029398
def vstack(**kwargs: dict) -> str:
text = kwargs.pop("text", None)
if not text:
return ""
if "tooltip" not in kwargs:
kwargs["tooltip"] = ""
if "href" in kwargs and kwargs["href"] is None:
kwargs.pop("href")
attrs = " ".join([
f'{k}="{v}"'
for k, v in kwargs.items()
])
return f"\n\t<tr><td {attrs}>{text}</td></tr>"
def escape_string(s: str) -> str:
return s.replace('"', '\\"')
proxy = SphinxHtmlProxy()
class_name = properties.label
label = (
f'<table border="0" align="left" tooltip="{class_name}" '
'width="0" cellpadding="0">'
)
class_url = proxy.url()
# Class name
label += vstack(
border=1,
href=class_url,
tooltip=escape_string(class_name),
target="_top",
text=f"<b>{class_name}</b>"
)
# Only class names
if properties.attrs is None and properties.methods is None:
return label + "</table>"
attrs: list[str] = properties.attrs or []
methods: list[nodes.FunctionDef] = properties.methods or []
# Add class attributes
if attrs:
label += vstack(
align="left",
text="<b>Attributes:</b>"
)
for attr in attrs:
attr_url = proxy.url(attr)
attr_label = attr.replace("|", r"\|")
label += vstack(
align="left",
href=attr_url,
target="_top",
tooltip=escape_string(f"{class_name}.{attr_label}"),
text=attr_label
)
# Add class methods
if methods:
label += vstack(
align="left",
text="<b>Methods:</b>"
)
for func in methods:
args = (
", "
.join(self._get_method_arguments(func))
.replace("|", r"\|")
)
method_name = func.name
method_url = proxy.url(func.name)
prototype = rf"{method_name}({args})"
if func.returns:
annotation_label = get_annotation_label(func.returns)
prototype += (
": " + self._escape_annotation_label(annotation_label)
)
if func.is_abstract():
prototype = f"<i>{prototype}</i>"
label += vstack(
align="left",
href=method_url,
target="_top",
tooltip=escape_string(f"{class_name}.{func.name}"),
text=prototype
)
label += "</table>"
# >>
return label
[docs]
def emit_node(
self,
name: str,
type_: NodeType,
properties: NodeProperties | None = None,
) -> None:
proxy = SphinxHtmlProxy()
i = name.rfind(".")
if i > 0:
proxy.module_name = name[: i]
proxy.class_name = name[i + 1:]
else:
proxy.module_name = name
proxy.class_name = None
if properties is None:
properties = NodeProperties(label=name)
shape = SHAPES[type_]
color = (
properties.color if properties.color is not None
else self.DEFAULT_COLOR
)
style = "filled" if color != self.DEFAULT_COLOR else "solid"
label = self._build_label_for_node(properties)
label_part = f", label=<{label}>" if label else ""
fontcolor_part = (
f', fontcolor="{properties.fontcolor}"' if properties.fontcolor
else ""
)
# URLs in Graphviz: https://graphviz.org/docs/attrs/URL/
# https://talk.observablehq.com/t/hyperlink-in-graphviz-node-doesnt-work/4775/2
# Example:
# dot`digraph { b [URL="https://google.com" target="_top"]; a -> b; }`
url = proxy.url()
self.emit(
f'"{name}" [' + ", ".join([
f'color="{color}"{fontcolor_part}{label_part}',
f'shape="{shape}"',
f'style="{style}"',
f'URL="{url}"',
]) + "];"
)