src/eric7/Graphics/PackageDiagramBuilder.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9278
36448ca469c2
--- a/src/eric7/Graphics/PackageDiagramBuilder.py	Wed Jul 13 11:16:20 2022 +0200
+++ b/src/eric7/Graphics/PackageDiagramBuilder.py	Wed Jul 13 14:55:47 2022 +0200
@@ -26,10 +26,11 @@
     """
     Class implementing a builder for UML like class diagrams of a package.
     """
+
     def __init__(self, dialog, view, project, package, noAttrs=False):
         """
         Constructor
-        
+
         @param dialog reference to the UML dialog
         @type UMLDialog
         @param view reference to the view object
@@ -43,16 +44,16 @@
         """
         super().__init__(dialog, view, project)
         self.setObjectName("PackageDiagram")
-        
+
         self.package = os.path.abspath(package)
         self.noAttrs = noAttrs
-        
+
         self.__relPackage = (
             self.project.getRelativePath(self.package)
-            if self.project.isProjectSource(self.package) else
-            ""
+            if self.project.isProjectSource(self.package)
+            else ""
         )
-    
+
     def initialize(self):
         """
         Public method to initialize the object.
@@ -60,57 +61,57 @@
         pname = self.project.getProjectName()
         name = (
             self.tr("Package Diagram {0}: {1}").format(
-                pname, self.project.getRelativePath(self.package))
-            if pname else
-            self.tr("Package Diagram: {0}").format(self.package)
+                pname, self.project.getRelativePath(self.package)
+            )
+            if pname
+            else self.tr("Package Diagram: {0}").format(self.package)
         )
         self.umlView.setDiagramName(name)
-    
+
     def __getCurrentShape(self, name):
         """
         Private method to get the named shape.
-        
+
         @param name name of the shape
         @type str
         @return shape
         @rtype QCanvasItem
         """
         return self.allClasses.get(name)
-    
+
     def __buildModulesDict(self):
         """
         Private method to build a dictionary of modules contained in the
         package.
-        
+
         @return dictionary of modules contained in the package
         @rtype dict
         """
         import Utilities.ModuleParser
-        
-        supportedExt = (
-            ['*{0}'.format(ext) for ext in
-             Preferences.getPython("Python3Extensions")] +
-            ['*.rb']
-        )
-        extensions = (
-            Preferences.getPython("Python3Extensions") +
-            ['.rb']
-        )
-        
+
+        supportedExt = [
+            "*{0}".format(ext) for ext in Preferences.getPython("Python3Extensions")
+        ] + ["*.rb"]
+        extensions = Preferences.getPython("Python3Extensions") + [".rb"]
+
         moduleDict = {}
         modules = []
         for ext in supportedExt:
-            modules.extend(glob.glob(
-                Utilities.normjoinpath(self.package, ext)))
+            modules.extend(glob.glob(Utilities.normjoinpath(self.package, ext)))
         tot = len(modules)
         progress = EricProgressDialog(
             self.tr("Parsing modules..."),
-            None, 0, tot, self.tr("%v/%m Modules"), self.parent())
+            None,
+            0,
+            tot,
+            self.tr("%v/%m Modules"),
+            self.parent(),
+        )
         progress.setWindowTitle(self.tr("Package Diagram"))
         try:
             progress.show()
             QApplication.processEvents()
-            
+
             now = time.monotonic()
             for prog, module in enumerate(modules):
                 progress.setValue(prog)
@@ -119,73 +120,71 @@
                     now = time.monotonic()
                 try:
                     mod = Utilities.ModuleParser.readModule(
-                        module, extensions=extensions, caching=False)
+                        module, extensions=extensions, caching=False
+                    )
                 except ImportError:
                     continue
                 else:
                     name = mod.name
                     if name.startswith(self.package):
-                        name = name[len(self.package) + 1:]
+                        name = name[len(self.package) + 1 :]
                     moduleDict[name] = mod
         finally:
             progress.setValue(tot)
             progress.deleteLater()
         return moduleDict
-    
+
     def __buildSubpackagesDict(self):
         """
         Private method to build a dictionary of sub-packages contained in this
         package.
