From 61b1838c40bd4eca84e7105d823f606e64a2b889 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sun, 16 Jan 2022 14:56:02 +0100 Subject: [PATCH] Added width and height rendering --- src/py_to_uxf/application.py | 16 +++- src/py_to_uxf_core/model/python_class.py | 4 +- src/py_to_uxf_core/model/uml_class.py | 25 +++++- .../service/function_scanner_service.py | 82 +++++++++--------- .../service/python_parser_service.py | 86 ++++++++++--------- .../service/umlet_creator_service.py | 9 +- 6 files changed, 131 insertions(+), 91 deletions(-) diff --git a/src/py_to_uxf/application.py b/src/py_to_uxf/application.py index 827b04d..12c3d3d 100644 --- a/src/py_to_uxf/application.py +++ b/src/py_to_uxf/application.py @@ -1,3 +1,5 @@ +import traceback + from cpl_core.application import ApplicationABC from cpl_core.configuration import ConfigurationABC from cpl_core.console import Console @@ -54,13 +56,19 @@ class Application(ApplicationABC): Console.write_line(f'Input path:', self._path) Console.write_line(f'Output path:', self._file) - classes: list[PythonClass] = self._parser.parse() - # self._console_output(classes) - xml = self._umlet_creator.generate_xml(classes) + xml = '' + try: + classes: list[PythonClass] = self._parser.parse() + Console.write_line(f'Found {len(classes)} classes') + # self._console_output(classes) + xml = self._umlet_creator.generate_xml(classes) + except Exception as e: + Console.error('Parsing failed', f'{e} -> {traceback.format_exc()}') + exit() if not self._file.endswith('.uxf'): Console.error(f'Unexpected file {self._file}') - return + exit() with open(self._file, 'w+') as file: file.write(xml) diff --git a/src/py_to_uxf_core/model/python_class.py b/src/py_to_uxf_core/model/python_class.py index 76ac337..8463088 100644 --- a/src/py_to_uxf_core/model/python_class.py +++ b/src/py_to_uxf_core/model/python_class.py @@ -16,11 +16,11 @@ class PythonClass: return self._name @property - def functions(self) -> list[PythonFunction]: + def functions(self) -> List[PythonFunction]: return self._functions @property - def attributes(self) -> list[PythonClassAttribute]: + def attributes(self) -> List[PythonClassAttribute]: return self._attributes def add_function(self, func: PythonFunction): diff --git a/src/py_to_uxf_core/model/uml_class.py b/src/py_to_uxf_core/model/uml_class.py index aa164e9..bd72e12 100644 --- a/src/py_to_uxf_core/model/uml_class.py +++ b/src/py_to_uxf_core/model/uml_class.py @@ -27,22 +27,43 @@ class UMLClass: 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 + def as_xml(self) -> str: + px_per_line = 16 + px_per_char = 2.9 + self._dimension.height += (self._cls.attributes.count() + self._cls.functions.count()) * px_per_line + longest_line_length = self._dimension.width / px_per_char + attributes = '' functions = '' if len(self._cls.attributes) > 0: for atr in self._cls.attributes: - attributes += f'{atr.access_modifier.value}{atr.name}: {atr.type}\n' + attribute = f'{atr.access_modifier.value}{atr.name}: {atr.type}\n' + if len(attribute) > longest_line_length: + longest_line_length = len(attribute) + attributes += attribute if len(self._cls.functions) > 0: for func in self._cls.functions: args = '' - functions += f'{func.access_modifier.value}{func.name}({args}): {func.return_type}\n' + function = f'{func.access_modifier.value}{func.name}({args}): {func.return_type}\n' + if len(function) > longest_line_length: + longest_line_length = len(function) + functions += function + + self._dimension.width = round(longest_line_length * px_per_char * px_per_char) return f"""\ diff --git a/src/py_to_uxf_core/service/function_scanner_service.py b/src/py_to_uxf_core/service/function_scanner_service.py index f3f06ad..a4d86b2 100644 --- a/src/py_to_uxf_core/service/function_scanner_service.py +++ b/src/py_to_uxf_core/service/function_scanner_service.py @@ -1,5 +1,6 @@ from typing import Optional +from cpl_core.console import Console from cpl_query.extension import List from py_to_uxf_core.abc.function_scanner_abc import FunctionScannerABC @@ -20,47 +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'): - line = line.replace('\t', '') - # name - name = line.split('def ')[1] - name = name.split('(')[0] + if not line.startswith('def ') and not line.startswith('async def '): + return None + + line = line.replace('\t', '') + # name + name = line.split('def ')[1] + name = name.split('(')[0] + access_modifier = AccessModifierEnum.public + args = List() + return_type = 'None' + + # access_modifier + if name == '__init__' or name == '__repr__' or name == '__str__': access_modifier = AccessModifierEnum.public - args = List() - return_type = 'None' + elif name.startswith('_'): + access_modifier = AccessModifierEnum.protected + elif name.startswith('__'): + access_modifier = AccessModifierEnum.private - # 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 + # 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 - # 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_type + if '->' in line: + return_type_str = line.split('-> ')[1] + return_type = return_type_str.split(':')[0] - # 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) - return None + return PythonFunction(access_modifier, name, args, return_type) 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 78dd7ab..73bc283 100644 --- a/src/py_to_uxf_core/service/python_parser_service.py +++ b/src/py_to_uxf_core/service/python_parser_service.py @@ -1,3 +1,4 @@ +import traceback from typing import Optional from cpl_core.console import Console @@ -35,53 +36,60 @@ class PythonParserService(PythonParserABC): is_function = False with open(file, 'r') as file_content: cls: Optional[PythonClass] = None - for line in file_content.readlines(): - line_with_tabs = line - 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 == '': - if is_function: - is_function = False - continue + for i in range(len(lines)): + try: + line = lines[i] + line_with_tabs = line + 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 == '': + if is_function: + is_function = False + continue - # start multi line comment - if line.startswith('"""') or line.count('"""') == 1 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('"""') or line.count('"""') == 1 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: - classes.append(cls) - continue + if is_comment: + 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 cls is None: + cls = self._class_scanner.scan_line_for_classes(line) + if cls is not None: + classes.append(cls) + continue - if len(line_with_tabs.split(' ')) > 3 or is_function: - 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 - attribute = self._attribute_scanner.scan_line_for_attribute(line) - if attribute is not None: - cls.add_attribute(attribute) + if len(line_with_tabs.split(' ')) > 3 or 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()}') + exit() return classes diff --git a/src/py_to_uxf_core/service/umlet_creator_service.py b/src/py_to_uxf_core/service/umlet_creator_service.py index 4dc13da..3b81449 100644 --- a/src/py_to_uxf_core/service/umlet_creator_service.py +++ b/src/py_to_uxf_core/service/umlet_creator_service.py @@ -16,14 +16,15 @@ class UmletCreatorService(UmletCreatorABC): def generate_xml(self, classes: list[PythonClass]) -> str: xml_cls = '' - width = 400 - height = 300 + default_width = 80 + default_height = 80 next_x = 10 for cls in classes: - uml_cls = UMLClass(cls, Position(next_x, 10), Dimension(width, height)) + uml_cls = UMLClass(cls, Position(next_x, 10), Dimension(default_width, default_height)) + # save class xml xml_cls += uml_cls.as_xml() - next_x += width + 10 + next_x += round(uml_cls.dimension.width, -1) + 10 return f"""\