Source code for pycodeanalyzer.core.engine.engine

import os
import time
from typing import Any, Dict, List, Tuple

from injector import inject, singleton

from pycodeanalyzer.core.abstraction.objects import AbstractObject
from pycodeanalyzer.core.analyzer.dependancy import DependancyAnalyser
from pycodeanalyzer.core.analyzer.identification import IdentityAnalyser
from pycodeanalyzer.core.analyzer.search import SearchAnalyser
from pycodeanalyzer.core.configuration.configuration import Configuration
from pycodeanalyzer.core.diagrams.iclassdiagrambuild import IClassDiagramBuild
from pycodeanalyzer.core.diagrams.mermaid import ClassDiagramBuild, PieCharBuild
from pycodeanalyzer.core.diagrams.plantuml import PlantUMLClassDiagramBuild
from pycodeanalyzer.core.filetree.filefetcher import FileFetcher
from pycodeanalyzer.core.json.pickler import Pickler
from pycodeanalyzer.core.languages.filedispatcher import FileDispatcher
from pycodeanalyzer.core.logging.loggerfactory import LoggerFactory
from pycodeanalyzer.core.utils.math import round_up
from pycodeanalyzer.injection import injector
from pycodeanalyzer.ui.app import Application, UiBrowseListener, UiStatListener

app = injector.get(Application)