-        
+
         @return dictionary of sub-packages contained in this package
         @rtype dict
         """
         import Utilities.ModuleParser
-        
-        supportedExt = (
-            ['*{0}'.format(ext) for ext in
-             Preferences.getPython("Python3Extensions")] +
-            ['*.rb']
-        )
-        extensions = (
-            Preferences.getPython("Python3Extensions") +
-            ['.rb']
-        )
-        
+
+        supportedExt = [
+            "*{0}".format(ext) for ext in Preferences.getPython("Python3Extensions")
+        ] + ["*.rb"]
+        extensions = Preferences.getPython("Python3Extensions") + [".rb"]
+
         subpackagesDict = {}
         subpackagesList = []
-        
+
         for subpackage in os.listdir(self.package):
             subpackagePath = os.path.join(self.package, subpackage)
             if (
-                os.path.isdir(subpackagePath) and
-                subpackage != "__pycache__" and
-                len(glob.glob(
-                    os.path.join(subpackagePath, "__init__.*")
-                )) != 0
+                os.path.isdir(subpackagePath)
+                and subpackage != "__pycache__"
+                and len(glob.glob(os.path.join(subpackagePath, "__init__.*"))) != 0
             ):
                 subpackagesList.append(subpackagePath)
-        
+
         tot = 0
         for ext in supportedExt:
             for subpackage in subpackagesList:
                 tot += len(glob.glob(Utilities.normjoinpath(subpackage, ext)))
         progress = EricProgressDialog(
             self.tr("Parsing modules..."),
-            None, 0, tot, self.tr("%v/%m Modules"), self.parent())
+            None,
+            0,
+            tot,
+            self.tr("%v/%m Modules"),
+            self.parent(),
+        )
         progress.setWindowTitle(self.tr("Package Diagram"))
         try:
             progress.show()
             QApplication.processEvents()
-            
+
             now = time.monotonic()
             for subpackage in subpackagesList:
                 packageName = os.path.basename(subpackage)
                 subpackagesDict[packageName] = []
                 modules = []
                 for ext in supportedExt:
-                    modules.extend(glob.glob(
-                        Utilities.normjoinpath(subpackage, ext)))
+                    modules.extend(glob.glob(Utilities.normjoinpath(subpackage, ext)))
                 for prog, module in enumerate(modules):
                     progress.setValue(prog)
                     if time.monotonic() - now > 0.01:
@@ -193,7 +192,8 @@
                         now = time.monotonic()
                     try:
                         mod = Utilities.ModuleParser.readModule(
-                            module, extensions=extensions, caching=False)
+                            module, extensions=extensions, caching=False
+                        )
                     except ImportError:
                         continue
                     else:
@@ -210,39 +210,45 @@
             progress.setValue(tot)
             progress.deleteLater()
         return subpackagesDict
-    
+
     def buildDiagram(self):
         """
         Public method to build the class shapes of the package diagram.
