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" |       "py_to_uxf_core": "src/py_to_uxf_core/py_to_uxf_core.json" | ||||||
|     }, |     }, | ||||||
|     "Scripts": { |     "Scripts": { | ||||||
|       "build-start": "cpl build; cd dist/py_to_uxf/build/py_to_uxf; echo \"Starting:\"; bash py_to_uxf ./", |       "build-start": "cpl build; cd dist/py_to_uxf/build/py_to_uxf; echo \"Starting:\"; bash py_to_uxf -p ./ -o uml.uxf", | ||||||
|       "bs": "cpl build-start" |       "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.application import ApplicationABC | ||||||
| from cpl_core.configuration import ConfigurationABC | from cpl_core.configuration import ConfigurationABC | ||||||
| from cpl_core.console import Console | from cpl_core.console import Console | ||||||
| from cpl_core.dependency_injection import ServiceProviderABC | 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.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): | class Application(ApplicationABC): | ||||||
| @@ -11,17 +18,66 @@ class Application(ApplicationABC): | |||||||
|     def __init__(self, config: ConfigurationABC, services: ServiceProviderABC): |     def __init__(self, config: ConfigurationABC, services: ServiceProviderABC): | ||||||
|         ApplicationABC.__init__(self, config, services) |         ApplicationABC.__init__(self, config, services) | ||||||
|  |  | ||||||
|         self._path = config.get_configuration('path') |         config.parse_console_arguments(self._services) | ||||||
|         self._parser = services.get_service(PythonParserABC) |         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): |     def configure(self): | ||||||
|         pass |         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): |     def main(self): | ||||||
|         Console.banner('Py to UXF') |         Console.banner('Py to UXF') | ||||||
|         if self._path is None: |         if self._path is None: | ||||||
|             Console.write_line('Expected path') |             self._exit('Expected path\n') | ||||||
|             return |  | ||||||
|  |  | ||||||
|         Console.write_line(f'Found path:', self._path) |         if self._file is None: | ||||||
|         self._parser.parse() |             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 | export PYTHONPATH=./:$PYTHONPATH | ||||||
|  |  | ||||||
| python3.9 py_to_uxf/main.py --path $@ | python3.9 py_to_uxf/main.py $@ | ||||||
| echo "" | echo "" | ||||||
|   | |||||||
| @@ -16,13 +16,14 @@ | |||||||
|     "LicenseName": "", |     "LicenseName": "", | ||||||
|     "LicenseDescription": "", |     "LicenseDescription": "", | ||||||
|     "Dependencies": [ |     "Dependencies": [ | ||||||
|       "sh_cpl-core>=2021.11.0.post3", |       "cpl-core==2022.10.0.post7", | ||||||
|       "sh_cpl-query==2021.11.0.post3" |       "cpl-query==2022.10.0.post2" | ||||||
|     ], |     ], | ||||||
|     "PythonVersion": ">=3.9.2", |     "DevDependencies": [ | ||||||
|     "PythonPath": { |       "cpl-cli==2022.10.1" | ||||||
|       "linux": "" |     ], | ||||||
|     }, |     "PythonVersion": ">=3.10.4", | ||||||
|  |     "PythonPath": {}, | ||||||
|     "Classifiers": [] |     "Classifiers": [] | ||||||
|   }, |   }, | ||||||
|   "BuildSettings": { |   "BuildSettings": { | ||||||
|   | |||||||
| @@ -1,18 +1,24 @@ | |||||||
| import os | import os | ||||||
|  | import sys | ||||||
|  |  | ||||||
| from cpl_core.application import StartupABC | 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.dependency_injection import ServiceProviderABC, ServiceCollectionABC | ||||||
| from cpl_core.environment import ApplicationEnvironment | 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.class_scanner_abc import ClassScannerABC | ||||||
| from py_to_uxf_core.abc.file_scanner_abc import FileScannerABC | 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.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.python_parser_abc import PythonParserABC | ||||||
| from py_to_uxf_core.abc.umlet_creator_abc import UmletCreatorABC | 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.class_scanner_service import ClassScannerService | ||||||
| from py_to_uxf_core.service.file_scanner_service import FileScannerService | 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.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.python_parser_service import PythonParserService | ||||||
| from py_to_uxf_core.service.umlet_creator_service import UmletCreatorService | 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: |     def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironment) -> ConfigurationABC: | ||||||
|         environment.set_runtime_directory(os.path.dirname(__file__)) |         environment.set_runtime_directory(os.path.dirname(__file__)) | ||||||
|         configuration.add_console_argument(ConsoleArgument('--', 'path', [], ' ', is_value_token_optional=False)) |         if os.path.isfile('py_to_uxf/appsettings.json'): | ||||||
|         configuration.add_console_arguments(error=False) |             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 |         return configuration | ||||||
|  |  | ||||||
|     def configure_services(self, services: ServiceCollectionABC, environment: ApplicationEnvironment) -> ServiceProviderABC: |     def configure_services(self, services: ServiceCollectionABC, environment: ApplicationEnvironment) -> ServiceProviderABC: | ||||||
|         services.add_transient(FileScannerABC, FileScannerService) |         services.add_transient(FileScannerABC, FileScannerService) | ||||||
|         services.add_transient(ClassScannerABC, ClassScannerService) |         services.add_transient(ClassScannerABC, ClassScannerService) | ||||||
|         services.add_transient(FunctionScannerABC, FunctionScannerService) |         services.add_transient(FunctionScannerABC, FunctionScannerService) | ||||||
|  |         services.add_transient(AttributeScannerABC, AttributeScannerService) | ||||||
|  |         services.add_transient(ImplementationScannerABC, ImplementationScannerService) | ||||||
|  |  | ||||||
|         services.add_singleton(PythonParserABC, PythonParserService) |         services.add_singleton(PythonParserABC, PythonParserService) | ||||||
|         services.add_singleton(UmletCreatorABC, UmletCreatorService) |         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 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): | class PythonParserABC(ABC): | ||||||
|  |  | ||||||
| @@ -7,4 +12,7 @@ class PythonParserABC(ABC): | |||||||
|     def __init__(self): pass |     def __init__(self): pass | ||||||
|  |  | ||||||
|     @abstractmethod |     @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 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): | class UmletCreatorABC(ABC): | ||||||
|  |  | ||||||
|     @abstractmethod |     @abstractmethod | ||||||
|     def __init__(self): pass |     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 | from enum import Enum | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class FunctionAccessModifierEnum(Enum): | class AccessModifierEnum(Enum): | ||||||
| 
 | 
 | ||||||
