From 5ba24cafaf4ce8301eead547d2002183d82f4f3c Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sun, 16 Jan 2022 20:11:13 +0100 Subject: [PATCH] Added rendering of implementations --- src/py_to_uxf/application.py | 3 +- src/py_to_uxf_core/abc/umlet_creator_abc.py | 3 +- src/py_to_uxf_core/model/dimension.py | 6 ++ src/py_to_uxf_core/model/position.py | 6 ++ src/py_to_uxf_core/model/uml_class.py | 58 +++++++++++--- .../service/umlet_creator_service.py | 79 ++++++++++++++++--- 6 files changed, 132 insertions(+), 23 deletions(-) diff --git a/src/py_to_uxf/application.py b/src/py_to_uxf/application.py index ba25583..135d07f 100644 --- a/src/py_to_uxf/application.py +++ b/src/py_to_uxf/application.py @@ -64,9 +64,8 @@ class Application(ApplicationABC): Console.write_line(f'Found {classes.count()} classes') implementations: List[ClassImplementation] = self._parser.parse_implementations(classes) Console.write_line(f'Found {implementations.count()} implementations') - Console.write_line(implementations) # self._console_output(classes) - xml = self._umlet_creator.generate_xml(classes) + xml = self._umlet_creator.generate_xml(classes, implementations) except Exception as e: Console.error('Parsing failed', f'{e} -> {traceback.format_exc()}') exit() diff --git a/src/py_to_uxf_core/abc/umlet_creator_abc.py b/src/py_to_uxf_core/abc/umlet_creator_abc.py index 18f6e34..c387234 100644 --- a/src/py_to_uxf_core/abc/umlet_creator_abc.py +++ b/src/py_to_uxf_core/abc/umlet_creator_abc.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod +from py_to_uxf_core.model.class_implementation import ClassImplementation from py_to_uxf_core.model.python_class import PythonClass @@ -9,4 +10,4 @@ class UmletCreatorABC(ABC): def __init__(self): pass @abstractmethod - def generate_xml(self, classes: list[PythonClass]) -> str: pass + def generate_xml(self, classes: list[PythonClass], implementations: list[ClassImplementation]) -> str: pass diff --git a/src/py_to_uxf_core/model/dimension.py b/src/py_to_uxf_core/model/dimension.py index 8016038..781a4ca 100644 --- a/src/py_to_uxf_core/model/dimension.py +++ b/src/py_to_uxf_core/model/dimension.py @@ -19,3 +19,9 @@ class Dimension: @height.setter def height(self, value: int): self._height = value + + def __str__(self): + return f'(w:{self._width}, h:{self._height})' + + def __repr__(self): + return f'(w:{self._width}, h:{self._height})' diff --git a/src/py_to_uxf_core/model/position.py b/src/py_to_uxf_core/model/position.py index 78db191..7e8fbf2 100644 --- a/src/py_to_uxf_core/model/position.py +++ b/src/py_to_uxf_core/model/position.py @@ -19,3 +19,9 @@ class Position: @y.setter def y(self, value: int): self._y = value + + def __str__(self): + return f'(x:{self._x}, y:{self._y})' + + def __repr__(self): + return f'(x:{self._x}, y:{self._y})' diff --git a/src/py_to_uxf_core/model/uml_class.py b/src/py_to_uxf_core/model/uml_class.py index bd72e12..09339f3 100644 --- a/src/py_to_uxf_core/model/uml_class.py +++ b/src/py_to_uxf_core/model/uml_class.py @@ -1,3 +1,4 @@ +import math import textwrap from cpl_core.console import Console @@ -19,6 +20,9 @@ class UMLClass: self._position = pos self._dimension = dim + self._attributes = '' + self._functions = '' + @property def cls(self) -> PythonClass: return self._cls @@ -39,21 +43,54 @@ class UMLClass: def dimension(self, value: int): self._dimension = value - def as_xml(self) -> str: + def implementation_as_xml(self, base_class: 'UMLClass') -> str: + base_point = Position( + base_class.position.x + int(base_class.dimension.width / 2), + base_class.position.y + base_class.dimension.height + ) + + sub_point = Position( + self._position.x + int(self._dimension.width / 2), + self._position.y + ) + + path_dimension = Dimension( + self._position.x - base_class.position.x, + sub_point.y - base_point.y + ) + path_position = Position( + 0, + 0 + ) + + return f"""\ + + Relation + + {base_point.x} + {base_point.y} + 0 + 0 + + lt=<<- + + {float(path_position.x)};{float(path_position.y)};{float(path_dimension.width)};{float(path_dimension.height)} + + """.replace(' ', '').replace('\t', '') + + def create_xml_body(self): px_per_line = 16 px_per_char = 2.9 - self._dimension.height += (self._cls.attributes.count() + self._cls.functions.count()) * px_per_line + self._dimension.height += int( + round((self._cls.attributes.count() + self._cls.functions.count()) * px_per_line, -1)) longest_line_length = self._dimension.width / px_per_char - attributes = '' - functions = '' - if len(self._cls.attributes) > 0: for atr in self._cls.attributes: attribute = f'{atr.access_modifier.value}{atr.name}: {atr.type}\n' if len(attribute) > longest_line_length: longest_line_length = len(attribute) - attributes += attribute + self._attributes += attribute if len(self._cls.functions) > 0: for func in self._cls.functions: @@ -61,10 +98,11 @@ class UMLClass: function = f'{func.access_modifier.value}{func.name}({args}): {func.return_type}\n' if len(function) > longest_line_length: longest_line_length = len(function) - functions += function + self._functions += function - self._dimension.width = round(longest_line_length * px_per_char * px_per_char) + self._dimension.width = int(round(longest_line_length * px_per_char * px_per_char, -1)) + def as_xml(self) -> str: return f"""\ UMLClass @@ -77,9 +115,9 @@ class UMLClass: {self._cls.name} -- - {attributes} + {self._attributes} -- - {functions} + {self._functions} diff --git a/src/py_to_uxf_core/service/umlet_creator_service.py b/src/py_to_uxf_core/service/umlet_creator_service.py index 3b81449..f288a67 100644 --- a/src/py_to_uxf_core/service/umlet_creator_service.py +++ b/src/py_to_uxf_core/service/umlet_creator_service.py @@ -1,8 +1,8 @@ -import textwrap - -from cpl_core.console import Console +import classes as classes +from cpl_query.extension import List from py_to_uxf_core.abc.umlet_creator_abc import UmletCreatorABC +from py_to_uxf_core.model.class_implementation import ClassImplementation from py_to_uxf_core.model.dimension import Dimension from py_to_uxf_core.model.position import Position from py_to_uxf_core.model.python_class import PythonClass @@ -14,22 +14,81 @@ class UmletCreatorService(UmletCreatorABC): def __init__(self): UmletCreatorABC.__init__(self) - def generate_xml(self, classes: list[PythonClass]) -> str: - xml_cls = '' + self._padding = 30 + + def _sort_by_implementation(self, uml_classes: List[UMLClass], implementations: List[ClassImplementation]): + base_sub_map: dict[UMLClass, list[UMLClass]] = {} + moved_classes: list[UMLClass] = [] + for implementation in implementations: + sub_class: UMLClass = uml_classes.where(lambda u: u.cls == implementation.subclass).first() + base_class: UMLClass = uml_classes.where(lambda u: u.cls == implementation.base).first() + + if base_class not in base_sub_map: + base_sub_map[base_class] = [] + + base_sub_map[base_class].append(sub_class) + + last_base = Position(self._padding, self._padding) + for base in base_sub_map: + subclasses = base_sub_map[base] + + base.position.x = last_base.x + base.position.y = self._padding + + last_sub = Position(base.position.x, base.position.y + base.dimension.height) + for sub_class in subclasses: + sub_class.position.x = base.position.x + sub_class.position.y = base.position.y + + if sub_class.position.x <= last_sub.x: + sub_class.position.x = last_sub.x + if sub_class.position.y <= last_sub.y: + sub_class.position.y = last_sub.y + self._padding * 2 + + last_sub.x = sub_class.position.x + sub_class.dimension.width + self._padding + moved_classes.append(sub_class) + + last_base = last_sub + last_base.x += self._padding * 2 + moved_classes.append(base) + + for cls in uml_classes: + if cls in moved_classes: + continue + + cls.position.x = last_base.x + self._padding + last_base.x = cls.position.x + cls.dimension.width + + def generate_xml(self, classes: List[PythonClass], implementations: List[ClassImplementation]) -> str: + uml_classes = List(UMLClass) + xml_classes = '' + xml_relations = '' default_width = 80 default_height = 80 - next_x = 10 + next_x = self._padding for cls in classes: - uml_cls = UMLClass(cls, Position(next_x, 10), Dimension(default_width, default_height)) + uml_cls = UMLClass(cls, Position(next_x, self._padding), Dimension(default_width, default_height)) + # trigger xml generation to render coordinates + uml_cls.create_xml_body() + next_x += round(uml_cls.dimension.width, -1) + self._padding + uml_classes.append(uml_cls) + + self._sort_by_implementation(uml_classes, implementations) + for implementation in implementations: + sub_class: UMLClass = uml_classes.where(lambda u: u.cls == implementation.subclass).first() + base_class: UMLClass = uml_classes.where(lambda u: u.cls == implementation.base).first() + xml_relations += sub_class.implementation_as_xml(base_class) + + for uml_cls in uml_classes: # save class xml - xml_cls += uml_cls.as_xml() - next_x += round(uml_cls.dimension.width, -1) + 10 + xml_classes += uml_cls.as_xml() return f"""\ 10 - {xml_cls} + {xml_classes} + {xml_relations} """.replace(' ', '').replace('\t', '')