-        
+
         The algorithm is borrowed from Boa Constructor.
         """
         self.allClasses = {}
-        
-        initlist = glob.glob(os.path.join(self.package, '__init__.*'))
+
+        initlist = glob.glob(os.path.join(self.package, "__init__.*"))
         if len(initlist) == 0:
             ct = QGraphicsTextItem(None)
             self.scene.addItem(ct)
             ct.setHtml(
-                self.tr("The directory <b>'{0}'</b> is not a package.")
-                    .format(self.package))
+                self.tr("The directory <b>'{0}'</b> is not a package.").format(
+                    self.package
+                )
+            )
             return
-        
+
         modules = self.__buildModulesDict()
         subpackages = self.__buildSubpackagesDict()
-        
+
         if not modules and not subpackages:
             ct = QGraphicsTextItem(None)
             self.scene.addItem(ct)
-            ct.setHtml(self.buildErrorMessage(
-                self.tr("The package <b>'{0}'</b> does not contain any modules"
-                        " or subpackages.").format(self.package)
-            ))
+            ct.setHtml(
+                self.buildErrorMessage(
+                    self.tr(
+                        "The package <b>'{0}'</b> does not contain any modules"
+                        " or subpackages."
+                    ).format(self.package)
+                )
+            )
             return
-            
+
         # step 1: build all classes found in the modules
         classesFound = False
-        
+
         for modName in list(modules.keys()):
             module = modules[modName]
             for cls in list(module.classes.keys()):
@@ -251,16 +257,20 @@
         if not classesFound and not subpackages:
             ct = QGraphicsTextItem(None)
             self.scene.addItem(ct)
-            ct.setHtml(self.buildErrorMessage(
-                self.tr("The package <b>'{0}'</b> does not contain any"
-                        " classes or subpackages.").format(self.package)
-            ))
+            ct.setHtml(
+                self.buildErrorMessage(
+                    self.tr(
+                        "The package <b>'{0}'</b> does not contain any"
+                        " classes or subpackages."
+                    ).format(self.package)
+                )
+            )
             return
-        
+
         # step 2: build the class hierarchies
         routes = []
         nodes = []
-        
+
         for modName in list(modules.keys()):
             module = modules[modName]
             todo = [module.createHierarchy()]
@@ -268,57 +278,57 @@
                 hierarchy = todo[0]
                 for className in hierarchy:
                     cw = self.__getCurrentShape(className)
-                    if not cw and className.find('.') >= 0:
-                        cw = self.__getCurrentShape(className.split('.')[-1])
+                    if not cw and className.find(".") >= 0:
+                        cw = self.__getCurrentShape(className.split(".")[-1])
                         if cw:
                             self.allClasses[className] = cw
                     if cw and cw.noAttrs != self.noAttrs:
                         cw = None
-                    if cw and not (cw.external and
-                                   (className in module.classes or
-                                    className in module.modules)
-                                   ):
+                    if cw and not (
+                        cw.external
+                        and (className in module.classes or className in module.modules)
+                    ):
                         if className not in nodes:
                             nodes.append(className)
                     else:
                         if className in module.classes:
                             # this is a local class (defined in this module)
                             self.__addLocalClass(
-                                className, module.classes[className],
-                                0, 0)
+                                className, module.classes[className], 0, 0
+                            )
                         elif className in module.modules:
                             # this is a local module (defined in this module)
                             self.__addLocalClass(
-                                className, module.modules[className],
-                                0, 0, True)
+                                className, module.modules[className], 0, 0, True
+                            )
                         else:
                             self.__addExternalClass(className, 0, 0)
                         nodes.append(className)
-                    
+
                     if hierarchy.get(className):
                         todo.append(hierarchy.get(className))
                         children = list(hierarchy.get(className).keys())
                         for child in children:
                             if (className, child) not in routes:
                                 routes.append((className, child))
-                
+
                 del todo[0]
-        
+
         # step 3: build the subpackages
         for subpackage in sorted(subpackages.keys()):
             self.__addPackage(subpackage, subpackages[subpackage], 0, 0)
             nodes.append(subpackage)
-        
+
         self.__arrangeClasses(nodes, routes[:])
         self.__createAssociations(routes)
         self.umlView.autoAdjustSceneSize(limit=True)
-    
+
     def __arrangeClasses(self, nodes, routes, whiteSpaceFactor=1.2):
         """
         Private method to arrange the shapes on the canvas.
-        
+
         The algorithm is borrowed from Boa Constructor.
-        
+
         @param nodes list of nodes to arrange
         @type list of str
         @param routes list of routes
