summaryrefslogtreecommitdiff
path: root/pylint/pyreverse/vcg_printer.py
diff options
context:
space:
mode:
authorAndreas Finkler <3929834+DudeNr33@users.noreply.github.com>2021-08-03 09:57:22 +0200
committerGitHub <noreply@github.com>2021-08-03 09:57:22 +0200
commit7bb50436d4bd440c074d1c0d8f9564743b2b56ab (patch)
tree66c96d9bcb331a0071ae305d13cf082ab1faa13a /pylint/pyreverse/vcg_printer.py
parent9ae559d202814091896cdd3b95068a12fa1bd94b (diff)
downloadpylint-git-7bb50436d4bd440c074d1c0d8f9564743b2b56ab.tar.gz
Create common ``Printer`` base class for ``pyreverse`` and improve typing. (#4731)
* Create common ``Printer`` base class for Pyreverse, and improve typing. * Use ``abc.ABC`` as metaclass for ``Printer`` instead of raising ``NotImplementedError`` * Rename ``vcgutils.py`` to ``vcg_printer.py``
Diffstat (limited to 'pylint/pyreverse/vcg_printer.py')
-rw-r--r--pylint/pyreverse/vcg_printer.py283
1 files changed, 283 insertions, 0 deletions
diff --git a/pylint/pyreverse/vcg_printer.py b/pylint/pyreverse/vcg_printer.py
new file mode 100644
index 000000000..86df2f6c0
--- /dev/null
+++ b/pylint/pyreverse/vcg_printer.py
@@ -0,0 +1,283 @@
+# Copyright (c) 2015-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
+# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
+# Copyright (c) 2018 ssolanki <sushobhitsolanki@gmail.com>
+# Copyright (c) 2020-2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
+# Copyright (c) 2020 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) 2020 Ram Rachum <ram@rachum.com>
+# Copyright (c) 2020 谭九鼎 <109224573@qq.com>
+# Copyright (c) 2020 Anthony Sottile <asottile@umich.edu>
+# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
+# Copyright (c) 2021 Andreas Finkler <andi.finkler@gmail.com>
+
+# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
+
+"""Functions to generate files readable with Georg Sander's vcg
+(Visualization of Compiler Graphs).
+You can download vcg at https://rw4.cs.uni-sb.de/~sander/html/gshome.html
+Note that vcg exists as a debian package.
+See vcg's documentation for explanation about the different values that
+maybe used for the functions parameters.
+"""
+from typing import Any, Dict, Mapping, Optional
+
+from pylint.pyreverse.printer import EdgeType, Layout, NodeProperties, NodeType, Printer
+
+ATTRS_VAL = {
+ "algos": (
+ "dfs",
+ "tree",
+ "minbackward",
+ "left_to_right",
+ "right_to_left",
+ "top_to_bottom",
+ "bottom_to_top",
+ "maxdepth",
+ "maxdepthslow",
+ "mindepth",
+ "mindepthslow",
+ "mindegree",
+ "minindegree",
+ "minoutdegree",
+ "maxdegree",
+ "maxindegree",
+ "maxoutdegree",
+ ),
+ "booleans": ("yes", "no"),
+ "colors": (
+ "black",
+ "white",
+ "blue",
+ "red",
+ "green",
+ "yellow",
+ "magenta",
+ "lightgrey",
+ "cyan",
+ "darkgrey",
+ "darkblue",
+ "darkred",
+ "darkgreen",
+ "darkyellow",
+ "darkmagenta",
+ "darkcyan",
+ "gold",
+ "lightblue",
+ "lightred",
+ "lightgreen",
+ "lightyellow",
+ "lightmagenta",
+ "lightcyan",
+ "lilac",
+ "turquoise",
+ "aquamarine",
+ "khaki",
+ "purple",
+ "yellowgreen",
+ "pink",
+ "orange",
+ "orchid",
+ ),
+ "shapes": ("box", "ellipse", "rhomb", "triangle"),
+ "textmodes": ("center", "left_justify", "right_justify"),
+ "arrowstyles": ("solid", "line", "none"),
+ "linestyles": ("continuous", "dashed", "dotted", "invisible"),
+}
+
+# meaning of possible values:
+# O -> string
+# 1 -> int
+# list -> value in list
+GRAPH_ATTRS = {
+ "title": 0,
+ "label": 0,
+ "color": ATTRS_VAL["colors"],
+ "textcolor": ATTRS_VAL["colors"],
+ "bordercolor": ATTRS_VAL["colors"],
+ "width": 1,
+ "height": 1,
+ "borderwidth": 1,
+ "textmode": ATTRS_VAL["textmodes"],
+ "shape": ATTRS_VAL["shapes"],
+ "shrink": 1,
+ "stretch": 1,
+ "orientation": ATTRS_VAL["algos"],
+ "vertical_order": 1,
+ "horizontal_order": 1,
+ "xspace": 1,
+ "yspace": 1,
+ "layoutalgorithm": ATTRS_VAL["algos"],
+ "late_edge_labels": ATTRS_VAL["booleans"],
+ "display_edge_labels": ATTRS_VAL["booleans"],
+ "dirty_edge_labels": ATTRS_VAL["booleans"],
+ "finetuning": ATTRS_VAL["booleans"],
+ "manhattan_edges": ATTRS_VAL["booleans"],
+ "smanhattan_edges": ATTRS_VAL["booleans"],
+ "port_sharing": ATTRS_VAL["booleans"],
+ "edges": ATTRS_VAL["booleans"],
+ "nodes": ATTRS_VAL["booleans"],
+ "splines": ATTRS_VAL["booleans"],
+}
+NODE_ATTRS = {
+ "title": 0,
+ "label": 0,
+ "color": ATTRS_VAL["colors"],
+ "textcolor": ATTRS_VAL["colors"],
+ "bordercolor": ATTRS_VAL["colors"],
+ "width": 1,
+ "height": 1,
+ "borderwidth": 1,
+ "textmode": ATTRS_VAL["textmodes"],
+ "shape": ATTRS_VAL["shapes"],
+ "shrink": 1,
+ "stretch": 1,
+ "vertical_order": 1,
+ "horizontal_order": 1,
+}
+EDGE_ATTRS = {
+ "sourcename": 0,
+ "targetname": 0,
+ "label": 0,
+ "linestyle": ATTRS_VAL["linestyles"],
+ "class": 1,
+ "thickness": 0,
+ "color": ATTRS_VAL["colors"],
+ "textcolor": ATTRS_VAL["colors"],
+ "arrowcolor": ATTRS_VAL["colors"],
+ "backarrowcolor": ATTRS_VAL["colors"],
+ "arrowsize": 1,
+ "backarrowsize": 1,
+ "arrowstyle": ATTRS_VAL["arrowstyles"],
+ "backarrowstyle": ATTRS_VAL["arrowstyles"],
+ "textmode": ATTRS_VAL["textmodes"],
+ "priority": 1,
+ "anchor": 1,
+ "horizontal_order": 1,
+}
+SHAPES: Dict[NodeType, str] = {
+ NodeType.PACKAGE: "box",
+ NodeType.CLASS: "box",
+ NodeType.INTERFACE: "ellipse",
+}
+ARROWS: Dict[EdgeType, Dict] = {
+ EdgeType.USES: dict(arrowstyle="solid", backarrowstyle="none", backarrowsize=0),
+ EdgeType.INHERITS: dict(
+ arrowstyle="solid", backarrowstyle="none", backarrowsize=10
+ ),
+ EdgeType.IMPLEMENTS: dict(
+ arrowstyle="solid",
+ backarrowstyle="none",
+ linestyle="dotted",
+ backarrowsize=10,
+ ),
+ EdgeType.ASSOCIATION: dict(
+ arrowstyle="solid", backarrowstyle="none", textcolor="green"
+ ),
+}
+ORIENTATION: Dict[Layout, str] = {
+ Layout.LEFT_TO_RIGHT: "left_to_right",
+ Layout.RIGHT_TO_LEFT: "right_to_left",
+ Layout.TOP_TO_BOTTOM: "top_to_bottom",
+ Layout.BOTTOM_TO_TOP: "bottom_to_top",
+}
+
+# Misc utilities ###############################################################
+
+
+class VCGPrinter(Printer):
+ def __init__(
+ self,
+ title: str,
+ layout: Optional[Layout] = None,
+ use_automatic_namespace: Optional[bool] = None,
+ ):
+ self._indent = ""
+ super().__init__(title, layout, use_automatic_namespace)
+
+ def _open_graph(self) -> None:
+ """Emit the header lines"""
+ self.emit(f"{self._indent}graph:{{\n")
+ self._inc_indent()
+ self._write_attributes(
+ GRAPH_ATTRS,
+ title=self.title,
+ layoutalgorithm="dfs",
+ late_edge_labels="yes",
+ port_sharing="no",
+ manhattan_edges="yes",
+ )
+ if self.layout:
+ self._write_attributes(GRAPH_ATTRS, orientation=ORIENTATION[self.layout])
+
+ def _close_graph(self) -> None:
+ """Emit the lines needed to properly close the graph."""
+ self._dec_indent()
+ self.emit(f"{self._indent}}}")
+
+ def emit_node(
+ self,
+ name: str,
+ type_: NodeType,
+ properties: Optional[NodeProperties] = None,
+ ) -> None:
+ """Create a new node. Nodes can be classes, packages, participants etc."""
+ if properties is None:
+ properties = NodeProperties(label=name)
+ self.emit(f'{self._indent}node: {{title:"{name}"', force_newline=False)
+ label = properties.label if properties.label is not None else name
+ self._write_attributes(
+ NODE_ATTRS,
+ label=label,
+ shape=SHAPES[type_],
+ )
+ self.emit("}")
+
+ def emit_edge(
+ self,
+ from_node: str,
+ to_node: str,
+ type_: EdgeType,
+ label: Optional[str] = None,
+ ) -> None:
+ """Create an edge from one node to another to display relationships."""
+ self.emit(
+ f'{self._indent}edge: {{sourcename:"{from_node}" targetname:"{to_node}"',
+ force_newline=False,
+ )
+ attributes = ARROWS[type_]
+ if label:
+ attributes["label"] = label
+ self._write_attributes(
+ EDGE_ATTRS,
+ **attributes,
+ )
+ self.emit("}")
+
+ def _write_attributes(self, attributes_dict: Mapping[str, Any], **args) -> None:
+ """write graph, node or edge attributes"""
+ for key, value in args.items():
+ try:
+ _type = attributes_dict[key]
+ except KeyError as e:
+ raise Exception(
+ f"no such attribute {key}\npossible attributes are {attributes_dict.keys()}"
+ ) from e
+
+ if not _type:
+ self.emit(f'{self._indent}{key}:"{value}"\n')
+ elif _type == 1:
+ self.emit(f"{self._indent}{key}:{int(value)}\n")
+ elif value in _type:
+ self.emit(f"{self._indent}{key}:{value}\n")
+ else:
+ raise Exception(
+ f"value {value} isn't correct for attribute {key} correct values are {type}"
+ )
+
+ def _inc_indent(self):
+ """increment indentation"""
+ self._indent += " "
+
+ def _dec_indent(self):
+ """decrement indentation"""
+ self._indent = self._indent[:-2]