#
# This file is distributed under the MIT License. See LICENSE.md for details.
#
from dataclasses import dataclass, field
from typing import List, Set, TypeAlias
def escape(string: str) -> str:
return string.replace("\\", "\\\\").replace('"', '\\"')
@dataclass(slots=True)
class Node:
label: str
color: str = ""
bgcolor: str = ""
entries: List[str] = field(default_factory=list)
def name(self) -> str:
return f"node_{id(self)}"
def to_graphviz(self) -> str:
result = ""
result += f""" {escape(self.name())} ["""
result += "label=<\n"
result += """
\n"""
cell_properties = ""
if self.bgcolor:
cell_properties = f' bgcolor="{self.bgcolor}"'
result += f""" | {escape(self.label)} |
\n"""
for index, entry in enumerate(self.entries):
result += f""" | {escape(entry)} |
\n"""
result += "
\n"
result += " >"
if self.color:
result += f', color="{self.color}"'
result += "];\n\n"
return result
def __hash__(self) -> int:
return hash(self.name())
@dataclass(frozen=True, slots=True)
class Edge:
source: Node
destination: Node
source_port: int = 0
destination_port: int = 0
head_label: str = ""
tail_label: str = ""
style: str = ""
color: str = ""
def to_graphviz(self) -> str:
result = ""
result += " "
if self.source_port >= 0:
result += f"{escape(self.source.name())}:entry_{self.source_port}"
else:
result += f"{escape(self.source.name())}"
result += " -> "
if self.destination_port >= 0:
result += f"{escape(self.destination.name())}:entry_{self.destination_port}"
else:
result += f"{escape(self.destination.name())}"
attributes = []
if self.head_label:
attributes.append(f'headlabel="{escape(self.head_label)}"')
if self.tail_label:
attributes.append(f'taillabel="{escape(self.tail_label)}"')
if self.style:
attributes.append(f'style="{escape(self.style)}"')
if self.color:
attributes.append(f'color="{escape(self.color)}"')
if len(attributes) > 0:
result += f" [{','.join(attributes)}]"
result += ";\n"
return result
class Graph:
"""A graph data structure for rendering purposes."""
Node: TypeAlias = Node
Edge: TypeAlias = Edge
def __init__(self) -> None:
self.nodes: Set[Node] = set()
self.edges: Set[Edge] = set()
def is_tree(self) -> bool:
# TODO: implement
return True
def to_graphviz(self) -> str:
result = "digraph structs {\n"
result += "rankdir = LR;\n"
result += "node [shape=plaintext];\n"
result += "\n\n"
for node in self.nodes:
result += node.to_graphviz()
for edge in self.edges:
result += edge.to_graphviz()
result += "\n}\n"
return result