@@ -328,16 +338,16 @@
         @type float
         """
         from . import GraphicsUtilities
+
         generations = GraphicsUtilities.sort(nodes, routes)
-        
+
         # calculate width and height of all elements
         sizes = []
         for generation in generations:
             sizes.append([])
             for child in generation:
-                sizes[-1].append(
-                    self.__getCurrentShape(child).sceneBoundingRect())
-                
+                sizes[-1].append(self.__getCurrentShape(child).sceneBoundingRect())
+
         # calculate total width and total height
         width = 0
         height = 0
@@ -346,56 +356,53 @@
         for generation in sizes:
             currentWidth = 0
             currentHeight = 0
-            
+
             for rect in generation:
                 if rect.bottom() > currentHeight:
                     currentHeight = rect.bottom()
                 currentWidth += rect.right()
-                
+
             # update totals
             if currentWidth > width:
                 width = currentWidth
             height += currentHeight
-            
+
             # store generation info
             widths.append(currentWidth)
             heights.append(currentHeight)
-        
+
         # add in some whitespace
         width *= whiteSpaceFactor
         height = height * whiteSpaceFactor - 20
         verticalWhiteSpace = 40.0
-        
+
         sceneRect = self.umlView.sceneRect()
         width += 50.0
         height += 50.0
         swidth = sceneRect.width() if width < sceneRect.width() else width
         sheight = sceneRect.height() if height < sceneRect.height() else height
         self.umlView.setSceneSize(swidth, sheight)
-        
+
         # distribute each generation across the width and the
         # generations across height
         y = 10.0
-        for currentWidth, currentHeight, generation in (
-            zip_longest(widths, heights, generations)
+        for currentWidth, currentHeight, generation in zip_longest(
+            widths, heights, generations
         ):
             x = 10.0
             # whiteSpace is the space between any two elements
-            whiteSpace = (
-                (width - currentWidth - 20) /
-                (len(generation) - 1.0 or 2.0)
-            )
+            whiteSpace = (width - currentWidth - 20) / (len(generation) - 1.0 or 2.0)
             for className in generation:
                 cw = self.__getCurrentShape(className)
                 cw.setPos(x, y)
                 rect = cw.sceneBoundingRect()
                 x = x + rect.width() + whiteSpace
             y = y + currentHeight + verticalWhiteSpace
-    
+
     def __addLocalClass(self, className, _class, x, y, isRbModule=False):
         """
         Private method to add a class defined in the module.
-        
+
         @param className name of the class to be as a dictionary key
         @type str
         @param _class class to be shown
@@ -408,6 +415,7 @@
         @type bool
         """
         from .ClassItem import ClassItem, ClassModel
+
         name = _class.name
         if isRbModule:
             name = "{0} (Module)".format(name)
@@ -415,20 +423,27 @@
             name,
             sorted(_class.methods.keys())[:],
             sorted(_class.attributes.keys())[:],
-            sorted(_class.globals.keys())[:]
+            sorted(_class.globals.keys())[:],
         )
-        cw = ClassItem(cl, False, x, y, noAttrs=self.noAttrs, scene=self.scene,
-                       colors=self.umlView.getDrawingColors())
+        cw = ClassItem(
+            cl,
+            False,
+            x,
+            y,
+            noAttrs=self.noAttrs,
+            scene=self.scene,
+            colors=self.umlView.getDrawingColors(),
+        )
         cw.setId(self.umlView.getItemId())
         self.allClasses[className] = cw
-    
+
     def __addExternalClass(self, _class, x, y):
         """
         Private method to add a class defined outside the module.
-        
+
         If the canvas is too small to take the shape, it
         is enlarged.
-        
+
         @param _class class to be shown
         @type ModuleParser.Class
         @param x x-coordinate
