diff --git a/src/py_to_uxf/application.py b/src/py_to_uxf/application.py index 12c3d3d..ba25583 100644 --- a/src/py_to_uxf/application.py +++ b/src/py_to_uxf/application.py @@ -4,9 +4,11 @@ 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 @@ -58,8 +60,11 @@ class Application(ApplicationABC): Console.write_line(f'Output path:', self._file) xml = '' try: - classes: list[PythonClass] = self._parser.parse() - Console.write_line(f'Found {len(classes)} classes') + 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') + Console.write_line(implementations) # self._console_output(classes) xml = self._umlet_creator.generate_xml(classes) except Exception as e: diff --git a/src/py_to_uxf/startup.py b/src/py_to_uxf/startup.py index e7e4a07..93691c1 100644 --- a/src/py_to_uxf/startup.py +++ b/src/py_to_uxf/startup.py @@ -9,12 +9,14 @@ 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 @@ -38,6 +40,7 @@ class Startup(StartupABC): 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) diff --git a/src/py_to_uxf_core/abc/implementation_scanner_abc.py b/src/py_to_uxf_core/abc/implementation_scanner_abc.py new file mode 100644 index 0000000..0f0c213 --- /dev/null +++ b/src/py_to_uxf_core/abc/implementation_scanner_abc.py @@ -0,0 +1,14 @@ +from abc import ABC, abstractmethod +from typing import Optional + +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]) -> Optional[ClassImplementation]: pass diff --git a/src/py_to_uxf_core/abc/python_parser_abc.py b/src/py_to_uxf_core/abc/python_parser_abc.py index 866cba1..1c7befb 100644 --- a/src/py_to_uxf_core/abc/python_parser_abc.py +++ b/src/py_to_uxf_core/abc/python_parser_abc.py @@ -2,6 +2,7 @@ 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 @@ -11,4 +12,7 @@ class PythonParserABC(ABC): def __init__(self): pass @abstractmethod - def parse(self) -> List[PythonClass]: pass + def parse_classes(self) -> List[PythonClass]: pass + + @abstractmethod + def parse_implementations(self, classes: List[PythonClass]) -> List[ClassImplementation]: pass diff --git a/src/py_to_uxf_core/model/class_implementation.py b/src/py_to_uxf_core/model/class_implementation.py new file mode 100644 index 0000000..428541d --- /dev/null +++ b/src/py_to_uxf_core/model/class_implementation.py @@ -0,0 +1,22 @@ +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 + + @property + def base(self) -> PythonClass: + return self._base + + @property + def subclass(self) -> PythonClass: + return self._subclass + + def __str__(self): + return f'{self._subclass.name} -> {self._base.name}' + + def __repr__(self): + return f'{self._subclass.name} -> {self._base.name}' diff --git a/src/py_to_uxf_core/service/implementation_scanner_service.py b/src/py_to_uxf_core/service/implementation_scanner_service.py new file mode 100644 index 0000000..5483fd5 --- /dev/null +++ b/src/py_to_uxf_core/service/implementation_scanner_service.py @@ -0,0 +1,32 @@ +from typing import Optional + +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.model.class_implementation import ClassImplementation +from py_to_uxf_core.model.python_class import PythonClass + + +class ImplementationScannerService(ImplementationScannerABC): + + def __init__(self): + pass + + def scan_line_for_implementation(self, line: str, classes: List[PythonClass]) -> Optional[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] + sub: Optional[PythonClass] = classes.where(lambda c: c.name == sub_name).first_or_default() + if sub is None: + return None + + base_name = line.split('(')[1].split(')')[0] + base: Optional[PythonClass] = classes.where(lambda c: c.name == base_name).first_or_default() + if base is None: + return None + + return ClassImplementation(base, sub) + + return None diff --git a/src/py_to_uxf_core/service/python_parser_service.py b/src/py_to_uxf_core/service/python_parser_service.py index 73bc283..1fd24bc 100644 --- a/src/py_to_uxf_core/service/python_parser_service.py +++ b/src/py_to_uxf_core/service/python_parser_service.py @@ -8,7 +8,9 @@ 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 @@ -19,7 +21,8 @@ class PythonParserService(PythonParserABC): file_scanner: FileScannerABC, class_scanner: ClassScannerABC, function_scanner: FunctionScannerABC, - attribute_scanner: AttributeScannerABC + attribute_scanner: AttributeScannerABC, + implementation_scanner: ImplementationScannerABC ): PythonParserABC.__init__(self) @@ -27,8 +30,11 @@ class PythonParserService(PythonParserABC): self._class_scanner = class_scanner self._function_scanner = function_scanner self._attribute_scanner = attribute_scanner + self._implementation_scanner = implementation_scanner - def parse(self) -> List[PythonClass]: + 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: @@ -74,6 +80,7 @@ class PythonParserService(PythonParserABC): 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) @@ -90,6 +97,56 @@ class PythonParserService(PythonParserABC): cls.add_attribute(attribute) except Exception as e: Console.error(f'Parsing {file}@{i}', f'{e} -> {traceback.format_exc()}') + file_content.close() 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: + implementations.append(implementation) + except Exception as e: + Console.error(f'Parsing {file}@{i}', f'{e} -> {traceback.format_exc()}') + file_content.close() + exit() + file_content.close() + return implementations