[docs]class AnalysisStats: """Data class for analysis stats.""" def __init__(self) -> None: self.nbFiles = 0 self.nbClasses = 0 self.nbEnums = 0 self.nbFunctions = 0 self.timeSpent = 0.0 self.languageDispatch: Dict[str, int] = {}
[docs]@singleton class Engine: """Engine of pycodeanalyzer. This class perfom the analysis and then handle commands to show the results. """ @inject def __init__( self, fileFetcher: FileFetcher, fileDispatcher: FileDispatcher, identityAnalyser: IdentityAnalyser, dependancyAnalyser: DependancyAnalyser, searchAnalyser: SearchAnalyser, uiStatListener: UiStatListener, uiBrowseListener: UiBrowseListener, classDiagramBuild: ClassDiagramBuild, pieCharBuild: PieCharBuild, configuration: Configuration, ) -> None: self.fileFetcher = fileFetcher self.fileDispatcher = fileDispatcher self.identityAnalyser = identityAnalyser self.dependancyAnalyser = dependancyAnalyser self.searchAnalyser = searchAnalyser self.uiStatListener = uiStatListener self.uiBrowseListener = uiBrowseListener self.pieCharBuild = pieCharBuild self.classDiagramBuild = classDiagramBuild self.configuration = configuration self.roots: List[Tuple[str, List[str]]] = [] self.nbFiles = 0 self.useByActivated = True self.stats = AnalysisStats()
[docs] def reset(self) -> None: self.roots = [] self.nbFiles = 0 self.stats = AnalysisStats() self.fileFetcher.reset()
[docs] def run(self, args: Any) -> None: self.reset() if args.templatefile: self.configuration.generateTemplate(args.templatefile) exit(0) if args.configfile: if not self.configuration.load(args.configfile): exit(1) if not args.no_ui: app.run() time.sleep(2) self.logger = LoggerFactory.createLogger(__name__) start_time = time.time() self.logger.debug("Setting up analyzers") self.logger.info("Start analysis") self.logger.debug("Fetching files") abstractObjects: List[AbstractObject] = [] for path in args.path: files = self.fileFetcher.fetch(path) self.nbFiles += len(files) self.roots.append((path, files)) if self.nbFiles == 0: self.logger.info("No files to analyze, quitting.") app.quit() else: self.logger.debug("Analysing fetched files") abstractObjects = self.fileDispatcher.dispatchRoots(self.roots) self.identityAnalyser.analyze(abstractObjects) self.logger.info("End analysis") end_time = time.time() self.recordStats(round_up(end_time - start_time, 2)) if args.dumpobj: pickler = Pickler() with open("dumpobj.json", "w") as file: file.write(pickler.encode(abstractObjects)) if args.exportPath: self.doExport(args.exportPath, args.exportFormat)
[docs] def recordStats(self, duration: float) -> None: self.stats.nbFiles = self.nbFiles self.stats.nbClasses = len(self.identityAnalyser.getClasses()) self.stats.nbEnums = len(self.identityAnalyser.getEnums()) self.stats.nbFunctions = len(self.identityAnalyser.getFunctions()) self.stats.timeSpent = duration self.stats.languageDispatch = self.fileFetcher.languagesCount self.logger.info("Stats :") self.logger.info("Files found %d", self.stats.nbFiles) self.logger.info("Classes found %d", self.stats.nbClasses) self.logger.info("Enums found %d", self.stats.nbEnums) self.logger.info("Functions found %d", self.stats.nbFunctions) self.logger.info("languages : %s", str(self.stats.languageDispatch)) self.logger.info("Analysis duration : %s seconds", str(self.stats.timeSpent))
[docs] def sendAnalysisStats(self) -> None: self.pieCharBuild.reset() for label, value in self.stats.languageDispatch.items(): self.pieCharBuild.addValue(label, value) languagePie = self.pieCharBuild.build("Languages") self.logger.debug("Stats sent to UI") self.uiStatListener.notifyStats( self.stats.nbFiles, self.stats.nbClasses, self.stats.nbEnums, self.stats.nbFunctions, languagePie, self.stats.timeSpent, )
[docs] def sendClasseNames(self) -> None: self.logger.debug("Class name tree sent to UI") self.uiBrowseListener.notifyClasseNames(self.identityAnalyser.getClasseTree())
[docs] def sendEnumNames(self) -> None: self.logger.debug("Enum name tree sent to UI") self.uiBrowseListener.notifyEnumNames(self.identityAnalyser.getEnumTree())
[docs] def sendFunctionNames(self) -> None: self.logger.debug("Function name tree sent to UI") self.uiBrowseListener.notifyFunctionNames( self.identityAnalyser.getFunctionTree() )
[docs] def sendFileNames(self) -> None: self.logger.debug("File name tree sent to UI") self.uiBrowseListener.notifyFileNames(self.identityAnalyser.getFileTree())
[docs] def sendClassData(self, className: str) -> None: klass = self.identityAnalyser.getClass(className) if not klass: self.logger.error("Unknow class requested : %s", className) return objects = self.dependancyAnalyser.analyze( self.identityAnalyser.getClasses(), self.identityAnalyser.getEnums(), klass ) if self.useByActivated: usedBy = self.dependancyAnalyser.getUsedBy( self.identityAnalyser.getClasses(), self.identityAnalyser.getEnums(), klass, ) else: usedBy = {} self.classDiagramBuild.reset() self.classDiagramBuild.createClass( objects[0], objects[1], objects[2], objects[3] ) mermaidDiag = self.classDiagramBuild.build() klassDesc: Dict[str, Any] = {} klassDesc["name"] = klass.name klassDesc["namespace"] = klass.namespace klassDesc["file"] = klass.origin klassDesc["parents"] = [] klassDesc["usedBy"] = usedBy for parent in klass.parents: parentKlass = self.dependancyAnalyser.getParent( self.identityAnalyser.getClasses(), klass, parent[0] ) if parentKlass: if parentKlass.namespace: klassDesc["parents"].append( parentKlass.namespace + "::" + parentKlass.name ) else: klassDesc["parents"].append(parentKlass.name) else: klassDesc["parents"].append("$_EXTERNAL_$" + parent[0]) self.logger.debug("Class data sent to UI") self.uiBrowseListener.notifyClassData(klassDesc, mermaidDiag)
[docs] def sendEnumData(self, enumName: str) -> None: enum = self.identityAnalyser.getEnum(enumName) if not enum: self.logger.error("Unknow enum requested : %s", enumName) return self.classDiagramBuild.reset() self.classDiagramBuild.createEnum(enum) mermaidDiag = self.classDiagramBuild.build() if self.useByActivated: usedBy = self.dependancyAnalyser.getUsedBy( self.identityAnalyser.getClasses(), self.identityAnalyser.getEnums(), enum, ) else: usedBy = {} enumDesc: Dict[str, Any] = {} enumDesc["name"] = enum.name enumDesc["namespace"] = enum.namespace enumDesc["file"] = enum.origin enumDesc["usedBy"] = usedBy self.logger.debug("Enum data sent to UI") self.uiBrowseListener.notifyEnumData(enumDesc, mermaidDiag)
[docs] def sendFunctionData(self, functionDef: str) -> None: func = self.identityAnalyser.getFunction(functionDef) if not func: self.logger.error("Unknow function requested : %s", functionDef) return functionDesc: Dict[str, Any] = {} functionDesc["name"] = func.name functionDesc["namespace"] = func.namespace functionDesc["file"] = func.origin functionDesc["rtype"] = func.returnType params = {} if self.useByActivated: usedBy = self.dependancyAnalyser.getUsedBy( self.identityAnalyser.getClasses(), self.identityAnalyser.getEnums(), func, ) else: usedBy = {} functionDesc["usedBy"] = usedBy for param in func.parameters: params[param[1]] = param[0] functionDesc["params"] = params functionDesc["doxygen"] = func.doxygen self.logger.debug("Function data sent to UI") self.uiBrowseListener.notifyFunctionData(functionDesc)
[docs] def sendFileData(self, fileName: str) -> None: filePath = fileName if "::" in fileName or "/" not in fileName: filePath = ( self.identityAnalyser.commonFilePath + "/" + fileName.replace("::", "/") ) if "::" not in fileName: fileName = fileName.replace( self.identityAnalyser.commonFilePath + "/", "" ).replace("/", "::") fileDesc: Dict[str, Any] = {} fileDesc["name"] = fileName fileDesc["path"] = filePath fileDesc["objects"] = self.identityAnalyser.getObjectInFile(filePath) fileDesc["content"] = open(filePath, mode="r").read() self.logger.debug("File data sent to UI") self.uiBrowseListener.notifyFileData(fileDesc)
[docs] def sendSearchResult(self, token: str) -> None: searchRes = self.searchAnalyser.searchInAllFiles( token, self.identityAnalyser.getFiles() ) self.uiBrowseListener.notifySearchResult(searchRes)
[docs] def doExport(self, exportPath: str, exportFormat: str) -> None: builder: IClassDiagramBuild = self.classDiagramBuild extension = ".mmd" if exportFormat == "plantuml": builder = PlantUMLClassDiagramBuild() extension = ".plantuml" self.logger.info("Exporting diagrams in %s format", exportFormat) if not os.path.isdir(exportPath): if not os.path.isfile(exportPath): os.makedirs(exportPath) else: self.logger.error( "Export path %s exist and is not a directory. Abort export.", exportPath, ) return for klass in self.identityAnalyser.getClasses(): objects = self.dependancyAnalyser.analyze( self.identityAnalyser.getClasses(), self.identityAnalyser.getEnums(), klass, ) builder.reset() builder.createClass(objects[0], objects[1], objects[2], objects[3]) diag = builder.build() classFileName = klass.getFullName().replace("::", "_") + extension exportedFile = os.path.join(exportPath, classFileName) self.logger.info("Exporting %s", klass.getFullName()) with open(exportedFile, "w", encoding="utf-8") as diagFile: diagFile.write(diag) for enum in self.identityAnalyser.getEnums(): builder.reset() builder.createEnum(enum) diag = builder.build() enumFileName = enum.getFullName().replace("::", "_") + extension exportedFile = os.path.join(exportPath, enumFileName) self.logger.info("Exporting %s", enum.getFullName()) with open(exportedFile, "w", encoding="utf-8") as diagFile: diagFile.write(diag)
[docs] def setUsedByActivation(self, activated: bool) -> None: self.useByActivated = activated
[docs] def requestUsedByUse(self) -> None: self.uiBrowseListener.notifyUsedByUse(self.useByActivated)