Compare commits
28 Commits
416940fd10
...
placement_
Author | SHA1 | Date | |
---|---|---|---|
f10221686c | |||
342de3e1d1 | |||
0dbd5f57e6 | |||
cea3e35800 | |||
60e09252c8 | |||
b3e0083633 | |||
fa5157298c | |||
00484711ab | |||
83f666353f | |||
405dded626 | |||
cc56bf33ce | |||
2790dbfbab | |||
e5b3e80378 | |||
f41e31371f | |||
1eb7aa37d3 | |||
5aad932c7c | |||
f7b88fe127 | |||
318a8362ca | |||
aa56fa473b | |||
5ba24cafaf | |||
c53b3b7669 | |||
61b1838c40 | |||
4b7f0e8231 | |||
9758f9aa50 | |||
f7edc44154 | |||
9a4de29af8 | |||
c0b34c1c5e | |||
4b14443fa1 |
@@ -6,8 +6,15 @@
|
||||
"py_to_uxf_core": "src/py_to_uxf_core/py_to_uxf_core.json"
|
||||
},
|
||||
"Scripts": {
|
||||
"build-start": "cpl build; cd dist/py_to_uxf/build/py_to_uxf; echo \"Starting:\"; bash py_to_uxf ./",
|
||||
"bs": "cpl build-start"
|
||||
"build-start": "cpl build; cd dist/py_to_uxf/build/py_to_uxf; echo \"Starting:\"; bash py_to_uxf -p ./ -o uml.uxf",
|
||||
"build-start-test": "cpl build; cd dist/py_to_uxf/build/py_to_uxf; echo \"Starting:\"; bash py_to_uxf -p ../../../../sh_gismo/src -o gismo.uxf",
|
||||
"compile": "cd src/; pyinstaller --specpath ../dist/cspec --workpath ../dist/cbuild --distpath ../dist/cdist --add-data ../../src/py_to_uxf/appsettings.json:. --hidden-import pynput.keyboard._xorg --hidden-import pynput.mouse._xorg --hidden-import pyfiglet.fonts --collect-data pyfiglet -n py_to_uxf -y py_to_uxf/main.py; cd ../;",
|
||||
"compile-start": "cpl compile; cd dist/cdist/py_to_uxf; echo \"Starting:\"; ./py_to_uxf -p ../../../src -o uml.uxf; cd ../../../",
|
||||
"compile-start-test": "cpl compile; cd dist/cdist/py_to_uxf; echo \"Starting:\"; ./py_to_uxf -p ../../../../sh_gismo/src -o gismo.uxf; cd ../../../",
|
||||
"bs": "cpl build-start",
|
||||
"bst": "cpl build-start-test",
|
||||
"cs": "cpl compile-start",
|
||||
"cst": "cpl compile-start-test"
|
||||
}
|
||||
}
|
||||
}
|
5
scripts/run.sh
Normal file
5
scripts/run.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
cd dist/cdist/py_to_uxf
|
||||
|
||||
time ./py_to_uxf -p ../../../../sh_gismo/src -o gismo.uxf
|
||||
|
||||
cd ../../../
|
@@ -1,9 +1,16 @@
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from cpl_core.application import ApplicationABC
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.console import Console
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_query.extension import List
|
||||
|
||||
from py_to_uxf_core.abc.python_parser_abc import PythonParserABC
|
||||
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.python_class import PythonClass
|
||||
|
||||
|
||||
class Application(ApplicationABC):
|
||||
@@ -11,17 +18,66 @@ class Application(ApplicationABC):
|
||||
def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
|
||||
ApplicationABC.__init__(self, config, services)
|
||||
|
||||
self._path = config.get_configuration('path')
|
||||
self._parser = services.get_service(PythonParserABC)
|
||||
config.parse_console_arguments(self._services)
|
||||
self._path = config.get_configuration('p')
|
||||
self._file = config.get_configuration('o')
|
||||
|
||||
self._parser: PythonParserABC = services.get_service(PythonParserABC)
|
||||
self._umlet_creator: UmletCreatorABC = services.get_service(UmletCreatorABC)
|
||||
|
||||
def configure(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _console_output(classes: list[PythonClass]):
|
||||
for cls in classes:
|
||||
Console.write_line(f'Class {cls.name}')
|
||||
if len(cls.attributes) > 0:
|
||||
Console.write_line('\tAttributes:')
|
||||
for atr in cls.attributes:
|
||||
Console.write_line(f'\t{atr.access_modifier.value}{atr.name}: {atr.type}')
|
||||
|
||||
if len(cls.functions) > 0:
|
||||
Console.write_line('\tFunctions:')
|
||||
for func in cls.functions:
|
||||
args = ''
|
||||
Console.write_line(f'\t{func.access_modifier.value}{func.name}({args}): {func.return_type}')
|
||||
Console.write_line()
|
||||
|
||||
@staticmethod
|
||||
def _exit(msg: str, tb=None):
|
||||
if tb is not None:
|
||||
Console.error(msg, tb)
|
||||
else:
|
||||
Console.error(msg)
|
||||
sys.exit()
|
||||
|
||||
def main(self):
|
||||
Console.banner('Py to UXF')
|
||||
if self._path is None:
|
||||
Console.write_line('Expected path')
|
||||
return
|
||||
self._exit('Expected path\n')
|
||||
|
||||
Console.write_line(f'Found path:', self._path)
|
||||
self._parser.parse()
|
||||
if self._file is None:
|
||||
self._exit(f'Expected output file\n')
|
||||
|
||||
Console.write_line(f'Input path:', self._path)
|
||||
Console.write_line(f'Output path:', self._file)
|
||||
xml = ''
|
||||
try:
|
||||
classes: List[PythonClass] = self._parser.parse_classes()
|
||||
Console.write_line(f'Found {classes.count()} classes')
|
||||
implementations: List[ClassImplementation] = self._parser.parse_implementations(classes)
|
||||
Console.write_line(f'Found {implementations.count()} implementations')
|
||||
# self._console_output(classes)
|
||||
xml = self._umlet_creator.generate_xml(classes, implementations)
|
||||
except Exception as e:
|
||||
self._exit('Parsing failed', f'{e} -> {traceback.format_exc()}\n')
|
||||
|
||||
if not self._file.endswith('.uxf'):
|
||||
self._exit(f'Unexpected file {self._file}\n')
|
||||
|
||||
with open(self._file, 'w+') as file:
|
||||
file.write(xml)
|
||||
file.close()
|
||||
|
||||
Console.write_line('\n')
|
||||
|
12
src/py_to_uxf/appsettings.json
Normal file
12
src/py_to_uxf/appsettings.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"Parser": {
|
||||
"IgnoreClassNames": [
|
||||
"ABC",
|
||||
"ABCMeta",
|
||||
"Enum"
|
||||
]
|
||||
},
|
||||
"UMLCreator": {
|
||||
"MaxPixelX": 2000
|
||||
}
|
||||
}
|
@@ -7,5 +7,5 @@ cd "$path/../"
|
||||
|
||||
export PYTHONPATH=./:$PYTHONPATH
|
||||
|
||||
python3.9 py_to_uxf/main.py --path $@
|
||||
python3.9 py_to_uxf/main.py $@
|
||||
echo ""
|
||||
|
@@ -16,13 +16,14 @@
|
||||
"LicenseName": "",
|
||||
"LicenseDescription": "",
|
||||
"Dependencies": [
|
||||
"sh_cpl-core>=2021.11.0.post3",
|
||||
"sh_cpl-query==2021.11.0.post3"
|
||||
"cpl-core==2022.10.0.post7",
|
||||
"cpl-query==2022.10.0.post2"
|
||||
],
|
||||
"PythonVersion": ">=3.9.2",
|
||||
"PythonPath": {
|
||||
"linux": ""
|
||||
},
|
||||
"DevDependencies": [
|
||||
"cpl-cli==2022.10.1"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {},
|
||||
"Classifiers": []
|
||||
},
|
||||
"BuildSettings": {
|
||||
|
@@ -1,18 +1,24 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from cpl_core.application import StartupABC
|
||||
from cpl_core.configuration import ConfigurationABC, ConsoleArgument
|
||||
from cpl_core.configuration import ConfigurationABC, ArgumentTypeEnum
|
||||
from cpl_core.console import Console
|
||||
from cpl_core.dependency_injection import ServiceProviderABC, ServiceCollectionABC
|
||||
from cpl_core.environment import ApplicationEnvironment
|
||||
|
||||
from py_to_uxf_core.abc.attribute_scanner_abc import AttributeScannerABC
|
||||
from py_to_uxf_core.abc.class_scanner_abc import ClassScannerABC
|
||||
from py_to_uxf_core.abc.file_scanner_abc import FileScannerABC
|
||||
from py_to_uxf_core.abc.function_scanner_abc import FunctionScannerABC
|
||||
from py_to_uxf_core.abc.implementation_scanner_abc import ImplementationScannerABC
|
||||
from py_to_uxf_core.abc.python_parser_abc import PythonParserABC
|
||||
from py_to_uxf_core.abc.umlet_creator_abc import UmletCreatorABC
|
||||
from py_to_uxf_core.service.attribute_scanner_service import AttributeScannerService
|
||||
from py_to_uxf_core.service.class_scanner_service import ClassScannerService
|
||||
from py_to_uxf_core.service.file_scanner_service import FileScannerService
|
||||
from py_to_uxf_core.service.function_scanner_service import FunctionScannerService
|
||||
from py_to_uxf_core.service.implementation_scanner_service import ImplementationScannerService
|
||||
from py_to_uxf_core.service.python_parser_service import PythonParserService
|
||||
from py_to_uxf_core.service.umlet_creator_service import UmletCreatorService
|
||||
|
||||
@@ -24,14 +30,25 @@ class Startup(StartupABC):
|
||||
|
||||
def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironment) -> ConfigurationABC:
|
||||
environment.set_runtime_directory(os.path.dirname(__file__))
|
||||
configuration.add_console_argument(ConsoleArgument('--', 'path', [], ' ', is_value_token_optional=False))
|
||||
configuration.add_console_arguments(error=False)
|
||||
if os.path.isfile('py_to_uxf/appsettings.json'):
|
||||
configuration.add_json_file('py_to_uxf/appsettings.json', optional=False, output=False)
|
||||
elif os.path.isfile('appsettings.json'):
|
||||
configuration.add_json_file('appsettings.json', optional=False, output=False)
|
||||
else:
|
||||
Console.error(f'Expected config file appsettings.json')
|
||||
sys.exit()
|
||||
|
||||
configuration.create_console_argument(ArgumentTypeEnum.Variable, '-', 'p', [], ' ')
|
||||
configuration.create_console_argument(ArgumentTypeEnum.Variable, '-', 'o', [], ' ')
|
||||
|
||||
return configuration
|
||||
|
||||
def configure_services(self, services: ServiceCollectionABC, environment: ApplicationEnvironment) -> ServiceProviderABC:
|
||||
services.add_transient(FileScannerABC, FileScannerService)
|
||||
services.add_transient(ClassScannerABC, ClassScannerService)
|
||||
services.add_transient(FunctionScannerABC, FunctionScannerService)
|
||||
services.add_transient(AttributeScannerABC, AttributeScannerService)
|
||||
services.add_transient(ImplementationScannerABC, ImplementationScannerService)
|
||||
|
||||
services.add_singleton(PythonParserABC, PythonParserService)
|
||||
services.add_singleton(UmletCreatorABC, UmletCreatorService)
|
||||
|
13
src/py_to_uxf_core/abc/attribute_scanner_abc.py
Normal file
13
src/py_to_uxf_core/abc/attribute_scanner_abc.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional
|
||||
|
||||
from py_to_uxf_core.model.python_class_attribute import PythonClassAttribute
|
||||
|
||||
|
||||
class AttributeScannerABC(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self): pass
|
||||
|
||||
@abstractmethod
|
||||
def scan_line_for_attribute(self, line: str) -> Optional[PythonClassAttribute]: pass
|
16
src/py_to_uxf_core/abc/implementation_scanner_abc.py
Normal file
16
src/py_to_uxf_core/abc/implementation_scanner_abc.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional, Union
|
||||
|
||||
from cpl_query.extension import List
|
||||
|
||||
from py_to_uxf_core.model.class_implementation import ClassImplementation
|
||||
from py_to_uxf_core.model.python_class import PythonClass
|
||||
|
||||
|
||||
class ImplementationScannerABC(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self): pass
|
||||
|
||||
@abstractmethod
|
||||
def scan_line_for_implementation(self, line: str, classes: list[PythonClass]) -> Union[Optional[ClassImplementation], List[ClassImplementation]]: pass
|
@@ -1,5 +1,10 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl_query.extension import List
|
||||
|
||||
from py_to_uxf_core.model.class_implementation import ClassImplementation
|
||||
from py_to_uxf_core.model.python_class import PythonClass
|
||||
|
||||
|
||||
class PythonParserABC(ABC):
|
||||
|
||||
@@ -7,4 +12,7 @@ class PythonParserABC(ABC):
|
||||
def __init__(self): pass
|
||||
|
||||
@abstractmethod
|
||||
def parse(self): pass
|
||||
def parse_classes(self) -> List[PythonClass]: pass
|
||||
|
||||
@abstractmethod
|
||||
def parse_implementations(self, classes: List[PythonClass]) -> List[ClassImplementation]: pass
|
||||
|
@@ -1,7 +1,13 @@
|
||||
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
|
||||
|
||||
|
||||
class UmletCreatorABC(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self): pass
|
||||
|
||||
@abstractmethod
|
||||
def generate_xml(self, classes: list[PythonClass], implementations: list[ClassImplementation]) -> str: pass
|
||||
|
25
src/py_to_uxf_core/configuration/__init__.py
Normal file
25
src/py_to_uxf_core/configuration/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
py_to_uxf
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
|
||||
:copyright: (c)
|
||||
:license:
|
||||
|
||||
"""
|
||||
|
||||
__title__ = 'py_to_uxf_core.configuration'
|
||||
__author__ = ''
|
||||
__license__ = ''
|
||||
__copyright__ = 'Copyright (c) '
|
||||
__version__ = '0.0.0'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
# imports
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='0', minor='0', micro='0')
|
23
src/py_to_uxf_core/configuration/parser_settings.py
Normal file
23
src/py_to_uxf_core/configuration/parser_settings.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import traceback
|
||||
|
||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl_core.console import Console
|
||||
|
||||
|
||||
class ParserSettings(ConfigurationModelABC):
|
||||
|
||||
def __init__(self):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
|
||||
self._ignore_class_names = []
|
||||
|
||||
@property
|
||||
def ignore_class_names(self) -> list[str]:
|
||||
return self._ignore_class_names
|
||||
|
||||
def from_dict(self, settings: dict):
|
||||
try:
|
||||
self._ignore_class_names = settings['IgnoreClassNames']
|
||||
except Exception as e:
|
||||
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {self.__name__} settings')
|
||||
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')
|
23
src/py_to_uxf_core/configuration/uml_creator_settings.py
Normal file
23
src/py_to_uxf_core/configuration/uml_creator_settings.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import traceback
|
||||
|
||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl_core.console import Console
|
||||
|
||||
|
||||
class UMLCreatorSettings(ConfigurationModelABC):
|
||||
|
||||
def __init__(self):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
|
||||
self._max_pixel_x = 0
|
||||
|
||||
@property
|
||||
def max_pixel_x(self) -> int:
|
||||
return self._max_pixel_x
|
||||
|
||||
def from_dict(self, settings: dict):
|
||||
try:
|
||||
self._max_pixel_x = settings['MaxPixelX']
|
||||
except Exception as e:
|
||||
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {self.__name__} settings')
|
||||
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')
|
@@ -1,7 +1,7 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class FunctionAccessModifierEnum(Enum):
|
||||
class AccessModifierEnum(Enum):
|
||||
|
||||
public = '+'
|
||||
private = '-'
|
31
src/py_to_uxf_core/model/class_implementation.py
Normal file
31
src/py_to_uxf_core/model/class_implementation.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from py_to_uxf_core.model.python_class import PythonClass
|
||||
|
||||
|
||||
class ClassImplementation:
|
||||
|
||||
def __init__(self, base: PythonClass, subclass: PythonClass):
|
||||
self._base = base
|
||||
self._subclass = subclass
|
||||
self._is_first = True
|
||||
|
||||
@property
|
||||
def base(self) -> PythonClass:
|
||||
return self._base
|
||||
|
||||
@property
|
||||
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}>'
|
||||
|
||||
def __repr__(self):
|
||||
return f'<{type(self).__name__} {self._is_first} {self._subclass.name} -> {self._base.name}>'
|
27
src/py_to_uxf_core/model/dimension.py
Normal file
27
src/py_to_uxf_core/model/dimension.py
Normal file
@@ -0,0 +1,27 @@
|
||||
class Dimension:
|
||||
|
||||
def __init__(self, width: int, height: int):
|
||||
self._width = width
|
||||
self._height = height
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return self._width
|
||||
|
||||
@width.setter
|
||||
def width(self, value: int):
|
||||
self._width = value
|
||||
|
||||
@property
|
||||
def height(self) -> int:
|
||||
return self._height
|
||||
|
||||
@height.setter
|
||||
def height(self, value: int):
|
||||
self._height = value
|
||||
|
||||
def __str__(self):
|
||||
return f'<{type(self).__name__} w:{self._width}, h:{self._height}>'
|
||||
|
||||
def __repr__(self):
|
||||
return f'<{type(self).__name__} w:{self._width}, h:{self._height}>'
|
27
src/py_to_uxf_core/model/position.py
Normal file
27
src/py_to_uxf_core/model/position.py
Normal file
@@ -0,0 +1,27 @@
|
||||
class Position:
|
||||
|
||||
def __init__(self, x: int, y: int):
|
||||
self._x = x
|
||||
self._y = y
|
||||
|
||||
@property
|
||||
def x(self) -> int:
|
||||
return self._x
|
||||
|
||||
@x.setter
|
||||
def x(self, value: int):
|
||||
self._x = value
|
||||
|
||||
@property
|
||||
def y(self) -> int:
|
||||
return self._y
|
||||
|
||||
@y.setter
|
||||
def y(self, value: int):
|
||||
self._y = value
|
||||
|
||||
def __str__(self):
|
||||
return f'<{type(self).__name__} x:{self._x}, y:{self._y}>'
|
||||
|
||||
def __repr__(self):
|
||||
return f'<{type(self).__name__} x:{self._x}, y:{self._y}>'
|
@@ -1,5 +1,6 @@
|
||||
from cpl_query.extension import List
|
||||
|
||||
from py_to_uxf_core.model.python_class_attribute import PythonClassAttribute
|
||||
from py_to_uxf_core.model.python_function import PythonFunction
|
||||
|
||||
|
||||
@@ -8,6 +9,7 @@ class PythonClass:
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
self._functions = List(PythonFunction)
|
||||
self._attributes = List(PythonClassAttribute)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
@@ -17,5 +19,18 @@ class PythonClass:
|
||||
def functions(self) -> List[PythonFunction]:
|
||||
return self._functions
|
||||
|
||||
@property
|
||||
def attributes(self) -> List[PythonClassAttribute]:
|
||||
return self._attributes
|
||||
|
||||
def add_function(self, func: PythonFunction):
|
||||
self._functions.append(func)
|
||||
|
||||
def add_attribute(self, attribute: PythonClassAttribute):
|
||||
self._attributes.append(attribute)
|
||||
|
||||
def __str__(self):
|
||||
return f'<{type(self).__name__} {self._name} a:{len(self._attributes)} f:{len(self._functions)}>'
|
||||
|
||||
def __repr__(self):
|
||||
return f'<{type(self).__name__} {self._name} a:{len(self._attributes)} f:{len(self._functions)}>'
|
||||
|
27
src/py_to_uxf_core/model/python_class_attribute.py
Normal file
27
src/py_to_uxf_core/model/python_class_attribute.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from py_to_uxf_core.model.access_modifier_enum import AccessModifierEnum
|
||||
|
||||
|
||||
class PythonClassAttribute:
|
||||
|
||||
def __init__(self, access_modifier: AccessModifierEnum, name: str, type: str):
|
||||
self._access_modifier = access_modifier
|
||||
self._name = name
|
||||
self._type = type
|
||||
|
||||
@property
|
||||
def access_modifier(self) -> AccessModifierEnum:
|
||||
return self._access_modifier
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def type(self) -> str:
|
||||
return self._type
|
||||
|
||||
def __str__(self):
|
||||
return f'<{type(self).__name__} {self._name}: {self._type}>'
|
||||
|
||||
def __repr__(self):
|
||||
return f'<{type(self).__name__} {self._name}: {self._type}>'
|
@@ -1,12 +1,12 @@
|
||||
from cpl_query.extension import List
|
||||
|
||||
from py_to_uxf_core.model.function_access_modifier_enum import FunctionAccessModifierEnum
|
||||
from py_to_uxf_core.model.access_modifier_enum import AccessModifierEnum
|
||||
from py_to_uxf_core.model.python_function_argument import PythonFunctionArgument
|
||||
|
||||
|
||||
class PythonFunction:
|
||||
|
||||
def __init__(self, access_modifier: FunctionAccessModifierEnum, name: str, args: List[PythonFunctionArgument], return_type: str):
|
||||
def __init__(self, access_modifier: AccessModifierEnum, name: str, args: List[PythonFunctionArgument], return_type: str):
|
||||
self._access_modifier = access_modifier
|
||||
self._name = name
|
||||
self._args = args
|
||||
|
@@ -13,7 +13,7 @@ class PythonFunctionArgument:
|
||||
return self._type
|
||||
|
||||
def __str__(self):
|
||||
return f'{self._name}: {self._type}'
|
||||
return f'<{type(self).__name__} {self._name}: {self._type}>'
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self._name}: {self._type}'
|
||||
return f'<{type(self).__name__} {self._name}: {self._type}>'
|
||||
|
146
src/py_to_uxf_core/model/uml_class.py
Normal file
146
src/py_to_uxf_core/model/uml_class.py
Normal file
@@ -0,0 +1,146 @@
|
||||
from cpl_query.extension import List
|
||||
|
||||
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
|
||||
|
||||
|
||||
class UMLClass:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
cls: PythonClass,
|
||||
pos: Position,
|
||||
dim: Dimension
|
||||
):
|
||||
self._cls = cls
|
||||
self._position = pos
|
||||
self._dimension = dim
|
||||
|
||||
self._base_classes = List(type(self))
|
||||
self._sub_classes = List(type(self))
|
||||
|
||||
self._attributes = ''
|
||||
self._functions = ''
|
||||
|
||||
@property
|
||||
def cls(self) -> PythonClass:
|
||||
return self._cls
|
||||
|
||||
@property
|
||||
def position(self) -> Position:
|
||||
return self._position
|
||||
|
||||
@position.setter
|
||||
def position(self, value: int):
|
||||
self._position = value
|
||||
|
||||
@property
|
||||
def dimension(self) -> Dimension:
|
||||
return self._dimension
|
||||
|
||||
@dimension.setter
|
||||
def dimension(self, value: int):
|
||||
self._dimension = value
|
||||
|
||||
@property
|
||||
def base_classes(self) -> List['UMLClass']:
|
||||
return self._base_classes
|
||||
|
||||
@base_classes.setter
|
||||
def base_classes(self, value: List['UMLClass']):
|
||||
self._base_classes = value
|
||||
|
||||
@property
|
||||
def sub_classes(self) -> List['UMLClass']:
|
||||
return self._sub_classes
|
||||
|
||||
@sub_classes.setter
|
||||
def sub_classes(self, value: List['UMLClass']):
|
||||
self._sub_classes = value
|
||||
|
||||
def __str__(self):
|
||||
return f'<{type(self).__name__} {self._cls}: {self._position} {self._dimension}>'
|
||||
|
||||
def __repr__(self):
|
||||
return f'<{type(self).__name__} {self._cls}: {self._position} {self._dimension}>'
|
||||
|
||||
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"""\
|
||||
<element>
|
||||
<id>Relation</id>
|
||||
<coordinates>
|
||||
<x>{base_point.x}</x>
|
||||
<y>{base_point.y}</y>
|
||||
<w>0</w>
|
||||
<h>0</h>
|
||||
</coordinates>
|
||||
<panel_attributes>lt=<<-
|
||||
</panel_attributes>
|
||||
<additional_attributes>{float(path_position.x)};{float(path_position.y)};{float(path_dimension.width)};{float(path_dimension.height)}</additional_attributes>
|
||||
</element>
|
||||
""".replace(' ', '').replace('\t', '')
|
||||
|
||||
def create_xml_body(self):
|
||||
px_per_line = 16
|
||||
px_per_char = 2.9
|
||||
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
|
||||
|
||||
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)
|
||||
self._attributes += attribute
|
||||
|
||||
if len(self._cls.functions) > 0:
|
||||
for func in self._cls.functions:
|
||||
args = ''
|
||||
function = f'{func.access_modifier.value}{func.name}({args}): {func.return_type}\n'
|
||||
if len(function) > longest_line_length:
|
||||
longest_line_length = len(function)
|
||||
self._functions += function
|
||||
|
||||
self._dimension.width = int(round(longest_line_length * px_per_char * px_per_char, -1))
|
||||
|
||||
def as_xml(self) -> str:
|
||||
return f"""\
|
||||
<element>
|
||||
<id>UMLClass</id>
|
||||
<coordinates>
|
||||
<x>{self._position.x}</x>
|
||||
<y>{self._position.y}</y>
|
||||
<w>{self._dimension.width}</w>
|
||||
<h>{self._dimension.height}</h>
|
||||
</coordinates>
|
||||
<panel_attributes>
|
||||
{self._cls.name}
|
||||
--
|
||||
{self._attributes}
|
||||
--
|
||||
{self._functions}
|
||||
</panel_attributes>
|
||||
<additional_attributes></additional_attributes>
|
||||
</element>
|
||||
""".replace(' ', '').replace('\t', '')
|
@@ -16,9 +16,12 @@
|
||||
"LicenseName": "",
|
||||
"LicenseDescription": "",
|
||||
"Dependencies": [
|
||||
"sh_cpl-core>=2021.11.0.post3"
|
||||
"cpl-core==2022.10.0.post2"
|
||||
],
|
||||
"PythonVersion": ">=3.9.2",
|
||||
"DevDependencies": [
|
||||
"cpl-cli==2022.10.1"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {
|
||||
"linux": ""
|
||||
},
|
||||
|
53
src/py_to_uxf_core/service/attribute_scanner_service.py
Normal file
53
src/py_to_uxf_core/service/attribute_scanner_service.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from typing import Optional
|
||||
|
||||
from cpl_core.console import Console
|
||||
|
||||
from py_to_uxf_core.abc.attribute_scanner_abc import AttributeScannerABC
|
||||
from py_to_uxf_core.model.access_modifier_enum import AccessModifierEnum
|
||||
from py_to_uxf_core.model.python_class_attribute import PythonClassAttribute
|
||||
|
||||
|
||||
class AttributeScannerService(AttributeScannerABC):
|
||||
|
||||
def __init__(self):
|
||||
AttributeScannerABC.__init__(self)
|
||||
|
||||
def scan_line_for_attribute(self, line: str) -> Optional[PythonClassAttribute]:
|
||||
# ATR=1
|
||||
# ATR:int=1
|
||||
# self.ATR=1
|
||||
# self._ATR=1
|
||||
# self.__ATR=1
|
||||
# self._ATR:int=1
|
||||
if '=' in line:
|
||||
line.replace(' ', '')
|
||||
name = line.split('=')[0]
|
||||
|
||||
if name.startswith('self.'):
|
||||
name = name.split('self.')[1]
|
||||
|
||||
if ':' in name:
|
||||
name = name.split(':')[0]
|
||||
|
||||
if '.' in name:
|
||||
return None
|
||||
|
||||
access_modifier = AccessModifierEnum.public
|
||||
type_str = 'any'
|
||||
|
||||
# access_modifier
|
||||
if name == '__init__' or name == '__repr__' or name == '__str__':
|
||||
access_modifier = AccessModifierEnum.public
|
||||
elif name.startswith('_'):
|
||||
access_modifier = AccessModifierEnum.protected
|
||||
elif name.startswith('__'):
|
||||
access_modifier = AccessModifierEnum.private
|
||||
|
||||
if ':' in line:
|
||||
type_str = line.split(':')[1]
|
||||
if '=' in type_str:
|
||||
type_str = type_str.split('=')[0]
|
||||
|
||||
return PythonClassAttribute(access_modifier, name, type_str)
|
||||
|
||||
return None
|
@@ -12,6 +12,8 @@ class ClassScannerService(ClassScannerABC):
|
||||
ClassScannerABC.__init__(self)
|
||||
|
||||
def scan_line_for_classes(self, line: str) -> Optional[PythonClass]:
|
||||
line = line.replace(' ', '')
|
||||
line = line.replace('\t', '')
|
||||
if line.startswith('class'):
|
||||
name = line.split('class ')[1]
|
||||
if '(' in name:
|
||||
|
@@ -12,7 +12,7 @@ class FileScannerService(FileScannerABC):
|
||||
def __init__(self, config: ConfigurationABC):
|
||||
FileScannerABC.__init__(self)
|
||||
self._config = config
|
||||
self._path = config.get_configuration('path')
|
||||
self._path = config.get_configuration('p')
|
||||
|
||||
def scan_files(self) -> List[str]:
|
||||
files = List(str)
|
||||
|
@@ -1,10 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
import value as value
|
||||
from cpl_core.console import Console
|
||||
from cpl_query.extension import List
|
||||
|
||||
from py_to_uxf_core.abc.function_scanner_abc import FunctionScannerABC
|
||||
from py_to_uxf_core.model.function_access_modifier_enum import FunctionAccessModifierEnum
|
||||
from py_to_uxf_core.model.access_modifier_enum import AccessModifierEnum
|
||||
from py_to_uxf_core.model.python_function import PythonFunction
|
||||
from py_to_uxf_core.model.python_function_argument import PythonFunctionArgument
|
||||
|
||||
@@ -21,46 +21,48 @@ class FunctionScannerService(FunctionScannerABC):
|
||||
# async def xy(x: int, y: int) -> int:
|
||||
# async def _xy(x: int, y: int) -> int:
|
||||
# async def __xy(x: int, y: int) -> int:
|
||||
if line.startswith('def') or line.startswith('async def'):
|
||||
# name
|
||||
name = line.split('def ')[1]
|
||||
name = name.split('(')[0]
|
||||
access_modifier = FunctionAccessModifierEnum.public
|
||||
args = List()
|
||||
return_type = 'None'
|
||||
if not line.startswith('def ') and not line.startswith('async def '):
|
||||
return None
|
||||
|
||||
# access_modifier
|
||||
if name == '__init__' or name == '__repr__' or name == '__str__':
|
||||
access_modifier = FunctionAccessModifierEnum.public
|
||||
elif name.startswith('_'):
|
||||
access_modifier = FunctionAccessModifierEnum.protected
|
||||
elif name.startswith('__'):
|
||||
access_modifier = FunctionAccessModifierEnum.private
|
||||
line = line.replace('\t', '')
|
||||
# name
|
||||
name = line.split('def ')[1]
|
||||
name = name.split('(')[0]
|
||||
access_modifier = AccessModifierEnum.public
|
||||
args = List()
|
||||
return_type = 'None'
|
||||
|
||||
# args
|
||||
args_str = line.split('(')[1]
|
||||
args_str = args_str.split(')')[0]
|
||||
# multiple arguments
|
||||
if ',' in args_str:
|
||||
for argument in args_str.split(','):
|
||||
# with type
|
||||
type_str = 'any'
|
||||
arg_name = argument
|
||||
if ':' in argument:
|
||||
type_str = argument.split(': ')[1]
|
||||
arg_name = argument.split(': ')[0]
|
||||
# without type
|
||||
if arg_name.startswith(' '):
|
||||
arg_name = arg_name.split(' ')[1]
|
||||
args.append(PythonFunctionArgument(arg_name, type_str))
|
||||
# single argument
|
||||
elif args_str != '':
|
||||
pass
|
||||
# access_modifier
|
||||
if name == '__init__' or name == '__repr__' or name == '__str__':
|
||||
access_modifier = AccessModifierEnum.public
|
||||
elif name.startswith('_'):
|
||||
access_modifier = AccessModifierEnum.protected
|
||||
elif name.startswith('__'):
|
||||
access_modifier = AccessModifierEnum.private
|
||||
|
||||
# return_type
|
||||
if '->' in line:
|
||||
return_type_str = line.split('-> ')[1]
|
||||
return_type = return_type_str.split(':')[0]
|
||||
# args
|
||||
args_str = line.split('(')[1]
|
||||
args_str = args_str.split(')')[0]
|
||||
# multiple arguments
|
||||
if ',' in args_str:
|
||||
for argument in args_str.split(','):
|
||||
# with type
|
||||
type_str = 'any'
|
||||
arg_name = argument
|
||||
if ':' in argument:
|
||||
type_str = argument.split(': ')[1]
|
||||
arg_name = argument.split(': ')[0]
|
||||
# without type
|
||||
if arg_name.startswith(' '):
|
||||
arg_name = arg_name.split(' ')[1]
|
||||
args.append(PythonFunctionArgument(arg_name, type_str))
|
||||
# single argument
|
||||
elif args_str != '':
|
||||
pass
|
||||
|
||||
return PythonFunction(access_modifier, name, args, return_type)
|
||||
return None
|
||||
# return_type
|
||||
if '->' in line:
|
||||
return_type_str = line.split('-> ')[1]
|
||||
return_type = return_type_str.split(':')[0]
|
||||
|
||||
return PythonFunction(access_modifier, name, args, return_type)
|
||||
|
61
src/py_to_uxf_core/service/implementation_scanner_service.py
Normal file
61
src/py_to_uxf_core/service/implementation_scanner_service.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from typing import Optional, Union
|
||||
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.console import Console
|
||||
from cpl_query.extension import List
|
||||
|
||||
from py_to_uxf_core.abc.implementation_scanner_abc import ImplementationScannerABC
|
||||
from py_to_uxf_core.configuration.parser_settings import ParserSettings
|
||||
from py_to_uxf_core.model.class_implementation import ClassImplementation
|
||||
from py_to_uxf_core.model.python_class import PythonClass
|
||||
|
||||
|
||||
class ImplementationScannerService(ImplementationScannerABC):
|
||||
|
||||
def __init__(self, configuration: ConfigurationABC):
|
||||
ImplementationScannerABC.__init__(self)
|
||||
self._parser_settings: ParserSettings = configuration.get_configuration(ParserSettings)
|
||||
|
||||
@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 is None:
|
||||
new_base = PythonClass(base_name)
|
||||
classes.append(new_base)
|
||||
return ClassImplementation(new_base, sub_class)
|
||||
|
||||
return ClassImplementation(base, sub_class)
|
||||
|
||||
def scan_line_for_implementation(self, line: str, classes: List[PythonClass]) -> Union[Optional[ClassImplementation], List[ClassImplementation]]:
|
||||
line = line.replace(' ', '')
|
||||
line = line.replace('\t', '')
|
||||
if line.startswith('class ') and '(' in line and ')' in line:
|
||||
sub_name = line.split('class ')[1].split('(')[0]
|
||||
subclasses = classes.where(lambda c: c.name == sub_name)
|
||||
implementations = List(ClassImplementation)
|
||||
for sub in subclasses:
|
||||
line = line.replace(' ', '')
|
||||
base_name = line.split('(')[1].split(')')[0]
|
||||
if ',' not in base_name:
|
||||
implementation = self._get_implementation(base_name, sub, classes)
|
||||
if implementation is not None:
|
||||
implementations.append(implementation)
|
||||
continue
|
||||
|
||||
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:
|
||||
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
|
@@ -1,11 +1,17 @@
|
||||
import sys
|
||||
import traceback
|
||||
from typing import Optional
|
||||
|
||||
from cpl_core.console import Console
|
||||
from cpl_query.extension import List
|
||||
|
||||
from py_to_uxf_core.abc.attribute_scanner_abc import AttributeScannerABC
|
||||
from py_to_uxf_core.abc.class_scanner_abc import ClassScannerABC
|
||||
from py_to_uxf_core.abc.file_scanner_abc import FileScannerABC
|
||||
from py_to_uxf_core.abc.function_scanner_abc import FunctionScannerABC
|
||||
from py_to_uxf_core.abc.implementation_scanner_abc import ImplementationScannerABC
|
||||
from py_to_uxf_core.abc.python_parser_abc import PythonParserABC
|
||||
from py_to_uxf_core.model.class_implementation import ClassImplementation
|
||||
from py_to_uxf_core.model.python_class import PythonClass
|
||||
|
||||
|
||||
@@ -15,55 +21,135 @@ class PythonParserService(PythonParserABC):
|
||||
self,
|
||||
file_scanner: FileScannerABC,
|
||||
class_scanner: ClassScannerABC,
|
||||
function_scanner: FunctionScannerABC
|
||||
function_scanner: FunctionScannerABC,
|
||||
attribute_scanner: AttributeScannerABC,
|
||||
implementation_scanner: ImplementationScannerABC
|
||||
):
|
||||
PythonParserABC.__init__(self)
|
||||
|
||||
self._file_scanner = file_scanner
|
||||
self._class_scanner = class_scanner
|
||||
self._function_scanner = function_scanner
|
||||
self._attribute_scanner = attribute_scanner
|
||||
self._implementation_scanner = implementation_scanner
|
||||
|
||||
def parse(self):
|
||||
self._files_with_classes: list[str] = []
|
||||
|
||||
def parse_classes(self) -> List[PythonClass]:
|
||||
files = self._file_scanner.scan_files()
|
||||
classes = List(PythonClass)
|
||||
for file in files:
|
||||
Console.write_line('\nfi:', file)
|
||||
is_comment = False
|
||||
is_function = False
|
||||
with open(file, 'r') as file_content:
|
||||
cls: Optional[PythonClass] = None
|
||||
for line in file_content.readlines():
|
||||
line = line.replace(' ', '')
|
||||
line = line.replace('\t', '')
|
||||
# replace line break at the end of line
|
||||
if line.endswith('\n'):
|
||||
line = line.replace('\n', '')
|
||||
lines = file_content.readlines()
|
||||
|
||||
if line == '\n' or line == '':
|
||||
continue
|
||||
for i in range(len(lines)):
|
||||
try:
|
||||
line = lines[i]
|
||||
line_with_tabs = line.replace(' ', '\t')
|
||||
line = line.replace(' ', '')
|
||||
line = line.replace('\t', '')
|
||||
# replace line break at the end of line
|
||||
if line.endswith('\n'):
|
||||
line = line.replace('\n', '')
|
||||
|
||||
# one line comments
|
||||
if line != '"""' and line.startswith('"""') and line.endswith('"""') or line.startswith('#'):
|
||||
continue
|
||||
if line == '\n' or line == '':
|
||||
continue
|
||||
|
||||
# start multi line comment
|
||||
if line.startswith('"""') and not is_comment:
|
||||
is_comment = True
|
||||
continue
|
||||
# one line comments
|
||||
if line != '"""' and line.startswith('"""') and line.endswith('"""') or line.startswith('#'):
|
||||
continue
|
||||
|
||||
# end multi line comment
|
||||
if line.startswith('"""') or line.endswith('"""') and is_comment:
|
||||
is_comment = False
|
||||
continue
|
||||
# start multi line comment
|
||||
if line.startswith('"""') or line.count('"""') == 1 and not is_comment:
|
||||
is_comment = True
|
||||
continue
|
||||
|
||||
if is_comment:
|
||||
continue
|
||||
# end multi line comment
|
||||
if line.startswith('"""') or line.endswith('"""') or line.count('"""') == 1 and is_comment:
|
||||
is_comment = False
|
||||
continue
|
||||
|
||||
if cls is None:
|
||||
cls = self._class_scanner.scan_line_for_classes(line)
|
||||
if cls is not None:
|
||||
Console.write_line('cl:', cls.name)
|
||||
continue
|
||||
if is_comment:
|
||||
continue
|
||||
|
||||
func = self._function_scanner.scan_line_for_function(line)
|
||||
if func is not None:
|
||||
cls.add_function(func)
|
||||
Console.write_line('fu:', func.access_modifier.value, func.name, func.args, func.return_type)
|
||||
if cls is None:
|
||||
cls = self._class_scanner.scan_line_for_classes(line)
|
||||
if cls is not None:
|
||||
classes.append(cls)
|
||||
self._files_with_classes.append(file)
|
||||
continue
|
||||
|
||||
func = self._function_scanner.scan_line_for_function(line)
|
||||
if func is not None:
|
||||
cls.add_function(func)
|
||||
is_function = True if func.name != '__init__' else False
|
||||
continue
|
||||
|
||||
if is_function:
|
||||
continue
|
||||
|
||||
attribute = self._attribute_scanner.scan_line_for_attribute(line)
|
||||
if attribute is not None:
|
||||
cls.add_attribute(attribute)
|
||||
except Exception as e:
|
||||
Console.error(f'Parsing {file}@{i}', f'{e} -> {traceback.format_exc()}')
|
||||
file_content.close()
|
||||
sys.exit()
|
||||
file_content.close()
|
||||
return classes
|
||||
|
||||
def parse_implementations(self, classes: List[PythonClass]) -> List[ClassImplementation]:
|
||||
implementations = List()
|
||||
files = self._files_with_classes
|
||||
for file in files:
|
||||
is_comment = False
|
||||
with open(file, 'r') as file_content:
|
||||
implementation: Optional[ClassImplementation] = None
|
||||
lines = file_content.readlines()
|
||||
|
||||
for i in range(len(lines)):
|
||||
try:
|
||||
line = lines[i]
|
||||
line = line.replace(' ', '')
|
||||
line = line.replace('\t', '')
|
||||
# replace line break at the end of line
|
||||
if line.endswith('\n'):
|
||||
line = line.replace('\n', '')
|
||||
|
||||
if line == '\n' or line == '':
|
||||
continue
|
||||
|
||||
# one line comments
|
||||
if line != '"""' and line.startswith('"""') and line.endswith('"""') or line.startswith('#'):
|
||||
continue
|
||||
|
||||
# start multi line comment
|
||||
if line.startswith('"""') or line.count('"""') == 1 and not is_comment:
|
||||
is_comment = True
|
||||
continue
|
||||
|
||||
# end multi line comment
|
||||
if line.startswith('"""') or line.endswith('"""') or line.count('"""') == 1 and is_comment:
|
||||
is_comment = False
|
||||
continue
|
||||
|
||||
if is_comment:
|
||||
continue
|
||||
|
||||
if implementation is None:
|
||||
implementation = self._implementation_scanner.scan_line_for_implementation(line, classes)
|
||||
if implementation is not None:
|
||||
if isinstance(implementation, List):
|
||||
for impl in implementation:
|
||||
implementations.append(impl)
|
||||
else:
|
||||
implementations.append(implementation)
|
||||
except Exception as e:
|
||||
Console.error(f'Parsing {file}@{i}', f'{e} -> {traceback.format_exc()}')
|
||||
file_content.close()
|
||||
sys.exit()
|
||||
file_content.close()
|
||||
return implementations
|
||||
|
@@ -1,7 +1,122 @@
|
||||
import sys
|
||||
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.console import Console
|
||||
from cpl_query.extension import List
|
||||
|
||||
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
|
||||
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, List]
|
||||
|
||||
|
||||
class UmletCreatorService(UmletCreatorABC):
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, config: ConfigurationABC):
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
def _resolve_implementations(classes: List[UMLClass], implementations: List[ClassImplementation]) -> List[UMLClass]:
|
||||
new_classes = List(UMLClass)
|
||||
sub_classes = List(UMLClass)
|
||||
for implementation in implementations:
|
||||
implementation: ClassImplementation = implementation
|
||||
base: UMLClass = classes.where(lambda c: c.cls == implementation.base).first()
|
||||
sub: UMLClass = classes.where(lambda c: c.cls == implementation.subclass).first()
|
||||
|
||||
base.sub_classes.append(sub)
|
||||
sub.base_classes.append(base)
|
||||
if base not in new_classes:
|
||||
new_classes.append(base)
|
||||
sub_classes.append(sub)
|
||||
|
||||
for cls in classes:
|
||||
if cls not in new_classes and cls not in sub_classes:
|
||||
new_classes.append(cls)
|
||||
|
||||
return new_classes
|
||||
|
||||
def _move_classes(self, classes: List[UMLClass], moved_classes: List[UMLClass], next_pos: Position, highest_y: int):
|
||||
for cls in classes:
|
||||
if cls.cls.name in self._parser_settings.ignore_class_names:
|
||||
continue
|
||||
|
||||
if cls not in moved_classes:
|
||||
Console.write_line(f'MOVE {cls.cls.name} to {next_pos} - {cls.dimension}')
|
||||
|
||||
if next_pos.x > self._settings.max_pixel_x:
|
||||
next_pos.x = self._padding
|
||||
next_pos.y = highest_y + self._padding * 2
|
||||
|
||||
cls.position.x = next_pos.x
|
||||
cls.position.y = next_pos.y
|
||||
|
||||
next_pos.x = cls.position.x + cls.dimension.width + self._padding
|
||||
|
||||
if cls.position.y + cls.dimension.height > highest_y:
|
||||
highest_y = cls.position.y + cls.dimension.height
|
||||
|
||||
moved_classes.append(cls)
|
||||
|
||||
if cls.sub_classes.count() > 0:
|
||||
next_pos.x = cls.position.x
|
||||
next_pos.y = cls.position.y + cls.dimension.height + self._padding
|
||||
self._move_classes(cls.sub_classes, moved_classes, next_pos, highest_y)
|
||||
|
||||
def generate_xml(self, classes: List[PythonClass], implementations: List[ClassImplementation]) -> str:
|
||||
uml_classes = List(UMLClass)
|
||||
xml_classes = ''
|
||||
xml_relations = ''
|
||||
|
||||
for cls in classes:
|
||||
uml_cls = UMLClass(cls, Position(0, self._padding), Dimension(80, 80))
|
||||
# trigger xml generation to render coordinates
|
||||
uml_cls.create_xml_body()
|
||||
uml_classes.append(uml_cls)
|
||||
|
||||
sorted_classes = self._resolve_implementations(uml_classes, implementations)
|
||||
moved_classes = List(UMLClass)
|
||||
self._move_classes(sorted_classes, moved_classes, Position(self._padding, self._padding), 0)
|
||||
|
||||
for uml_cls in moved_classes:
|
||||
# save class xml
|
||||
if uml_cls.cls.name in self._parser_settings.ignore_class_names:
|
||||
continue
|
||||
|
||||
xml_classes += uml_cls.as_xml()
|
||||
|
||||
# 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)
|
||||
|
||||
return f"""\
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<diagram program="umlet" version="14.1.1">
|
||||
<zoom_level>10</zoom_level>
|
||||
{xml_classes}
|
||||
{xml_relations}
|
||||
</diagram>
|
||||
""".replace(' ', '').replace('\t', '')
|
||||
|
Reference in New Issue
Block a user