diff --git a/src/py_to_uxf/appsettings.json b/src/py_to_uxf/appsettings.json index 04c7f32..5c85d9e 100644 --- a/src/py_to_uxf/appsettings.json +++ b/src/py_to_uxf/appsettings.json @@ -2,6 +2,7 @@ "Parser": { "IgnoreClassNames": [ "ABC", + "ABCMeta", "Enum" ] }, diff --git a/src/py_to_uxf_core/model/class_implementation.py b/src/py_to_uxf_core/model/class_implementation.py index fc06af3..293fe8d 100644 --- a/src/py_to_uxf_core/model/class_implementation.py +++ b/src/py_to_uxf_core/model/class_implementation.py @@ -6,7 +6,7 @@ class ClassImplementation: def __init__(self, base: PythonClass, subclass: PythonClass): self._base = base self._subclass = subclass - self.is_first = True + self._is_first = True @property def base(self) -> PythonClass: @@ -16,8 +16,16 @@ class ClassImplementation: def subclass(self) -> PythonClass: return self._subclass + @property + def is_first(self) -> bool: + return self._is_first + + @is_first.setter + def is_first(self, value: bool): + self._is_first = value + def __str__(self): - return f'<{type(self).__name__} {self.is_first} {self._subclass.name} -> {self._base.name}>' + return f'<{type(self).__name__} {self._is_first} {self._subclass.name} -> {self._base.name}>' def __repr__(self): - return f'<{type(self).__name__} {self.is_first} {self._subclass.name} -> {self._base.name}>' + return f'<{type(self).__name__} {self._is_first} {self._subclass.name} -> {self._base.name}>' diff --git a/src/py_to_uxf_core/model/uml_class.py b/src/py_to_uxf_core/model/uml_class.py index a9ea9be..2dca5f6 100644 --- a/src/py_to_uxf_core/model/uml_class.py +++ b/src/py_to_uxf_core/model/uml_class.py @@ -1,7 +1,4 @@ -import math -import textwrap - -from cpl_core.console import Console +from cpl_query.extension import List from py_to_uxf_core.model.dimension import Dimension from py_to_uxf_core.model.position import Position diff --git a/src/py_to_uxf_core/service/implementation_scanner_service.py b/src/py_to_uxf_core/service/implementation_scanner_service.py index e44b390..1aaaaa6 100644 --- a/src/py_to_uxf_core/service/implementation_scanner_service.py +++ b/src/py_to_uxf_core/service/implementation_scanner_service.py @@ -16,11 +16,9 @@ class ImplementationScannerService(ImplementationScannerABC): ImplementationScannerABC.__init__(self) self._parser_settings: ParserSettings = configuration.get_configuration(ParserSettings) - def _get_implementation(self, base_name: str, sub_class: PythonClass, classes: List[PythonClass]) -> Optional[ClassImplementation]: + @staticmethod + def _get_implementation(base_name: str, sub_class: PythonClass, classes: List[PythonClass]) -> Optional[ClassImplementation]: base: Optional[PythonClass] = classes.where(lambda c: c.name == base_name).first_or_default() - if base_name in self._parser_settings.ignore_class_names: - return None - if base is None: new_base = PythonClass(base_name) classes.append(new_base) @@ -44,13 +42,20 @@ class ImplementationScannerService(ImplementationScannerABC): implementations.append(implementation) continue - for name in base_name.split(','): + split_base_name = base_name.split(',') + found_first_base = False + for i in range(len(split_base_name)): + name = split_base_name[i] if '=' in name: continue implementation = self._get_implementation(name, sub, classes) if implementation is not None: - implementation.is_first = False + if i > 0 and found_first_base: + implementation.is_first = False + + if not found_first_base: + found_first_base = name not in self._parser_settings.ignore_class_names implementations.append(implementation) return implementations return None 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 8a55d3c..afc64e2 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,11 @@ +import sys + from cpl_core.configuration import ConfigurationABC from cpl_core.console import Console -from cpl_query.extension import List +from cpl_query.extension import List, IterableABC from py_to_uxf_core.abc.umlet_creator_abc import UmletCreatorABC +from py_to_uxf_core.configuration.parser_settings import ParserSettings from py_to_uxf_core.configuration.uml_creator_settings import UMLCreatorSettings from py_to_uxf_core.model.class_implementation import ClassImplementation from py_to_uxf_core.model.dimension import Dimension @@ -10,6 +13,8 @@ from py_to_uxf_core.model.position import Position from py_to_uxf_core.model.python_class import PythonClass from py_to_uxf_core.model.uml_class import UMLClass +OrderedImplementation = dict[ClassImplementation, IterableABC] + class UmletCreatorService(UmletCreatorABC): @@ -17,6 +22,7 @@ class UmletCreatorService(UmletCreatorABC): UmletCreatorABC.__init__(self) self._settings: UMLCreatorSettings = config.get_configuration(UMLCreatorSettings) + self._parser_settings: ParserSettings = config.get_configuration(ParserSettings) self._moved_classes: list[UMLClass] = [] self._padding = 30 @@ -85,6 +91,112 @@ class UmletCreatorService(UmletCreatorABC): if cls.position.y + cls.dimension.height > highest_y: highest_y = cls.position.y + cls.dimension.height + def _sort_implementations_by_base_order(self, implementations: List[ClassImplementation]) -> OrderedImplementation: + """ + Sort given implementations by order of base classes + SubClass(FirstBaseClass, SecondBaseClass, ThirdBaseClass) + """ + ordered_implementations: OrderedImplementation = {} + for implementation in list(implementations): + if not implementation.is_first or implementation.base.name in self._parser_settings.ignore_class_names: + continue + + ordered_implementations[implementation] = implementations.where(lambda i: i.subclass == implementation.subclass and not i.is_first) + + return ordered_implementations + + def _move_by_implementation(self, implementations: OrderedImplementation, uml_classes: List[UMLClass]) -> List[UMLClass]: + base_sub_map: dict[UMLClass, 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] = [] + + if sub_class in base_sub_map[base_class]: + continue + + base_sub_map[base_class].append(sub_class) + for sub_impl in implementations[implementation]: + si_sub_class: UMLClass = uml_classes.where(lambda u: u.cls == sub_impl.subclass).first() + si_base_class: UMLClass = uml_classes.where(lambda u: u.cls == sub_impl.base).first() + + if si_base_class not in base_sub_map: + base_sub_map[si_base_class] = [] + + if si_sub_class in base_sub_map[si_base_class]: + continue + base_sub_map[si_base_class].append(si_sub_class) + + moved_classes = List(UMLClass) + next_pos = Position(self._padding, self._padding) + highest_y = 0 + for base in base_sub_map: + subclasses = base_sub_map[base] + if base in moved_classes: + Console.write_line('b_SKIP', base) + continue + + if next_pos.x >= self._settings.max_pixel_x: + next_pos.x = self._padding + next_pos.y = highest_y + self._padding + + base.position.x = next_pos.x + base.position.y = next_pos.y + if base.position.y + base.dimension.height > highest_y: + highest_y = base.position.y + base.dimension.height + + next_pos.y = base.position.y + base.dimension.height + self._padding + + Console.write_line('moved b', base) + moved_classes.append(base) + + moved_subclasses = [] + for sub in subclasses: + if sub in moved_classes: + Console.write_line('s_SKIP', sub) + continue + + sub.position.x = next_pos.x + sub.position.y = next_pos.y + next_pos.x += sub.dimension.width + self._padding + if sub.position.y + sub.dimension.height > highest_y: + highest_y = sub.position.y + sub.dimension.height + + Console.write_line('moved s', sub) + moved_classes.append(sub) + moved_subclasses.append(sub) + + delta_x = self._padding + if len(moved_subclasses) == 0: + delta_x += base.dimension.width + next_pos.x += delta_x + next_pos.y = base.position.y + + for cls in uml_classes: + if cls in moved_classes: + continue + + new_x = next_pos.x + if new_x <= self._settings.max_pixel_x: + cls.position.x = new_x + next_pos.x = new_x + cls.dimension.width + self._padding + cls.position.y = next_pos.y + else: + cls.position.x = self._padding + cls.position.y = highest_y + self._padding * 2 + next_pos.x = cls.position.x + next_pos.y = cls.position.y + + if cls.position.y + cls.dimension.height > highest_y: + highest_y = cls.position.y + cls.dimension.height + + moved_classes.append(cls) + # Console.write_line('moved c', cls) + + return moved_classes + def generate_xml(self, classes: List[PythonClass], implementations: List[ClassImplementation]) -> str: uml_classes = List(UMLClass) xml_classes = '' @@ -96,16 +208,22 @@ class UmletCreatorService(UmletCreatorABC): uml_cls.create_xml_body() uml_classes.append(uml_cls) - self._sort_by_implementation(uml_classes, implementations) + # self._sort_by_implementation(uml_classes, implementations) + oi = self._sort_implementations_by_base_order(implementations) + for uml_cls in self._move_by_implementation(oi, uml_classes): + # save class xml + if uml_cls.cls.name in self._parser_settings.ignore_class_names: + continue + + xml_classes += uml_cls.as_xml() + 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.cls.name in self._parser_settings.ignore_class_names: + continue xml_relations += sub_class.implementation_as_xml(base_class) - for uml_cls in uml_classes: - # save class xml - xml_classes += uml_cls.as_xml() - return f"""\