@@ -437,16 +452,24 @@
         @type float
         """
         from .ClassItem import ClassItem, ClassModel
+
         cl = ClassModel(_class)
-        cw = ClassItem(cl, True, x, y, noAttrs=self.noAttrs, scene=self.scene,
-                       colors=self.umlView.getDrawingColors())
+        cw = ClassItem(
+            cl,
+            True,
+            x,
+            y,
+            noAttrs=self.noAttrs,
+            scene=self.scene,
+            colors=self.umlView.getDrawingColors(),
+        )
         cw.setId(self.umlView.getItemId())
         self.allClasses[_class] = cw
-    
+
     def __addPackage(self, name, modules, x, y):
         """
         Private method to add a package to the diagram.
-        
+
         @param name package name to be shown
         @type str
         @param modules list of module names contained in the package
@@ -457,20 +480,23 @@
         @type float
         """
         from .PackageItem import PackageItem, PackageModel
+
         pm = PackageModel(name, modules)
-        pw = PackageItem(pm, x, y, scene=self.scene,
-                         colors=self.umlView.getDrawingColors())
+        pw = PackageItem(
+            pm, x, y, scene=self.scene, colors=self.umlView.getDrawingColors()
+        )
         pw.setId(self.umlView.getItemId())
         self.allClasses[name] = pw
-    
+
     def __createAssociations(self, routes):
         """
         Private method to generate the associations between the class shapes.
-        
+
         @param routes list of relationsships
         @type list of tuple of (str, str)
         """
         from .AssociationItem import AssociationItem, AssociationType
+
         for route in routes:
             if len(route) > 1:
                 assoc = AssociationItem(
@@ -478,13 +504,14 @@
                     self.__getCurrentShape(route[0]),
                     AssociationType.GENERALISATION,
                     topToBottom=True,
-                    colors=self.umlView.getDrawingColors())
+                    colors=self.umlView.getDrawingColors(),
+                )
                 self.scene.addItem(assoc)
-    
+
     def parsePersistenceData(self, version, data):
         """
         Public method to parse persisted data.
-        
+
         @param version version of the data
         @type str
         @param data persisted data to be parsed
@@ -494,23 +521,23 @@
         """
         parts = data.split(", ")
         if (
-            len(parts) != 2 or
-            not parts[0].startswith("package=") or
-            not parts[1].startswith("no_attributes=")
+            len(parts) != 2
+            or not parts[0].startswith("package=")
+            or not parts[1].startswith("no_attributes=")
         ):
             return False
-        
+
         self.package = parts[0].split("=", 1)[1].strip()
         self.noAttrs = Utilities.toBool(parts[1].split("=", 1)[1].strip())
-        
+
         self.initialize()
-        
+
         return True
-    
+
     def toDict(self):
         """
         Public method to collect data to be persisted.
-        
+
         @return dictionary containing data to be persisted
         @rtype dict
         """
@@ -518,19 +545,19 @@
             "project_name": self.project.getProjectName(),
             "no_attributes": self.noAttrs,
         }
-        
+
         data["package"] = (
             Utilities.fromNativeSeparators(self.__relPackage)
-            if self.__relPackage else
-            Utilities.fromNativeSeparators(self.package)
+            if self.__relPackage
+            else Utilities.fromNativeSeparators(self.package)
         )
-        
+
         return data
-    
+
     def fromDict(self, version, data):
         """
         Public method to populate the class with data persisted by 'toDict()'.
-        
+
         @param version version of the data
         @type str
         @param data dictionary containing the persisted data
@@ -541,7 +568,7 @@
         """
         try:
             self.noAttrs = data["no_attributes"]
-            
+
             package = Utilities.toNativeSeparators(data["package"])
             if os.path.isabs(package):
                 self.package = package
@@ -554,12 +581,12 @@
                         " Please open it and try again.</p>"
                     ).format(data["project_name"])
                     return False, msg
-                
+
                 self.__relPackage = package
                 self.package = self.project.getAbsolutePath(package)
         except KeyError:
             return False, ""
-        
+
         self.initialize()
-        
+
         return True, ""

eric ide

mercurial