From 53e99b450dc7560b0a9983b9b9b52e60b2c1cdf9 Mon Sep 17 00:00:00 2001 From: DudeNr33 <3929834+DudeNr33@users.noreply.github.com> Date: Mon, 24 May 2021 11:17:48 +0200 Subject: [PATCH 01/13] WIP: automatically color package diagrams in pyreverse --- pylint/pyreverse/writer.py | 46 ++++++++++++++++++++++++++++++++- tests/data/packages_No_Name.dot | 6 ++--- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py index 0d80bb9ca6..ea5ac02db9 100644 --- a/pylint/pyreverse/writer.py +++ b/pylint/pyreverse/writer.py @@ -16,6 +16,7 @@ """Utilities for creating VCG and Dot diagrams""" +import itertools import os from pylint.graph import DotBackend @@ -49,7 +50,13 @@ def write_packages(self, diagram): """write a package diagram""" # sorted to get predictable (hence testable) results for i, obj in enumerate(sorted(diagram.modules(), key=lambda x: x.title)): - self.printer.emit_node(i, label=self.get_title(obj), shape="box") + self.printer.emit_node( + i, + label=self.get_title(obj), + shape="box", + color=self.get_color(obj), + style="filled", + ) obj.fig_id = i # package dependencies for rel in diagram.get_relationships("depends"): @@ -90,6 +97,10 @@ def get_title(self, obj): """get project title""" raise NotImplementedError + def get_color(self, obj): + """get shape color""" + raise NotImplementedError + def get_values(self, obj): """get label and shape for classes.""" raise NotImplementedError @@ -111,6 +122,28 @@ def __init__(self, config): fontcolor="green", arrowtail="none", arrowhead="diamond", style="solid" ), ] + self.available_colors = itertools.cycle( + [ + "aliceblue", + "antiquewhite", + "aquamarine", + "burlywood", + "cadetblue", + "chartreuse", + "chocolate", + "coral", + "cornflowerblue", + "cyan", + "darkgoldenrod", + "darkseagreen", + "dodgerblue", + "forestgreen", + "gold", + "hotpink", + "mediumspringgreen", + ] + ) + self.used_colors = {} DiagramWriter.__init__(self, config, styles) def set_printer(self, file_name, basename): @@ -123,6 +156,13 @@ def get_title(self, obj): """get project title""" return obj.title + def get_color(self, obj): + """get shape color""" + base_name = ".".join(obj.title.split(".", 2)[:2]) + if base_name not in self.used_colors: + self.used_colors[base_name] = next(self.available_colors) + return self.used_colors[base_name] + def get_values(self, obj): """get label and shape for classes. @@ -184,6 +224,10 @@ def get_title(self, obj): """get project title in vcg format""" return r"\fb%s\fn" % obj.title + def get_color(self, obj): + """get color for object""" + return None + def get_values(self, obj): """get label and shape for classes. diff --git a/tests/data/packages_No_Name.dot b/tests/data/packages_No_Name.dot index 1ceeb72427..e2aa60bfd7 100644 --- a/tests/data/packages_No_Name.dot +++ b/tests/data/packages_No_Name.dot @@ -1,8 +1,8 @@ digraph "packages_No_Name" { charset="utf-8" rankdir=BT -"0" [label="data", shape="box"]; -"1" [label="data.clientmodule_test", shape="box"]; -"2" [label="data.suppliermodule_test", shape="box"]; +"0" [color="aliceblue", label="data", shape="box", style="filled"]; +"1" [color="antiquewhite", label="data.clientmodule_test", shape="box", style="filled"]; +"2" [color="aquamarine", label="data.suppliermodule_test", shape="box", style="filled"]; "1" -> "2" [arrowhead="open", arrowtail="none"]; } From 832202b4f10b1b2d93ff261d9958b805ef583c59 Mon Sep 17 00:00:00 2001 From: DudeNr33 <3929834+DudeNr33@users.noreply.github.com> Date: Mon, 24 May 2021 18:41:35 +0200 Subject: [PATCH 02/13] Add command line options ``--colorized`` and ``-max-color-depth``. Distinguish modules and packages. --- pylint/pyreverse/main.py | 20 ++++++++++++++++++++ pylint/pyreverse/writer.py | 25 +++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index a62a1c2e7f..9ada811bd6 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -138,6 +138,26 @@ help="create a *. output file if format available.", ), ), + ( + "colorized", + dict( + dest="colorized", + action="store_true", + default=False, + help="Use colored output. Classes/modules of the same package get the same color.", + ), + ), + ( + "max-color-depth", + dict( + dest="max_color_depth", + action="store", + default=2, + metavar="", + type="int", + help="Use separate colors up to package depth of ", + ), + ), ( "ignore", { diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py index ea5ac02db9..61e4efa437 100644 --- a/pylint/pyreverse/writer.py +++ b/pylint/pyreverse/writer.py @@ -55,7 +55,7 @@ def write_packages(self, diagram): label=self.get_title(obj), shape="box", color=self.get_color(obj), - style="filled", + style=self.get_style(), ) obj.fig_id = i # package dependencies @@ -97,6 +97,10 @@ def get_title(self, obj): """get project title""" raise NotImplementedError + def get_style(self): + """get style""" + raise NotImplementedError + def get_color(self, obj): """get shape color""" raise NotImplementedError @@ -156,9 +160,22 @@ def get_title(self, obj): """get project title""" return obj.title + def get_style(self): + """get style of object""" + if not self.config.colorized: + return "solid" + return "filled" + def get_color(self, obj): """get shape color""" - base_name = ".".join(obj.title.split(".", 2)[:2]) + if not self.config.colorized: + return "black" + depth = self.config.max_color_depth + if obj.node.package: + package = obj.title + else: + package = obj.title.rsplit(".", maxsplit=1)[0] + base_name = ".".join(package.split(".", depth)[:depth]) if base_name not in self.used_colors: self.used_colors[base_name] = next(self.available_colors) return self.used_colors[base_name] @@ -224,6 +241,10 @@ def get_title(self, obj): """get project title in vcg format""" return r"\fb%s\fn" % obj.title + def get_style(self): + """get style of object""" + return None + def get_color(self, obj): """get color for object""" return None From 7010985da7ab61220dddfe2c07a48becabeb175f Mon Sep 17 00:00:00 2001 From: DudeNr33 <3929834+DudeNr33@users.noreply.github.com> Date: Tue, 25 May 2021 18:21:09 +0200 Subject: [PATCH 03/13] Dummy implementation of new methods for VCG writer; ignore style attribute in VCGPrinter --- pylint/pyreverse/vcgutils.py | 7 +++++++ pylint/pyreverse/writer.py | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pylint/pyreverse/vcgutils.py b/pylint/pyreverse/vcgutils.py index 38193b3254..8f6810998a 100644 --- a/pylint/pyreverse/vcgutils.py +++ b/pylint/pyreverse/vcgutils.py @@ -81,6 +81,11 @@ "linestyles": ("continuous", "dashed", "dotted", "invisible"), } +# attributes that may be passed into the function but are not relevant vor the VCGPrinter (e.g. attributes only relevant for DOT files) +IGNORED_ATTRS = { + "style", +} + # meaning of possible values: # O -> string # 1 -> int @@ -194,6 +199,8 @@ def edge(self, from_node, to_node, edge_type="", **args): def _write_attributes(self, attributes_dict, **args): """write graph, node or edge attributes""" for key, value in args.items(): + if key in IGNORED_ATTRS: + continue try: _type = attributes_dict[key] except KeyError as e: diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py index 61e4efa437..e04f1880cb 100644 --- a/pylint/pyreverse/writer.py +++ b/pylint/pyreverse/writer.py @@ -174,7 +174,7 @@ def get_color(self, obj): if obj.node.package: package = obj.title else: - package = obj.title.rsplit(".", maxsplit=1)[0] + package, _ = obj.title.rsplit(".", maxsplit=1) base_name = ".".join(package.split(".", depth)[:depth]) if base_name not in self.used_colors: self.used_colors[base_name] = next(self.available_colors) @@ -247,7 +247,7 @@ def get_style(self): def get_color(self, obj): """get color for object""" - return None + return "black" def get_values(self, obj): """get label and shape for classes. From 72e2d01a5ab573c000d23ca83b3baf8ccb7e75bb Mon Sep 17 00:00:00 2001 From: DudeNr33 <3929834+DudeNr33@users.noreply.github.com> Date: Tue, 25 May 2021 18:23:04 +0200 Subject: [PATCH 04/13] Add simple unit test for colored output --- tests/data/packages_No_Name.dot | 6 +-- tests/unittest_pyreverse_writer.py | 76 +++++++++++++++++++++--------- 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/tests/data/packages_No_Name.dot b/tests/data/packages_No_Name.dot index e2aa60bfd7..64d5725f58 100644 --- a/tests/data/packages_No_Name.dot +++ b/tests/data/packages_No_Name.dot @@ -1,8 +1,8 @@ digraph "packages_No_Name" { charset="utf-8" rankdir=BT -"0" [color="aliceblue", label="data", shape="box", style="filled"]; -"1" [color="antiquewhite", label="data.clientmodule_test", shape="box", style="filled"]; -"2" [color="aquamarine", label="data.suppliermodule_test", shape="box", style="filled"]; +"0" [color="black", label="data", shape="box", style="solid"]; +"1" [color="black", label="data.clientmodule_test", shape="box", style="solid"]; +"2" [color="black", label="data.suppliermodule_test", shape="box", style="solid"]; "1" -> "2" [arrowhead="open", arrowtail="none"]; } diff --git a/tests/unittest_pyreverse_writer.py b/tests/unittest_pyreverse_writer.py index 98bb014881..abb99cb4e1 100644 --- a/tests/unittest_pyreverse_writer.py +++ b/tests/unittest_pyreverse_writer.py @@ -44,14 +44,34 @@ "show_builtin": False, "only_classnames": False, "output_directory": "", + "colorized": False, + "max_color_depth": 2, +} + +_COLORIZED = { + "all_ancestors": None, + "show_associated": None, + "module_names": None, + "output_format": "dot", + "diadefs_file": None, + "quiet": 0, + "show_ancestors": None, + "classes": (), + "all_associated": None, + "mode": "PUB_ONLY", + "show_builtin": False, + "only_classnames": False, + "output_directory": "", + "colorized": True, + "max_color_depth": 2, } class Config: """config object for tests""" - def __init__(self): - for attr, value in _DEFAULTS.items(): + def __init__(self, config): + for attr, value in config.items(): setattr(self, attr, value) @@ -79,41 +99,51 @@ def _astroid_wrapper(func, modname): DOT_FILES = ["packages_No_Name.dot", "classes_No_Name.dot"] +COLORIZED_DOT_FILES = ["packages_colorized.dot", "classes_colorized.dot"] -@pytest.fixture(scope="module") -def setup(): - project = get_project(os.path.join(os.path.dirname(__file__), "data")) +def _create_files(config, name="No Name"): + project = get_project(os.path.join(os.path.dirname(__file__), "data"), name) linker = Linker(project) - CONFIG = Config() - handler = DiadefsHandler(CONFIG) + handler = DiadefsHandler(config) dd = DefaultDiadefGenerator(linker, handler).visit(project) for diagram in dd: diagram.extract_relationships() - writer = DotWriter(CONFIG) + writer = DotWriter(config) writer.write(dd) + + +@pytest.fixture(scope="module") +def cleanup(): yield - for fname in DOT_FILES: + for fname in DOT_FILES + COLORIZED_DOT_FILES: try: os.remove(fname) except FileNotFoundError: continue -@pytest.mark.usefixtures("setup") -@pytest.mark.parametrize("generated_file", DOT_FILES) -def test_dot_files(generated_file): - expected_file = os.path.join(os.path.dirname(__file__), "data", generated_file) - generated = _file_lines(generated_file) - expected = _file_lines(expected_file) - generated = "\n".join(generated) - expected = "\n".join(expected) - files = f"\n *** expected : {expected_file}, generated : {generated_file} \n" - diff = "\n".join( - line for line in unified_diff(expected.splitlines(), generated.splitlines()) - ) - assert expected == generated, f"{files}{diff}" - os.remove(generated_file) +@pytest.mark.usefixtures("cleanup") +@pytest.mark.parametrize( + "config, name, generated_files", + [ + (Config(_DEFAULTS), "No Name", DOT_FILES), + (Config(_COLORIZED), "colorized", COLORIZED_DOT_FILES), + ], +) +def test_dot_files(config, name, generated_files): + _create_files(config, name) + for generated_file in generated_files: + expected_file = os.path.join(os.path.dirname(__file__), "data", generated_file) + generated = _file_lines(generated_file) + expected = _file_lines(expected_file) + generated = "\n".join(generated) + expected = "\n".join(expected) + files = f"\n *** expected : {expected_file}, generated : {generated_file} \n" + diff = "\n".join( + line for line in unified_diff(expected.splitlines(), generated.splitlines()) + ) + assert expected == generated, f"{files}{diff}" @pytest.mark.parametrize( From 71d6d2974947ab383ba49a31b36fd2dfbaeb33cf Mon Sep 17 00:00:00 2001 From: DudeNr33 <3929834+DudeNr33@users.noreply.github.com> Date: Tue, 25 May 2021 18:23:51 +0200 Subject: [PATCH 05/13] Add test files for colorized output --- tests/data/classes_colorized.dot | 12 ++++++++++++ tests/data/packages_colorized.dot | 8 ++++++++ 2 files changed, 20 insertions(+) create mode 100644 tests/data/classes_colorized.dot create mode 100644 tests/data/packages_colorized.dot diff --git a/tests/data/classes_colorized.dot b/tests/data/classes_colorized.dot new file mode 100644 index 0000000000..2509f1132c --- /dev/null +++ b/tests/data/classes_colorized.dot @@ -0,0 +1,12 @@ +digraph "classes_colorized" { +charset="utf-8" +rankdir=BT +"0" [label="{Ancestor|attr : str\lcls_member\l|get_value()\lset_value(value)\l}", shape="record"]; +"1" [label="{DoNothing|\l|}", shape="record"]; +"2" [label="{Interface|\l|get_value()\lset_value(value)\l}", shape="record"]; +"3" [label="{Specialization|TYPE : str\lrelation\ltop : str\l|}", shape="record"]; +"3" -> "0" [arrowhead="empty", arrowtail="none"]; +"0" -> "2" [arrowhead="empty", arrowtail="node", style="dashed"]; +"1" -> "0" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"]; +"1" -> "3" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"]; +} diff --git a/tests/data/packages_colorized.dot b/tests/data/packages_colorized.dot new file mode 100644 index 0000000000..82fcb2a582 --- /dev/null +++ b/tests/data/packages_colorized.dot @@ -0,0 +1,8 @@ +digraph "packages_colorized" { +charset="utf-8" +rankdir=BT +"0" [color="aliceblue", label="data", shape="box", style="filled"]; +"1" [color="aliceblue", label="data.clientmodule_test", shape="box", style="filled"]; +"2" [color="aliceblue", label="data.suppliermodule_test", shape="box", style="filled"]; +"1" -> "2" [arrowhead="open", arrowtail="none"]; +} From bc0febf5fdbc65718ee90df7460d600d1c496c07 Mon Sep 17 00:00:00 2001 From: DudeNr33 <3929834+DudeNr33@users.noreply.github.com> Date: Wed, 26 May 2021 19:42:46 +0200 Subject: [PATCH 06/13] Apply consistent definition of dictionaries --- pylint/pyreverse/main.py | 49 ++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index 9ada811bd6..04b3b93695 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -10,6 +10,7 @@ # Copyright (c) 2020 Peter Kolbus # Copyright (c) 2020 hippo91 # Copyright (c) 2021 Mark Byrne +# Copyright (c) 2021 Andreas Finkler # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/master/LICENSE @@ -123,8 +124,7 @@ short="k", action="store_true", default=False, - help="don't show attributes and methods in the class boxes; \ -this disables -f values", + help="don't show attributes and methods in the class boxes; this disables -f values", ), ), ( @@ -160,35 +160,34 @@ ), ( "ignore", - { - "type": "csv", - "metavar": "", - "dest": "ignore_list", - "default": ("CVS",), - "help": "Files or directories to be skipped. They " - "should be base names, not paths.", - }, + dict( + type="csv", + metavar="", + dest="ignore_list", + default=("CVS",), + help="Files or directories to be skipped. They should be base names, not paths.", + ), ), ( "project", - { - "default": "", - "type": "string", - "short": "p", - "metavar": "", - "help": "set the project name.", - }, + dict( + default="", + type="string", + short="p", + metavar="", + help="set the project name.", + ), ), ( "output-directory", - { - "default": "", - "type": "string", - "short": "d", - "action": "store", - "metavar": "", - "help": "set the output directory path.", - }, + dict( + default="", + type="string", + short="d", + action="store", + metavar="", + help="set the output directory path.", + ), ), ) From 128ea443e938b20b7e77118ed549e5f92659e401 Mon Sep 17 00:00:00 2001 From: DudeNr33 <3929834+DudeNr33@users.noreply.github.com> Date: Wed, 26 May 2021 19:43:42 +0200 Subject: [PATCH 07/13] Fix unittest that also relied on the Config class of another test module --- tests/unittest_pyreverse_diadefs.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unittest_pyreverse_diadefs.py b/tests/unittest_pyreverse_diadefs.py index e90db22512..047060af38 100644 --- a/tests/unittest_pyreverse_diadefs.py +++ b/tests/unittest_pyreverse_diadefs.py @@ -21,7 +21,7 @@ import astroid import pytest -from unittest_pyreverse_writer import Config, get_project +from unittest_pyreverse_writer import _DEFAULTS, Config, get_project from pylint.pyreverse.diadefslib import ( ClassDiadefGenerator, @@ -49,7 +49,7 @@ def _process_relations(relations): @pytest.fixture def HANDLER(): - return DiadefsHandler(Config()) + return DiadefsHandler(Config(_DEFAULTS)) @pytest.fixture(scope="module") @@ -60,7 +60,7 @@ def PROJECT(): def test_option_values(HANDLER, PROJECT): """test for ancestor, associated and module options""" df_h = DiaDefGenerator(Linker(PROJECT), HANDLER) - cl_config = Config() + cl_config = Config(_DEFAULTS) cl_config.classes = ["Specialization"] cl_h = DiaDefGenerator(Linker(PROJECT), DiadefsHandler(cl_config)) assert df_h._get_levels() == (0, 0) @@ -74,9 +74,9 @@ def test_option_values(HANDLER, PROJECT): hndl._set_default_options() assert hndl._get_levels() == (-1, -1) assert hndl.module_names - handler = DiadefsHandler(Config()) + handler = DiadefsHandler(Config(_DEFAULTS)) df_h = DiaDefGenerator(Linker(PROJECT), handler) - cl_config = Config() + cl_config = Config(_DEFAULTS) cl_config.classes = ["Specialization"] cl_h = DiaDefGenerator(Linker(PROJECT), DiadefsHandler(cl_config)) for hndl in [df_h, cl_h]: @@ -114,7 +114,7 @@ def test_functional_relation_extraction(self): # XXX should be catching pyreverse environnement problem but doesn't # pyreverse doesn't extracts the relations but this test ok project = get_project("data") - handler = DiadefsHandler(Config()) + handler = DiadefsHandler(Config(_DEFAULTS)) diadefs = handler.get_diadefs(project, Linker(project, tag=True)) cd = diadefs[1] relations = _process_relations(cd.relationships) From d2763be8650b2413c429697c8f40a51f616b139e Mon Sep 17 00:00:00 2001 From: DudeNr33 <3929834+DudeNr33@users.noreply.github.com> Date: Wed, 26 May 2021 19:44:12 +0200 Subject: [PATCH 08/13] Enable coloring of class diagrams without relying on the module-names=y option --- pylint/pyreverse/writer.py | 25 +++++++++++++++++++------ tests/data/classes_No_Name.dot | 8 ++++---- tests/data/classes_colorized.dot | 8 ++++---- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py index e04f1880cb..48b845dc4e 100644 --- a/pylint/pyreverse/writer.py +++ b/pylint/pyreverse/writer.py @@ -19,6 +19,9 @@ import itertools import os +import astroid +from astroid import modutils + from pylint.graph import DotBackend from pylint.pyreverse.utils import is_exception from pylint.pyreverse.vcgutils import VCGPrinter @@ -170,11 +173,16 @@ def get_color(self, obj): """get shape color""" if not self.config.colorized: return "black" + qualified_name = obj.node.qname() + if modutils.is_standard_module(qualified_name.split(".", maxsplit=1)[0]): + return "grey" depth = self.config.max_color_depth - if obj.node.package: - package = obj.title + if isinstance(obj.node, astroid.ClassDef): + package = qualified_name.rsplit(".", maxsplit=2)[0] + elif obj.node.package: + package = qualified_name else: - package, _ = obj.title.rsplit(".", maxsplit=1) + package = qualified_name.rsplit(".", maxsplit=1)[0] base_name = ".".join(package.split(".", depth)[:depth]) if base_name not in self.used_colors: self.used_colors[base_name] = next(self.available_colors) @@ -197,9 +205,14 @@ def get_values(self, obj): args = [] label = r"{}{}({})\l".format(label, func.name, ", ".join(args)) label = "{%s}" % label - if is_exception(obj.node): - return dict(fontcolor="red", label=label, shape="record") - return dict(label=label, shape="record") + values = dict( + label=label, + shape="record", + fontcolor="red" if is_exception(obj.node) else "black", + style=self.get_style(), + color=self.get_color(obj), + ) + return values def close_graph(self): """print the dot graph into """ diff --git a/tests/data/classes_No_Name.dot b/tests/data/classes_No_Name.dot index 8867b4e416..68f52e4b7b 100644 --- a/tests/data/classes_No_Name.dot +++ b/tests/data/classes_No_Name.dot @@ -1,10 +1,10 @@ digraph "classes_No_Name" { charset="utf-8" rankdir=BT -"0" [label="{Ancestor|attr : str\lcls_member\l|get_value()\lset_value(value)\l}", shape="record"]; -"1" [label="{DoNothing|\l|}", shape="record"]; -"2" [label="{Interface|\l|get_value()\lset_value(value)\l}", shape="record"]; -"3" [label="{Specialization|TYPE : str\lrelation\ltop : str\l|}", shape="record"]; +"0" [color="black", fontcolor="black", label="{Ancestor|attr : str\lcls_member\l|get_value()\lset_value(value)\l}", shape="record", style="solid"]; +"1" [color="black", fontcolor="black", label="{DoNothing|\l|}", shape="record", style="solid"]; +"2" [color="black", fontcolor="black", label="{Interface|\l|get_value()\lset_value(value)\l}", shape="record", style="solid"]; +"3" [color="black", fontcolor="black", label="{Specialization|TYPE : str\lrelation\ltop : str\l|}", shape="record", style="solid"]; "3" -> "0" [arrowhead="empty", arrowtail="none"]; "0" -> "2" [arrowhead="empty", arrowtail="node", style="dashed"]; "1" -> "0" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"]; diff --git a/tests/data/classes_colorized.dot b/tests/data/classes_colorized.dot index 2509f1132c..b815001b92 100644 --- a/tests/data/classes_colorized.dot +++ b/tests/data/classes_colorized.dot @@ -1,10 +1,10 @@ digraph "classes_colorized" { charset="utf-8" rankdir=BT -"0" [label="{Ancestor|attr : str\lcls_member\l|get_value()\lset_value(value)\l}", shape="record"]; -"1" [label="{DoNothing|\l|}", shape="record"]; -"2" [label="{Interface|\l|get_value()\lset_value(value)\l}", shape="record"]; -"3" [label="{Specialization|TYPE : str\lrelation\ltop : str\l|}", shape="record"]; +"0" [color="aliceblue", fontcolor="black", label="{Ancestor|attr : str\lcls_member\l|get_value()\lset_value(value)\l}", shape="record", style="filled"]; +"1" [color="aliceblue", fontcolor="black", label="{DoNothing|\l|}", shape="record", style="filled"]; +"2" [color="aliceblue", fontcolor="black", label="{Interface|\l|get_value()\lset_value(value)\l}", shape="record", style="filled"]; +"3" [color="aliceblue", fontcolor="black", label="{Specialization|TYPE : str\lrelation\ltop : str\l|}", shape="record", style="filled"]; "3" -> "0" [arrowhead="empty", arrowtail="none"]; "0" -> "2" [arrowhead="empty", arrowtail="node", style="dashed"]; "1" -> "0" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"]; From 738e7b92f4a42debc16f583025d721997bbfbe0a Mon Sep 17 00:00:00 2001 From: DudeNr33 <3929834+DudeNr33@users.noreply.github.com> Date: Wed, 26 May 2021 20:00:02 +0200 Subject: [PATCH 09/13] Modify test data and config so that colorization of standard library modules is checked --- tests/data/classes_No_Name.dot | 7 ++++--- tests/data/classes_colorized.dot | 29 ++++++++++++++++++++++------- tests/data/suppliermodule_test.py | 3 +++ tests/unittest_pyreverse_writer.py | 4 ++-- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/tests/data/classes_No_Name.dot b/tests/data/classes_No_Name.dot index 68f52e4b7b..f3ee953b13 100644 --- a/tests/data/classes_No_Name.dot +++ b/tests/data/classes_No_Name.dot @@ -4,9 +4,10 @@ rankdir=BT "0" [color="black", fontcolor="black", label="{Ancestor|attr : str\lcls_member\l|get_value()\lset_value(value)\l}", shape="record", style="solid"]; "1" [color="black", fontcolor="black", label="{DoNothing|\l|}", shape="record", style="solid"]; "2" [color="black", fontcolor="black", label="{Interface|\l|get_value()\lset_value(value)\l}", shape="record", style="solid"]; -"3" [color="black", fontcolor="black", label="{Specialization|TYPE : str\lrelation\ltop : str\l|}", shape="record", style="solid"]; -"3" -> "0" [arrowhead="empty", arrowtail="none"]; +"3" [color="black", fontcolor="black", label="{MyDict|\l|}", shape="record", style="solid"]; +"4" [color="black", fontcolor="black", label="{Specialization|TYPE : str\lrelation\ltop : str\l|}", shape="record", style="solid"]; +"4" -> "0" [arrowhead="empty", arrowtail="none"]; "0" -> "2" [arrowhead="empty", arrowtail="node", style="dashed"]; "1" -> "0" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"]; -"1" -> "3" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"]; +"1" -> "4" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"]; } diff --git a/tests/data/classes_colorized.dot b/tests/data/classes_colorized.dot index b815001b92..899109b17a 100644 --- a/tests/data/classes_colorized.dot +++ b/tests/data/classes_colorized.dot @@ -2,11 +2,26 @@ digraph "classes_colorized" { charset="utf-8" rankdir=BT "0" [color="aliceblue", fontcolor="black", label="{Ancestor|attr : str\lcls_member\l|get_value()\lset_value(value)\l}", shape="record", style="filled"]; -"1" [color="aliceblue", fontcolor="black", label="{DoNothing|\l|}", shape="record", style="filled"]; -"2" [color="aliceblue", fontcolor="black", label="{Interface|\l|get_value()\lset_value(value)\l}", shape="record", style="filled"]; -"3" [color="aliceblue", fontcolor="black", label="{Specialization|TYPE : str\lrelation\ltop : str\l|}", shape="record", style="filled"]; -"3" -> "0" [arrowhead="empty", arrowtail="none"]; -"0" -> "2" [arrowhead="empty", arrowtail="node", style="dashed"]; -"1" -> "0" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"]; -"1" -> "3" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"]; +"1" [color="grey", fontcolor="black", label="{Collection|\l|}", shape="record", style="filled"]; +"2" [color="grey", fontcolor="black", label="{Container|\l|}", shape="record", style="filled"]; +"3" [color="aliceblue", fontcolor="black", label="{DoNothing|\l|}", shape="record", style="filled"]; +"4" [color="aliceblue", fontcolor="black", label="{Interface|\l|get_value()\lset_value(value)\l}", shape="record", style="filled"]; +"5" [color="grey", fontcolor="black", label="{Iterable|\l|}", shape="record", style="filled"]; +"6" [color="grey", fontcolor="black", label="{Mapping|\l|get(key, default)\litems()\lkeys()\lvalues()\l}", shape="record", style="filled"]; +"7" [color="grey", fontcolor="black", label="{MutableMapping|\l|clear()\lpop(key, default)\lpopitem()\lsetdefault(key, default)\lupdate()\l}", shape="record", style="filled"]; +"8" [color="aliceblue", fontcolor="black", label="{MyDict|\l|}", shape="record", style="filled"]; +"9" [color="grey", fontcolor="black", label="{Sized|\l|}", shape="record", style="filled"]; +"10" [color="aliceblue", fontcolor="black", label="{Specialization|TYPE : str\lrelation\ltop : str\l|}", shape="record", style="filled"]; +"11" [color="grey", fontcolor="black", label="{UserDict|data : dict\l|copy()\lfromkeys(cls, iterable, value)\l}", shape="record", style="filled"]; +"1" -> "2" [arrowhead="empty", arrowtail="none"]; +"1" -> "5" [arrowhead="empty", arrowtail="none"]; +"1" -> "9" [arrowhead="empty", arrowtail="none"]; +"6" -> "1" [arrowhead="empty", arrowtail="none"]; +"7" -> "6" [arrowhead="empty", arrowtail="none"]; +"8" -> "11" [arrowhead="empty", arrowtail="none"]; +"10" -> "0" [arrowhead="empty", arrowtail="none"]; +"11" -> "7" [arrowhead="empty", arrowtail="none"]; +"0" -> "4" [arrowhead="empty", arrowtail="node", style="dashed"]; +"3" -> "0" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"]; +"3" -> "10" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"]; } diff --git a/tests/data/suppliermodule_test.py b/tests/data/suppliermodule_test.py index 24dc9a02fe..b86eadd8db 100644 --- a/tests/data/suppliermodule_test.py +++ b/tests/data/suppliermodule_test.py @@ -1,4 +1,5 @@ """ file suppliermodule.py """ +from collections import UserDict class Interface: def get_value(self): @@ -8,3 +9,5 @@ def set_value(self, value): raise NotImplementedError class DoNothing: pass + +class MyDict(UserDict): pass diff --git a/tests/unittest_pyreverse_writer.py b/tests/unittest_pyreverse_writer.py index abb99cb4e1..fd1dd5f73d 100644 --- a/tests/unittest_pyreverse_writer.py +++ b/tests/unittest_pyreverse_writer.py @@ -15,7 +15,7 @@ # For details: https://github.com/PyCQA/pylint/blob/master/LICENSE """ -unit test for visitors.diadefs and extensions.diadefslib modules +unit test for pyreverse writer classes """ @@ -49,7 +49,7 @@ } _COLORIZED = { - "all_ancestors": None, + "all_ancestors": True, "show_associated": None, "module_names": None, "output_format": "dot", From 36c36880106004cfee7e39d320589a9b8b29671a Mon Sep 17 00:00:00 2001 From: DudeNr33 <3929834+DudeNr33@users.noreply.github.com> Date: Fri, 28 May 2021 21:27:32 +0200 Subject: [PATCH 10/13] Add regression tests for vcg output --- pylint/pyreverse/vcgutils.py | 4 +-- tests/data/classes_vcg.vcg | 41 +++++++++++++++++++++++++++++ tests/data/packages_vcg.vcg | 20 ++++++++++++++ tests/unittest_pyreverse_diadefs.py | 1 + tests/unittest_pyreverse_writer.py | 29 +++++++++++++++++--- 5 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 tests/data/classes_vcg.vcg create mode 100644 tests/data/packages_vcg.vcg diff --git a/pylint/pyreverse/vcgutils.py b/pylint/pyreverse/vcgutils.py index 8f6810998a..3a940849ba 100644 --- a/pylint/pyreverse/vcgutils.py +++ b/pylint/pyreverse/vcgutils.py @@ -82,9 +82,7 @@ } # attributes that may be passed into the function but are not relevant vor the VCGPrinter (e.g. attributes only relevant for DOT files) -IGNORED_ATTRS = { - "style", -} +IGNORED_ATTRS = {"style", "color"} # meaning of possible values: # O -> string diff --git a/tests/data/classes_vcg.vcg b/tests/data/classes_vcg.vcg new file mode 100644 index 0000000000..2c87100db0 --- /dev/null +++ b/tests/data/classes_vcg.vcg @@ -0,0 +1,41 @@ +graph:{ + title:"classes_vcg" + layoutalgorithm:dfs + late_edge_labels:yes + port_sharing:no + manhattan_edges:yes + node: {title:"0" label:"\fbAncestor\fn\n\f____________\n\f08attr : str\n\f08cls_member\n\f____________\n\f10get_value()\n\f10set_value()" + shape:box +} + node: {title:"1" label:"\fbDoNothing\fn\n\f___________" + shape:box +} + node: {title:"2" label:"\fbInterface\fn\n\f___________\n\f10get_value()\n\f10set_value()" + shape:box +} + node: {title:"3" label:"\fbMyDict\fn\n\f________" + shape:box +} + node: {title:"4" label:"\fbSpecialization\fn\n\f________________\n\f08TYPE : str\n\f08relation\n\f08top : str\n\f________________" + shape:box +} + edge: {sourcename:"4" targetname:"0" arrowstyle:solid + backarrowstyle:none + backarrowsize:10 +} + edge: {sourcename:"0" targetname:"2" arrowstyle:solid + backarrowstyle:none + linestyle:dotted + backarrowsize:10 +} + edge: {sourcename:"1" targetname:"0" label:"cls_member" + arrowstyle:solid + backarrowstyle:none + textcolor:green +} + edge: {sourcename:"1" targetname:"4" label:"relation" + arrowstyle:solid + backarrowstyle:none + textcolor:green +} +} diff --git a/tests/data/packages_vcg.vcg b/tests/data/packages_vcg.vcg new file mode 100644 index 0000000000..c646166c6e --- /dev/null +++ b/tests/data/packages_vcg.vcg @@ -0,0 +1,20 @@ +graph:{ + title:"packages_vcg" + layoutalgorithm:dfs + late_edge_labels:yes + port_sharing:no + manhattan_edges:yes + node: {title:"0" label:"\fbdata\fn" + shape:box +} + node: {title:"1" label:"\fbdata.clientmodule_test\fn" + shape:box +} + node: {title:"2" label:"\fbdata.suppliermodule_test\fn" + shape:box +} + edge: {sourcename:"1" targetname:"2" arrowstyle:solid + backarrowstyle:none + backarrowsize:0 +} +} diff --git a/tests/unittest_pyreverse_diadefs.py b/tests/unittest_pyreverse_diadefs.py index 047060af38..9d4e355250 100644 --- a/tests/unittest_pyreverse_diadefs.py +++ b/tests/unittest_pyreverse_diadefs.py @@ -143,6 +143,7 @@ def test_known_values1(HANDLER, PROJECT): (True, "Ancestor"), (True, "DoNothing"), (True, "Interface"), + (True, "MyDict"), (True, "Specialization"), ] diff --git a/tests/unittest_pyreverse_writer.py b/tests/unittest_pyreverse_writer.py index fd1dd5f73d..e69e5840ca 100644 --- a/tests/unittest_pyreverse_writer.py +++ b/tests/unittest_pyreverse_writer.py @@ -28,7 +28,7 @@ from pylint.pyreverse.diadefslib import DefaultDiadefGenerator, DiadefsHandler from pylint.pyreverse.inspector import Linker, project_from_files from pylint.pyreverse.utils import get_visibility -from pylint.pyreverse.writer import DotWriter +from pylint.pyreverse.writer import DotWriter, VCGWriter _DEFAULTS = { "all_ancestors": None, @@ -48,6 +48,24 @@ "max_color_depth": 2, } +_VCG_OUTPUT = { + "all_ancestors": None, + "show_associated": None, + "module_names": None, + "output_format": "vcg", + "diadefs_file": None, + "quiet": 0, + "show_ancestors": None, + "classes": (), + "all_associated": None, + "mode": "PUB_ONLY", + "show_builtin": False, + "only_classnames": False, + "output_directory": "", + "colorized": False, + "max_color_depth": 2, +} + _COLORIZED = { "all_ancestors": True, "show_associated": None, @@ -100,6 +118,7 @@ def _astroid_wrapper(func, modname): DOT_FILES = ["packages_No_Name.dot", "classes_No_Name.dot"] COLORIZED_DOT_FILES = ["packages_colorized.dot", "classes_colorized.dot"] +VCG_FILES = ["packages_vcg.vcg", "classes_vcg.vcg"] def _create_files(config, name="No Name"): @@ -109,14 +128,17 @@ def _create_files(config, name="No Name"): dd = DefaultDiadefGenerator(linker, handler).visit(project) for diagram in dd: diagram.extract_relationships() - writer = DotWriter(config) + if config.output_format == "vcg": + writer = VCGWriter(config) + else: + writer = DotWriter(config) writer.write(dd) @pytest.fixture(scope="module") def cleanup(): yield - for fname in DOT_FILES + COLORIZED_DOT_FILES: + for fname in DOT_FILES + COLORIZED_DOT_FILES + VCG_FILES: try: os.remove(fname) except FileNotFoundError: @@ -129,6 +151,7 @@ def cleanup(): [ (Config(_DEFAULTS), "No Name", DOT_FILES), (Config(_COLORIZED), "colorized", COLORIZED_DOT_FILES), + (Config(_VCG_OUTPUT), "vcg", VCG_FILES), ], ) def test_dot_files(config, name, generated_files): From 225ef45af675d047edd7fe07792b12ed7831f172 Mon Sep 17 00:00:00 2001 From: DudeNr33 <3929834+DudeNr33@users.noreply.github.com> Date: Fri, 28 May 2021 21:53:05 +0200 Subject: [PATCH 11/13] Delete class inheriting from standard library in test data, since the test does not work in tox env --- tests/data/classes_No_Name.dot | 7 +++---- tests/data/classes_colorized.dot | 29 +++++++---------------------- tests/data/classes_vcg.vcg | 9 +++------ tests/data/suppliermodule_test.py | 3 --- tests/unittest_pyreverse_diadefs.py | 1 - 5 files changed, 13 insertions(+), 36 deletions(-) diff --git a/tests/data/classes_No_Name.dot b/tests/data/classes_No_Name.dot index f3ee953b13..68f52e4b7b 100644 --- a/tests/data/classes_No_Name.dot +++ b/tests/data/classes_No_Name.dot @@ -4,10 +4,9 @@ rankdir=BT "0" [color="black", fontcolor="black", label="{Ancestor|attr : str\lcls_member\l|get_value()\lset_value(value)\l}", shape="record", style="solid"]; "1" [color="black", fontcolor="black", label="{DoNothing|\l|}", shape="record", style="solid"]; "2" [color="black", fontcolor="black", label="{Interface|\l|get_value()\lset_value(value)\l}", shape="record", style="solid"]; -"3" [color="black", fontcolor="black", label="{MyDict|\l|}", shape="record", style="solid"]; -"4" [color="black", fontcolor="black", label="{Specialization|TYPE : str\lrelation\ltop : str\l|}", shape="record", style="solid"]; -"4" -> "0" [arrowhead="empty", arrowtail="none"]; +"3" [color="black", fontcolor="black", label="{Specialization|TYPE : str\lrelation\ltop : str\l|}", shape="record", style="solid"]; +"3" -> "0" [arrowhead="empty", arrowtail="none"]; "0" -> "2" [arrowhead="empty", arrowtail="node", style="dashed"]; "1" -> "0" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"]; -"1" -> "4" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"]; +"1" -> "3" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"]; } diff --git a/tests/data/classes_colorized.dot b/tests/data/classes_colorized.dot index 899109b17a..b815001b92 100644 --- a/tests/data/classes_colorized.dot +++ b/tests/data/classes_colorized.dot @@ -2,26 +2,11 @@ digraph "classes_colorized" { charset="utf-8" rankdir=BT "0" [color="aliceblue", fontcolor="black", label="{Ancestor|attr : str\lcls_member\l|get_value()\lset_value(value)\l}", shape="record", style="filled"]; -"1" [color="grey", fontcolor="black", label="{Collection|\l|}", shape="record", style="filled"]; -"2" [color="grey", fontcolor="black", label="{Container|\l|}", shape="record", style="filled"]; -"3" [color="aliceblue", fontcolor="black", label="{DoNothing|\l|}", shape="record", style="filled"]; -"4" [color="aliceblue", fontcolor="black", label="{Interface|\l|get_value()\lset_value(value)\l}", shape="record", style="filled"]; -"5" [color="grey", fontcolor="black", label="{Iterable|\l|}", shape="record", style="filled"]; -"6" [color="grey", fontcolor="black", label="{Mapping|\l|get(key, default)\litems()\lkeys()\lvalues()\l}", shape="record", style="filled"]; -"7" [color="grey", fontcolor="black", label="{MutableMapping|\l|clear()\lpop(key, default)\lpopitem()\lsetdefault(key, default)\lupdate()\l}", shape="record", style="filled"]; -"8" [color="aliceblue", fontcolor="black", label="{MyDict|\l|}", shape="record", style="filled"]; -"9" [color="grey", fontcolor="black", label="{Sized|\l|}", shape="record", style="filled"]; -"10" [color="aliceblue", fontcolor="black", label="{Specialization|TYPE : str\lrelation\ltop : str\l|}", shape="record", style="filled"]; -"11" [color="grey", fontcolor="black", label="{UserDict|data : dict\l|copy()\lfromkeys(cls, iterable, value)\l}", shape="record", style="filled"]; -"1" -> "2" [arrowhead="empty", arrowtail="none"]; -"1" -> "5" [arrowhead="empty", arrowtail="none"]; -"1" -> "9" [arrowhead="empty", arrowtail="none"]; -"6" -> "1" [arrowhead="empty", arrowtail="none"]; -"7" -> "6" [arrowhead="empty", arrowtail="none"]; -"8" -> "11" [arrowhead="empty", arrowtail="none"]; -"10" -> "0" [arrowhead="empty", arrowtail="none"]; -"11" -> "7" [arrowhead="empty", arrowtail="none"]; -"0" -> "4" [arrowhead="empty", arrowtail="node", style="dashed"]; -"3" -> "0" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"]; -"3" -> "10" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"]; +"1" [color="aliceblue", fontcolor="black", label="{DoNothing|\l|}", shape="record", style="filled"]; +"2" [color="aliceblue", fontcolor="black", label="{Interface|\l|get_value()\lset_value(value)\l}", shape="record", style="filled"]; +"3" [color="aliceblue", fontcolor="black", label="{Specialization|TYPE : str\lrelation\ltop : str\l|}", shape="record", style="filled"]; +"3" -> "0" [arrowhead="empty", arrowtail="none"]; +"0" -> "2" [arrowhead="empty", arrowtail="node", style="dashed"]; +"1" -> "0" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"]; +"1" -> "3" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"]; } diff --git a/tests/data/classes_vcg.vcg b/tests/data/classes_vcg.vcg index 2c87100db0..101d791c58 100644 --- a/tests/data/classes_vcg.vcg +++ b/tests/data/classes_vcg.vcg @@ -13,13 +13,10 @@ graph:{ node: {title:"2" label:"\fbInterface\fn\n\f___________\n\f10get_value()\n\f10set_value()" shape:box } - node: {title:"3" label:"\fbMyDict\fn\n\f________" + node: {title:"3" label:"\fbSpecialization\fn\n\f________________\n\f08TYPE : str\n\f08relation\n\f08top : str\n\f________________" shape:box } - node: {title:"4" label:"\fbSpecialization\fn\n\f________________\n\f08TYPE : str\n\f08relation\n\f08top : str\n\f________________" - shape:box -} - edge: {sourcename:"4" targetname:"0" arrowstyle:solid + edge: {sourcename:"3" targetname:"0" arrowstyle:solid backarrowstyle:none backarrowsize:10 } @@ -33,7 +30,7 @@ graph:{ backarrowstyle:none textcolor:green } - edge: {sourcename:"1" targetname:"4" label:"relation" + edge: {sourcename:"1" targetname:"3" label:"relation" arrowstyle:solid backarrowstyle:none textcolor:green diff --git a/tests/data/suppliermodule_test.py b/tests/data/suppliermodule_test.py index b86eadd8db..24dc9a02fe 100644 --- a/tests/data/suppliermodule_test.py +++ b/tests/data/suppliermodule_test.py @@ -1,5 +1,4 @@ """ file suppliermodule.py """ -from collections import UserDict class Interface: def get_value(self): @@ -9,5 +8,3 @@ def set_value(self, value): raise NotImplementedError class DoNothing: pass - -class MyDict(UserDict): pass diff --git a/tests/unittest_pyreverse_diadefs.py b/tests/unittest_pyreverse_diadefs.py index 098d1257d0..7809654109 100644 --- a/tests/unittest_pyreverse_diadefs.py +++ b/tests/unittest_pyreverse_diadefs.py @@ -141,7 +141,6 @@ def test_known_values1(HANDLER, PROJECT): (True, "Ancestor"), (True, "DoNothing"), (True, "Interface"), - (True, "MyDict"), (True, "Specialization"), ] From e8e898fc003b1218f3209222927a65204ecd6548 Mon Sep 17 00:00:00 2001 From: DudeNr33 <3929834+DudeNr33@users.noreply.github.com> Date: Sat, 29 May 2021 11:24:51 +0200 Subject: [PATCH 12/13] Refactoring: extract logic what attributes to set for packages in method ``get_package_properties``. Rename ``get_values`` to ``get_class_properties``. VCGWriter and VCGPrinter thus no longer need special handling. --- pylint/pyreverse/vcgutils.py | 5 ---- pylint/pyreverse/writer.py | 46 +++++++++++++++++------------------- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/pylint/pyreverse/vcgutils.py b/pylint/pyreverse/vcgutils.py index 3a940849ba..38193b3254 100644 --- a/pylint/pyreverse/vcgutils.py +++ b/pylint/pyreverse/vcgutils.py @@ -81,9 +81,6 @@ "linestyles": ("continuous", "dashed", "dotted", "invisible"), } -# attributes that may be passed into the function but are not relevant vor the VCGPrinter (e.g. attributes only relevant for DOT files) -IGNORED_ATTRS = {"style", "color"} - # meaning of possible values: # O -> string # 1 -> int @@ -197,8 +194,6 @@ def edge(self, from_node, to_node, edge_type="", **args): def _write_attributes(self, attributes_dict, **args): """write graph, node or edge attributes""" for key, value in args.items(): - if key in IGNORED_ATTRS: - continue try: _type = attributes_dict[key] except KeyError as e: diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py index 48b845dc4e..62863dc1d0 100644 --- a/pylint/pyreverse/writer.py +++ b/pylint/pyreverse/writer.py @@ -53,13 +53,7 @@ def write_packages(self, diagram): """write a package diagram""" # sorted to get predictable (hence testable) results for i, obj in enumerate(sorted(diagram.modules(), key=lambda x: x.title)): - self.printer.emit_node( - i, - label=self.get_title(obj), - shape="box", - color=self.get_color(obj), - style=self.get_style(), - ) + self.printer.emit_node(i, **self.get_package_properties(obj)) obj.fig_id = i # package dependencies for rel in diagram.get_relationships("depends"): @@ -71,7 +65,7 @@ def write_classes(self, diagram): """write a class diagram""" # sorted to get predictable (hence testable) results for i, obj in enumerate(sorted(diagram.objects, key=lambda x: x.title)): - self.printer.emit_node(i, **self.get_values(obj)) + self.printer.emit_node(i, **self.get_class_properties(obj)) obj.fig_id = i # inheritance links for rel in diagram.get_relationships("specialization"): @@ -100,15 +94,11 @@ def get_title(self, obj): """get project title""" raise NotImplementedError - def get_style(self): - """get style""" - raise NotImplementedError - - def get_color(self, obj): - """get shape color""" + def get_package_properties(self, obj): + """get label and shape for packages.""" raise NotImplementedError - def get_values(self, obj): + def get_class_properties(self, obj): """get label and shape for classes.""" raise NotImplementedError @@ -188,7 +178,16 @@ def get_color(self, obj): self.used_colors[base_name] = next(self.available_colors) return self.used_colors[base_name] - def get_values(self, obj): + def get_package_properties(self, obj): + """get label and shape for packages.""" + return dict( + label=self.get_title(obj), + shape="box", + color=self.get_color(obj), + style=self.get_style(), + ) + + def get_class_properties(self, obj): """get label and shape for classes. The label contains all attributes and methods @@ -254,15 +253,14 @@ def get_title(self, obj): """get project title in vcg format""" return r"\fb%s\fn" % obj.title - def get_style(self): - """get style of object""" - return None - - def get_color(self, obj): - """get color for object""" - return "black" + def get_package_properties(self, obj): + """get label and shape for packages.""" + return dict( + label=self.get_title(obj), + shape="box", + ) - def get_values(self, obj): + def get_class_properties(self, obj): """get label and shape for classes. The label contains all attributes and methods From e70c1356c3f9f408cf685ab1b70c08fd85b94a90 Mon Sep 17 00:00:00 2001 From: DudeNr33 <3929834+DudeNr33@users.noreply.github.com> Date: Sat, 29 May 2021 11:34:27 +0200 Subject: [PATCH 13/13] Add ChangeLog and whatsnew entrAdd ChangeLog and whatsnew entryy Add ChangeLog and whatsnew entry --- ChangeLog | 4 ++++ doc/whatsnew/2.9.rst | 2 ++ 2 files changed, 6 insertions(+) diff --git a/ChangeLog b/ChangeLog index a62cb9231d..4977c6db66 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,10 @@ Release date: TBA .. Put new features and bugfixes here and also in 'doc/whatsnew/2.9.rst' +* Added ``--colorized`` option to ``pyreverse`` to visualize modules/packages of the same parent package. + + Closes #4488 + * Added ``deprecated-decorator``: Emitted when deprecated decorator is used. Closes #4429 diff --git a/doc/whatsnew/2.9.rst b/doc/whatsnew/2.9.rst index c3ad8e7bf3..0f7cb27bc8 100644 --- a/doc/whatsnew/2.9.rst +++ b/doc/whatsnew/2.9.rst @@ -55,3 +55,5 @@ Other Changes like ``[]`` and ``[1, 2, 3]``. * ``ignore-paths`` configuration directive has been added. Defined regex patterns are matched against file path. + +* ``pyreverse`` now supports automatic coloring of package and class diagrams for .dot files by passing the ``--colorized`` option.