diff options
author | Andreas Finkler <3929834+DudeNr33@users.noreply.github.com> | 2021-08-03 09:57:22 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-03 09:57:22 +0200 |
commit | 7bb50436d4bd440c074d1c0d8f9564743b2b56ab (patch) | |
tree | 66c96d9bcb331a0071ae305d13cf082ab1faa13a /pylint/pyreverse/vcg_printer.py | |
parent | 9ae559d202814091896cdd3b95068a12fa1bd94b (diff) | |
download | pylint-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.py | 283 |
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] |