|     public = '+' |     public = '+' | ||||||
|     private = '-' |     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 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 | from py_to_uxf_core.model.python_function import PythonFunction | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -8,6 +9,7 @@ class PythonClass: | |||||||
|     def __init__(self, name): |     def __init__(self, name): | ||||||
|         self._name = name |         self._name = name | ||||||
|         self._functions = List(PythonFunction) |         self._functions = List(PythonFunction) | ||||||
|  |         self._attributes = List(PythonClassAttribute) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def name(self) -> str: |     def name(self) -> str: | ||||||
| @@ -17,5 +19,18 @@ class PythonClass: | |||||||
|     def functions(self) -> List[PythonFunction]: |     def functions(self) -> List[PythonFunction]: | ||||||
|         return self._functions |         return self._functions | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def attributes(self) -> List[PythonClassAttribute]: | ||||||
|  |         return self._attributes | ||||||
|  |  | ||||||
|     def add_function(self, func: PythonFunction): |     def add_function(self, func: PythonFunction): | ||||||
|         self._functions.append(func) |         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 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 | from py_to_uxf_core.model.python_function_argument import PythonFunctionArgument | ||||||
|  |  | ||||||
|  |  | ||||||
| class PythonFunction: | 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._access_modifier = access_modifier | ||||||
|         self._name = name |         self._name = name | ||||||
|         self._args = args |         self._args = args | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ class PythonFunctionArgument: | |||||||
|         return self._type |         return self._type | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return f'{self._name}: {self._type}' |         return f'<{type(self).__name__} {self._name}: {self._type}>' | ||||||
|  |  | ||||||
|     def __repr__(self): |     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": "", |     "LicenseName": "", | ||||||
|     "LicenseDescription": "", |     "LicenseDescription": "", | ||||||
|     "Dependencies": [ |     "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": { |     "PythonPath": { | ||||||
|       "linux": "" |       "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) |         ClassScannerABC.__init__(self) | ||||||
|  |  | ||||||
|     def scan_line_for_classes(self, line: str) -> Optional[PythonClass]: |     def scan_line_for_classes(self, line: str) -> Optional[PythonClass]: | ||||||
|  |         line = line.replace('    ', '') | ||||||
|  |         line = line.replace('\t', '') | ||||||
|         if line.startswith('class'): |         if line.startswith('class'): | ||||||
|             name = line.split('class ')[1] |             name = line.split('class ')[1] | ||||||
|             if '(' in name: |             if '(' in name: | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ class FileScannerService(FileScannerABC): | |||||||
|     def __init__(self, config: ConfigurationABC): |     def __init__(self, config: ConfigurationABC): | ||||||
|         FileScannerABC.__init__(self) |         FileScannerABC.__init__(self) | ||||||
|         self._config = config |         self._config = config | ||||||
|         self._path = config.get_configuration('path') |         self._path = config.get_configuration('p') | ||||||
|  |  | ||||||
|     def scan_files(self) -> List[str]: |     def scan_files(self) -> List[str]: | ||||||
|         files = List(str) |         files = List(str) | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| from typing import Optional | from typing import Optional | ||||||
|  |  | ||||||
| import value as value | from cpl_core.console import Console | ||||||
| from cpl_query.extension import List | from cpl_query.extension import List | ||||||
|  |  | ||||||
| from py_to_uxf_core.abc.function_scanner_abc import FunctionScannerABC | 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 import PythonFunction | ||||||
| from py_to_uxf_core.model.python_function_argument import PythonFunctionArgument | 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: |         # 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'): |         if not line.startswith('def ') and not line.startswith('async def '): | ||||||
|             # name |             return None | ||||||
|             name = line.split('def ')[1] |  | ||||||
|             name = name.split('(')[0] |  | ||||||
|             access_modifier = FunctionAccessModifierEnum.public |  | ||||||
|             args = List() |  | ||||||
|             return_type = 'None' |  | ||||||
|  |  | ||||||
|             # access_modifier |         line = line.replace('\t', '') | ||||||
|             if name == '__init__' or name == '__repr__' or name == '__str__': |         # name | ||||||
|                 access_modifier = FunctionAccessModifierEnum.public |         name = line.split('def ')[1] | ||||||
|             elif name.startswith('_'): |         name = name.split('(')[0] | ||||||
|                 access_modifier = FunctionAccessModifierEnum.protected |         access_modifier = AccessModifierEnum.public | ||||||
|             elif name.startswith('__'): |         args = List() | ||||||
|                 access_modifier = FunctionAccessModifierEnum.private |         return_type = 'None' | ||||||
|  |  | ||||||
|             # args |         # access_modifier | ||||||
|             args_str = line.split('(')[1] |         if name == '__init__' or name == '__repr__' or name == '__str__': | ||||||
|             args_str = args_str.split(')')[0] |             access_modifier = AccessModifierEnum.public | ||||||
|             # multiple arguments |         elif name.startswith('_'): | ||||||
|             if ',' in args_str: |             access_modifier = AccessModifierEnum.protected | ||||||
|                 for argument in args_str.split(','): |         elif name.startswith('__'): | ||||||
|                     # with type |             access_modifier = AccessModifierEnum.private | ||||||
|                     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_type |         # args | ||||||
|             if '->' in line: |         args_str = line.split('(')[1] | ||||||
|                 return_type_str = line.split('-> ')[1] |         args_str = args_str.split(')')[0] | ||||||
|                 return_type = return_type_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_type | ||||||
|         return None |         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 typing import Optional | ||||||
|  |  | ||||||
| from cpl_core.console import Console | 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.class_scanner_abc import ClassScannerABC | ||||||
| from py_to_uxf_core.abc.file_scanner_abc import FileScannerABC | 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.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.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 | from py_to_uxf_core.model.python_class import PythonClass | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -15,55 +21,135 @@ class PythonParserService(PythonParserABC): | |||||||
|             self, |             self, | ||||||
|             file_scanner: FileScannerABC, |             file_scanner: FileScannerABC, | ||||||
|             class_scanner: ClassScannerABC, |             class_scanner: ClassScannerABC, | ||||||
|             function_scanner: FunctionScannerABC |             function_scanner: FunctionScannerABC, | ||||||
|  |             attribute_scanner: AttributeScannerABC, | ||||||
|  |             implementation_scanner: ImplementationScannerABC | ||||||
|     ): |     ): | ||||||
|         PythonParserABC.__init__(self) |         PythonParserABC.__init__(self) | ||||||
|  |  | ||||||
|         self._file_scanner = file_scanner |         self._file_scanner = file_scanner | ||||||
|         self._class_scanner = class_scanner |         self._class_scanner = class_scanner | ||||||
|         self._function_scanner = function_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() |         files = self._file_scanner.scan_files() | ||||||
|  |         classes = List(PythonClass) | ||||||
|         for file in files: |         for file in files: | ||||||
|             Console.write_line('\nfi:', file) |  | ||||||
|             is_comment = False |             is_comment = False | ||||||
|  |             is_function = False | ||||||
|             with open(file, 'r') as file_content: |             with open(file, 'r') as file_content: | ||||||
|                 cls: Optional[PythonClass] = None |                 cls: Optional[PythonClass] = None | ||||||
|                 for line in file_content.readlines(): |                 lines = 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', '') |  | ||||||
|  |  | ||||||
|                     if line == '\n' or line == '': |                 for i in range(len(lines)): | ||||||
|                         continue |                     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 == '\n' or line == '': | ||||||
|                     if line != '"""' and line.startswith('"""') and line.endswith('"""') or line.startswith('#'): |                             continue | ||||||
|                         continue |  | ||||||
|  |  | ||||||
|                     # start multi line comment |                         # one line comments | ||||||
|                     if line.startswith('"""') and not is_comment: |                         if line != '"""' and line.startswith('"""') and line.endswith('"""') or line.startswith('#'): | ||||||
|                         is_comment = True |                             continue | ||||||
|                         continue |  | ||||||
|  |  | ||||||
|                     # end multi line comment |                         # start multi line comment | ||||||
|                     if line.startswith('"""') or line.endswith('"""') and is_comment: |                         if line.startswith('"""') or line.count('"""') == 1 and not is_comment: | ||||||
|                         is_comment = False |                             is_comment = True | ||||||
|                         continue |                             continue | ||||||
|  |  | ||||||
|                     if is_comment: |                         # end multi line comment | ||||||
|                         continue |                         if line.startswith('"""') or line.endswith('"""') or line.count('"""') == 1 and is_comment: | ||||||
|  |                             is_comment = False | ||||||
|  |                             continue | ||||||
|  |  | ||||||
|                     if cls is None: |                         if is_comment: | ||||||
|                         cls = self._class_scanner.scan_line_for_classes(line) |                             continue | ||||||
|                         if cls is not None: |  | ||||||
|                             Console.write_line('cl:', cls.name) |  | ||||||
|                         continue |  | ||||||
|  |  | ||||||
|                     func = self._function_scanner.scan_line_for_function(line) |                         if cls is None: | ||||||
|                     if func is not None: |                             cls = self._class_scanner.scan_line_for_classes(line) | ||||||
|                         cls.add_function(func) |                             if cls is not None: | ||||||
|                         Console.write_line('fu:', func.access_modifier.value, func.name, func.args, func.return_type) |                                 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.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): | class UmletCreatorService(UmletCreatorABC): | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self, config: ConfigurationABC): | ||||||
|         UmletCreatorABC.__init__(self) |         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