def reversePython(self, umlFrame: UmlClassDiagramsFrame, directoryName: str, files: List[str]): """ Reverse engineering Python files to OglClass's Args: umlFrame: The uml frame to display on directoryName: The directory name where the selected files reside files: A list of files to parse """ fileCount: int = len(files) dlg = ProgressDialog('Parsing Files', 'Starting', parent=umlFrame, style=PD_APP_MODAL | PD_ELAPSED_TIME) dlg.SetRange(fileCount) currentFileCount: int = 0 onGoingParents: Parents = Parents({}) for fileName in files: try: fqFileName: str = f'{directoryName}{osSep}{fileName}' self.logger.info(f'Processing file: {fqFileName}') dlg.Update(currentFileCount, f'Processing: {fileName}') fileStream: FileStream = FileStream(fqFileName) lexer: Python3Lexer = Python3Lexer(fileStream) stream: CommonTokenStream = CommonTokenStream(lexer) parser: Python3Parser = Python3Parser(stream) tree: Python3Parser.File_inputContext = parser.file_input() if parser.getNumberOfSyntaxErrors() != 0: self.logger.error( f"File {fileName} contains {parser.getNumberOfSyntaxErrors()} syntax errors" ) # TODO: Put up a dialog continue self.visitor = PyutPythonVisitor() self.visitor.parents = onGoingParents self.visitor.visit(tree) self._generatePyutClasses() onGoingParents = self.visitor.parents currentFileCount += 1 except (ValueError, Exception) as e: from org.pyut.errorcontroller.ErrorManager import ErrorManager eMsg: str = f'file: {fileName}\n{e} - {ErrorManager.getErrorInfo()}' self.logger.error(eMsg) dlg.Destroy() raise PythonParseException(eMsg) dlg.Destroy() self._generateOglClasses(umlFrame) self._layoutUmlClasses(umlFrame) self._generateInheritanceLinks(umlFrame)
def testRetrieveMethods(self): tree: Python3Parser.File_inputContext = self._setupVisitor('Vertex.py') visitor: PyutPythonVisitor = PyutPythonVisitor() visitor.visit(tree) expectedNumberOfFields: int = 3 self.assertEqual(expectedNumberOfFields, len(visitor.fields), 'Not enough fields')
def testRetrieveCodeFromMethods(self): tree: Python3Parser.File_inputContext = self._setupVisitor('Vertex.py') visitor: PyutPythonVisitor = PyutPythonVisitor() visitor.visit(tree) expectedNumberOfMethodsWithCode: int = 3 self.assertEqual(expectedNumberOfMethodsWithCode, len(visitor.methodCode), 'Not enough code')
def testNonPropertyDecorator(self): tree: Python3Parser.File_inputContext = self._setupVisitor( 'NonPropertyDecoratorClass.py') visitor: PyutPythonVisitor = PyutPythonVisitor() visitor.visit(tree) actualNumPops: int = len(visitor.propertyNames) self.assertEqual(0, actualNumPops, 'There should be no properties in the test class')
def testMultiClassFileWithInheritance(self): tree: Python3Parser.File_inputContext = self._setupVisitor('Opie.py') visitor: PyutPythonVisitor = PyutPythonVisitor() visitor.visit(tree) expectedParentName: str = 'Cat' expectedChildName: str = 'Opie' self.assertTrue(expectedParentName in visitor.parents, 'Missing parent') actualChildName: str = visitor.parents[expectedParentName][0] self.assertEqual(expectedChildName, actualChildName, 'Missing child')
def testOnlyInitHasParameters(self): tree: Python3Parser.File_inputContext = self._setupVisitor('Vertex.py') visitor: PyutPythonVisitor = PyutPythonVisitor() visitor.visit(tree) self.assertTrue('Vertex' in visitor.classMethods, 'Oops, I am missing my class key') self.assertTrue('__init__' in visitor.parameters, 'I am missing a method') self.assertFalse('surround_faces' in visitor.parameters, 'I am missing a method') self.assertFalse('surround_half_edges' in visitor.parameters, 'I am missing a method')
def testDecoratedProperty(self): tree: Python3Parser.File_inputContext = self._setupVisitor( 'ClassWithProperties.py') visitor: PyutPythonVisitor = PyutPythonVisitor() visitor.visit(tree) self.logger.info(f'{visitor.classMethods=}') self.logger.info(f'{visitor.propertyNames=}') self.logger.info(f'{visitor.setterProperties=}') self.logger.info(f'{visitor.getterProperties=}') self.assertTrue(len(visitor.propertyNames) == 2) self.assertTrue(len(visitor.setterProperties) == 2) self.assertTrue(len(visitor.getterProperties) == 2)
def testInheritanceMultiParentMultiChildren(self): tree: Python3Parser.File_inputContext = self._setupVisitor( 'DeepInheritance.py') visitor: PyutPythonVisitor = PyutPythonVisitor() visitor.visit(tree) expectedParentName1: str = 'ParentClass1' expectedParentName2: str = 'ParentClass2' self.assertEqual(len(visitor.parents), 2, 'Incorrect parent count') self.assertTrue(expectedParentName1 in visitor.parents, f'Missing parent: {expectedParentName1}') self.assertTrue(expectedParentName2 in visitor.parents, f'Missing parent: {expectedParentName2}') parent1Children: List[str] = visitor.parents[expectedParentName1] expectedParent1Child1: str = 'ChildClass1' expectedParent1Child2: str = 'ChildClass2' self.assertTrue( expectedParent1Child1 in parent1Children, f'Missing child: {expectedParent1Child1} of parent {expectedParentName1}' ) self.assertTrue( expectedParent1Child2 in parent1Children, f'Missing child: {expectedParent1Child2} of parent {expectedParentName1}' ) parent2Children: List[str] = visitor.parents[expectedParentName2] expectedParent2Child3: str = 'ChildClass3' expectedParent2Child4: str = 'ChildClass4' self.assertTrue( expectedParent2Child3 in parent2Children, f'Missing child: {expectedParent2Child3} of parent {expectedParentName2}' ) self.assertTrue( expectedParent2Child4 in parent2Children, f'Missing child: {expectedParent2Child4} of parent {expectedParentName2}' )
def testLargeClassWithMethodsThatHaveParameters(self): tree: Python3Parser.File_inputContext = self._setupVisitor( 'GMLExporter.py') visitor: PyutPythonVisitor = PyutPythonVisitor() visitor.visit(tree) self.assertTrue('GMLExporter' in visitor.classMethods, 'Oops, I am missing my class key') self.assertTrue('translate' in visitor.parameters, 'I am missing a method - translate') # self.assertTrue('prettyPrint' in visitor.parameters, 'I am missing a method - prettyPrint') # this is a property self.assertTrue('write' in visitor.parameters, 'I am missing a method - write') self.assertTrue( '_generateNodeGraphicsSection' in visitor.parameters, 'I am missing a method - _generateNodeGraphicsSection') self.assertTrue('__generatePoint' in visitor.parameters, 'I am missing a method - __generatePoint')
def testDataClass(self): tree: Python3Parser.File_inputContext = self._setupVisitor( 'DataTestClass.py') visitor: PyutPythonVisitor = PyutPythonVisitor() visitor.visit(tree) self.logger.info(f'{visitor.dataClassNames}') self.assertTrue('DataTestClass' in visitor.dataClassNames, 'Bad property count') actualNumProps: int = len(visitor.dataClassProperties) self.assertEqual( TestPyutPythonVisitor.EXPECTED_DATA_CLASS_PROPERTY_COUNT, actualNumProps, f'There should be {TestPyutPythonVisitor.EXPECTED_DATA_CLASS_PROPERTY_COUNT} properties in the test class' ) for dPropertyTuple in visitor.dataClassProperties: actualProp: str = dPropertyTuple[1] self.assertTrue(actualProp in self.EXPECTED_DATA_CLASS_PROPERTIES, 'Missing property')
class ReverseEngineerPython2: PyutClassName = str ClassName = str PyutClasses = Dict[PyutClassName, PyutClass] OglClasses = Dict[ClassName, OglClass] PYTHON_ASSIGNMENT: str = '=' PYTHON_TYPE_DELIMITER: str = ':' PYTHON_EOL_COMMENT: str = '#' def __init__(self): self.logger: Logger = getLogger(__name__) self.visitor: PyutPythonVisitor = cast(PyutPythonVisitor, None) self._pyutClasses: ReverseEngineerPython2.PyutClasses = {} self._oglClasses: ReverseEngineerPython2.OglClasses = {} def reversePython(self, umlFrame: UmlClassDiagramsFrame, directoryName: str, files: List[str]): """ Reverse engineering Python files to OglClass's Args: umlFrame: The uml frame to display on directoryName: The directory name where the selected files reside files: A list of files to parse """ fileCount: int = len(files) dlg = ProgressDialog('Parsing Files', 'Starting', parent=umlFrame, style=PD_APP_MODAL | PD_ELAPSED_TIME) dlg.SetRange(fileCount) currentFileCount: int = 0 onGoingParents: PyutPythonVisitor.Parents = {} for fileName in files: try: fqFileName: str = f'{directoryName}{osSep}{fileName}' self.logger.info(f'Processing file: {fqFileName}') dlg.Update(currentFileCount, f'Processing: {fileName}') fileStream: FileStream = FileStream(fqFileName) lexer: Python3Lexer = Python3Lexer(fileStream) stream: CommonTokenStream = CommonTokenStream(lexer) parser: Python3Parser = Python3Parser(stream) tree: Python3Parser.File_inputContext = parser.file_input() if parser.getNumberOfSyntaxErrors() != 0: self.logger.error(f"File {fileName} contains {parser.getNumberOfSyntaxErrors()} syntax errors") # TODO: Put up a dialog continue self.visitor = PyutPythonVisitor() self.visitor.parents = onGoingParents self.visitor.visit(tree) self._generatePyutClasses() onGoingParents = self.visitor.parents currentFileCount += 1 except (ValueError, Exception) as e: from org.pyut.errorcontroller.ErrorManager import ErrorManager eMsg: str = f'file: {fileName}\n{e} - {ErrorManager.getErrorInfo()}' self.logger.error(eMsg) dlg.Destroy() raise PythonParseException(eMsg) dlg.Destroy() self._generateOglClasses(umlFrame) self._layoutUmlClasses(umlFrame) self._generateInheritanceLinks(umlFrame) def _generatePyutClasses(self): for className in self._classNames(): pyutClass: PyutClass = PyutClass(name=className) pyutClass = self._addFields(pyutClass) for methodName in self._methodNames(className): pyutMethod: PyutMethod = PyutMethod(name=methodName) if methodName[0:2] == "__": pyutMethod.setVisibility(PyutVisibilityEnum.PRIVATE) elif methodName[0] == "_": pyutMethod.setVisibility(PyutVisibilityEnum.PROTECTED) else: pyutMethod.setVisibility(PyutVisibilityEnum.PUBLIC) pyutMethod = self._addParameters(pyutMethod) pyutMethod.sourceCode = self.visitor.methodCode[methodName] pyutClass.addMethod(pyutMethod) setterProperties: PyutPythonVisitor.Parameters = self.visitor.setterProperties getterProperties: PyutPythonVisitor.Parameters = self.visitor.getterProperties for propName in self.visitor.propertyNames: setterParams: List[str] = setterProperties[propName] getterParams: List[str] = getterProperties[propName] self.logger.info(f'Processing - {propName=} {setterParams=} {getterParams=}') setter, getter = self._createProperties(propName=propName, setterParams=setterParams) pyutClass.addMethod(setter) pyutClass.addMethod(getter) self._pyutClasses[className] = pyutClass self.logger.info(f'Generated {len(self._pyutClasses)} classes') def _createProperties(self, propName: str, setterParams: List[str]) -> Tuple[PyutMethod, PyutMethod]: setter: PyutMethod = PyutMethod(name=propName, visibility=PyutVisibilityEnum.PUBLIC) getter: PyutMethod = PyutMethod(name=propName, visibility=PyutVisibilityEnum.PUBLIC) nameType: str = setterParams[0] potentialNameType: List[str] = nameType.split(':') if len(potentialNameType) == 2: param: PyutParam = PyutParam(name=potentialNameType[0], theParameterType=PyutType(value=potentialNameType[1])) setter.addParam(param) getter.returnType = PyutType(value=potentialNameType[1]) else: param: PyutParam = PyutParam(name=potentialNameType[0]) setter.addParam(param) return setter, getter def _addParameters(self, pyutMethod: PyutMethod) -> PyutMethod: methodName: str = pyutMethod.name if methodName in self.visitor.parameters: parameters: PyutPythonVisitor.Parameters = self.visitor.parameters[methodName] for parameter in parameters: self.logger.debug(f'parameter: {parameter}') paramNameType = parameter.split(':') # # TODO: account for default values # if len(paramNameType) == 2: # Somebody is good and did typing pyutType: PyutType = PyutType(paramNameType[1]) pyutParam: PyutParam = PyutParam(name=paramNameType[0], theParameterType=pyutType) pyutMethod.addParam(pyutParam) return pyutMethod def _addFields(self, pyutClass: PyutClass) -> PyutClass: """ Can look like this: fieldData: x:int=0 fieldData = 0 Args: pyutClass: Where to add the fields Returns: The updated input class """ for fieldData in self.visitor.fields: self.logger.debug(f'fieldData: {fieldData}') pyutField: PyutField = self._parseFieldToPyut(fieldData) pyutClass.addField(pyutField) return pyutClass def _generateOglClasses(self, umlFrame: UmlClassDiagramsFrame): for pyutClassName in self._pyutClasses: try: pyutClass: PyutClass = self._pyutClasses[pyutClassName] oglClass: OglClass = OglClass(pyutClass) umlFrame.addShape(oglClass, 0, 0) oglClass.autoResize() self._oglClasses[pyutClassName] = oglClass except (ValueError, Exception) as e: self.logger.error(f"Error while creating class {pyutClassName}, {e}") def _generateInheritanceLinks(self, umlFrame: UmlClassDiagramsFrame): parents: PyutPythonVisitor.Parents = self.visitor.parents for parentName in parents.keys(): children: PyutPythonVisitor.Children = parents[parentName] for childName in children: try: parentOglClass: OglClass = self._oglClasses[parentName] childOglClass: OglClass = self._oglClasses[childName] self.__createInheritanceLink(child=childOglClass, parent=parentOglClass, umlFrame=umlFrame) except KeyError as ke: # Probably there is no parent we are tracking self.logger.error(f'Apparently we are not tracking this parent: {ke}') continue def _methodNames(self, className: str) -> List[str]: methodNames: List[str] = [] try: methodNames = self.visitor.classMethods[className] except KeyError: pass # A class with no methods ?? return methodNames def _classNames(self) -> List[str]: return self.visitor.classNames def _layoutUmlClasses(self, umlFrame: UmlClassDiagramsFrame): """ Organize by vertical descending sizes Args: umlFrame: """ oglClasses: List[OglClass] = list(self._oglClasses.values()) # Sort by descending height sortedOglClasses = sorted(oglClasses, key=lambda oglClassToSort: oglClassToSort._height, reverse=True) x: int = 20 y: int = 20 incY: int = 0 for oglClass in sortedOglClasses: incX, sy = oglClass.GetSize() incX += 20 sy += 20 incY = max(incY, int(sy)) # find good coordinates if x + incX >= umlFrame.maxWidth: x = 20 y += incY incY = int(sy) oglClass.SetPosition(x, y) x += incX def _parseFieldToPyut(self, fieldData: str) -> PyutField: self.logger.debug(f'fieldData: {fieldData}') if ReverseEngineerPython2.PYTHON_TYPE_DELIMITER in fieldData and ReverseEngineerPython2.PYTHON_ASSIGNMENT in fieldData: pyutField: PyutField = self.__complexParseFieldToPyut(fieldData) else: pyutField: PyutField = self.__simpleParseFieldToPyut(fieldData) return pyutField def __simpleParseFieldToPyut(self, fieldData: str) -> PyutField: pyutField: PyutField = PyutField() noCommentFieldData: str = self.__stripEndOfLineComment(fieldData) fieldAndValue: List[str] = noCommentFieldData.split(ReverseEngineerPython2.PYTHON_ASSIGNMENT) pyutField.name = fieldAndValue[0].strip() pyutField.defaultValue = fieldAndValue[1].strip() return pyutField def __complexParseFieldToPyut(self, fieldData: str) -> PyutField: """ Can look like this: fieldData: x:int=0 Args: fieldData: Returns: """ noCommentFieldData: str = self.__stripEndOfLineComment(fieldData) fieldAndType: List[str] = noCommentFieldData.split(ReverseEngineerPython2.PYTHON_TYPE_DELIMITER) fieldName: str = fieldAndType[0] vis: PyutVisibilityEnum = self.__determineFieldVisibility(fieldName) fieldName = self.__appropriatelyCleanupName(vis=vis, fieldName=fieldName) pyutField: PyutField = PyutField(name=fieldName, visibility=vis) if len(fieldAndType) > 1: typeAndDefaultValue: List[str] = fieldAndType[1].split(ReverseEngineerPython2.PYTHON_ASSIGNMENT) pyutType: PyutType = PyutType(value=typeAndDefaultValue[0].strip()) pyutField.setType(theType=pyutType) if len(typeAndDefaultValue) > 1: pyutField.setDefaultValue(typeAndDefaultValue[1].strip()) return pyutField def __determineFieldVisibility(self, name: str) -> PyutVisibilityEnum: vis: PyutVisibilityEnum = PyutVisibilityEnum.PUBLIC if len(name) > 1: if name[-2:] != "__": if name[0:2] == "__": vis: PyutVisibilityEnum = PyutVisibilityEnum.PRIVATE elif name[0] == "_": vis: PyutVisibilityEnum = PyutVisibilityEnum.PROTECTED return vis def __appropriatelyCleanupName(self, vis: PyutVisibilityEnum, fieldName: str) -> str: if vis == PyutVisibilityEnum.PUBLIC: return fieldName elif vis == PyutVisibilityEnum.PRIVATE: fieldName = fieldName[2:] return fieldName else: fieldName = fieldName[1:] return fieldName def __stripEndOfLineComment(self, fieldData: str) -> str: fieldAndComment: List[str] = fieldData.split(ReverseEngineerPython2.PYTHON_EOL_COMMENT) return fieldAndComment[0] def __createInheritanceLink(self, child: OglClass, parent: OglClass, umlFrame: UmlClassDiagramsFrame): """ Add a paternity link between child and father. Args: child: A child parent: The daddy!! Returns: an OgLink """ cmdGroup: CommandGroup = CommandGroup('Creating an inheritance link') # inheritance points back to parent cmd: CreateOglLinkCommand = CreateOglLinkCommand(src=child, dst=parent) cmdGroup.addCommand(cmd) historyManager: HistoryManager = umlFrame.getHistory() historyManager.addCommandGroup(cmdGroup) cmd.execute()