PluginMetricsRadon.py

Tue, 09 Jul 2024 14:24:16 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 09 Jul 2024 14:24:16 +0200
branch
eric7
changeset 120
61bf877c4f1a
parent 114
c4e39e9e3987
child 122
bcfe13e956fb
permissions
-rw-r--r--

Added the Ui_*.py files and redid the distribution archive.

# -*- coding: utf-8 -*-

# Copyright (c) 2015 - 2024 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the radon code metrics plug-in.
"""

import contextlib
import os

from PyQt6.QtCore import QObject, QTranslator, pyqtSignal
from PyQt6.QtGui import QAction

from eric7 import Globals, Preferences
from eric7.EricGui.EricAction import EricAction
from eric7.EricWidgets import EricMessageBox
from eric7.EricWidgets.EricApplication import ericApp
from eric7.Project.ProjectBrowserModel import ProjectBrowserFileItem
from eric7.SystemUtilities import FileSystemUtilities

# Start-Of-Header
name = "Radon Metrics Plugin"
author = "Detlev Offenbach <detlev@die-offenbachs.de>"
autoactivate = True
deactivateable = True
version = "10.3.1"
className = "RadonMetricsPlugin"
packageName = "RadonMetrics"
shortDescription = "Code metrics plugin using radon package"
longDescription = (
    """This plug-in implements dialogs to show various code metrics. These"""
    """ are determined using the radon code metrics package. 'Raw code"""
    """ metrics', 'Maintainability Index' and 'McCabe Complexity' can be"""
    """ requested through different dialogs for one file or the whole"""
    """ project."""
)
needsRestart = False
hasCompiledForms = True
pyqtApi = 2
# End-Of-Header

error = ""


class RadonMetricsPlugin(QObject):
    """
    Class implementing the radon code metrics plug-in.

    @signal metricsDone(str, dict) emitted when the code metrics were
        determined for a file
    @signal maintainabilityIndexDone(str, dict) emitted when the
        maintainability index was determined for a file
    @signal complexityDone(str, dict) emitted when the
        cyclomatic complexity was determined for a file
    @signal error(str, str, str) emitted in case of an error
    @signal batchFinished(str) emitted when a code metrics batch is done
    """

    metricsDone = pyqtSignal(str, dict)
    maintainabilityIndexDone = pyqtSignal(str, dict)
    complexityDone = pyqtSignal(str, dict)
    error = pyqtSignal(str, str, str)
    batchFinished = pyqtSignal(str)

    def __init__(self, ui):
        """
        Constructor

        @param ui reference to the user interface object
        @type UserInterface
        """
        super().__init__(ui)
        self.__ui = ui
        self.__initialize()

        self.backgroundService = ericApp().getObject("BackgroundService")

        path = os.path.join(os.path.dirname(__file__), packageName)

        # raw code metrics calculation
        self.backgroundService.serviceConnect(
            "radon_raw",
            "Python3",
            path,
            "CodeMetricsCalculator",
            lambda fn, res: self.metricsCalculationDone("raw", fn, res),
            onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy3(
                "raw", fx, lang, fn, msg
            ),
            onBatchDone=lambda fx, lang: self.batchJobDone("raw", fx, lang),
        )

        # maintainability index calculation
        self.backgroundService.serviceConnect(
            "radon_mi",
            "Python3",
            path,
            "MaintainabilityIndexCalculator",
            lambda fn, res: self.metricsCalculationDone("mi", fn, res),
            onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy3(
                "mi", fx, lang, fn, msg
            ),
            onBatchDone=lambda fx, lang: self.batchJobDone("mi", fx, lang),
        )

        # cyclomatic complexity
        self.backgroundService.serviceConnect(
            "radon_cc",
            "Python3",
            path,
            "CyclomaticComplexityCalculator",
            lambda fn, res: self.metricsCalculationDone("cc", fn, res),
            onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy3(
                "cc", fx, lang, fn, msg
            ),
            onBatchDone=lambda fx, lang: self.batchJobDone("cc", fx, lang),
        )

        self.queuedBatches = {
            "raw": [],
            "mi": [],
            "cc": [],
        }
        self.batchesFinished = {
            "raw": True,
            "mi": True,
            "cc": True,
        }

        self.__translator = None
        self.__loadTranslator()

    def __serviceError(self, type_, fn, msg):
        """
        Private slot handling service errors.

        @param type_ type of the calculated metrics
        @type str, one of ["raw", "mi", "cc"]
        @param fn file name
        @type str
        @param msg message text
        @type str
        """
        self.error.emit(type_, fn, msg)

    def serviceErrorPy3(self, type_, fx, lang, fn, msg):
        """
        Public slot handling service errors for Python 3.

        @param type_ type of the calculated metrics
        @type str, one of ["raw", "mi", "cc"]
        @param fx service name
        @type str
        @param lang language
        @type str
        @param fn file name
        @type str
        @param msg message text
        @type str
        """
        if fx in ["radon_" + type_, "batch_radon_" + type_]:
            if fx == "radon_" + type_:
                self.__serviceError(type_, fn, msg)
            else:
                self.__serviceError(type_, self.tr("Python 3 batch job"), msg)
                self.batchJobDone(type_, fx, lang)

    def batchJobDone(self, type_, fx, lang):
        """
        Public slot handling the completion of a batch job.

        @param type_ type of the calculated metrics
        @type str, one of ["raw", "mi", "cc"]
        @param fx service name
        @type str
        @param lang language
        @type str
        """
        if fx in ["radon_" + type_, "batch_radon_" + type_]:
            if lang in self.queuedBatches[type_]:
                self.queuedBatches[type_].remove(lang)
            # prevent sending the signal multiple times
            if len(self.queuedBatches[type_]) == 0 and not self.batchesFinished[type_]:
                self.batchFinished.emit(type_)
                self.batchesFinished[type_] = True

    def metricsCalculationDone(self, type_, filename, result):
        """
        Public slot to dispatch the result.

        @param type_ type of the calculated metrics
        @type str, one of ["raw", "mi", "cc"]
        @param filename name of the file the results belong to
        @type str
        @param result result dictionary
        @type dict
        """
        if type_ == "raw":
            self.metricsDone.emit(filename, result)
        elif type_ == "mi":
            self.maintainabilityIndexDone.emit(filename, result)
        elif type_ == "cc":
            self.complexityDone.emit(filename, result)
        else:
            self.error.emit(
                type_,
                filename,
                self.tr("Unknown metrics result received ({0}).").format(type_),
            )

    def __initialize(self):
        """
        Private slot to (re)initialize the plugin.
        """
        self.__projectRawMetricsDialog = None
        self.__projectMIDialog = None
        self.__projectCCDialog = None
        self.__projectMetricsActs = []
        self.__projectSeparatorActs = []

        self.__projectBrowserRawMetricsDialog = None
        self.__projectBrowserMIDialog = None
        self.__projectBrowserCCDialog = None
        self.__projectBrowserMenu = None
        self.__projectBrowserMetricsActs = []
        self.__projectBrowserSeparatorActs = []

        self.__editors = []
        self.__editorRawMetricsDialog = None
        self.__editorMIDialog = None
        self.__editorCCDialog = None
        self.__editorMetricsActs = []
        self.__editorSeparatorActs = []

    def rawMetrics(self, lang, filename, source):
        """
        Public method to prepare raw code metrics calculation on one Python
        source file.

        @param lang language of the file or None to determine by internal
            algorithm
        @type str or None
        @param filename source filename
        @type str
        @param source string containing the code
        @type str
        """
        if lang is None:
            lang = "Python3"
        if lang == "Python3":
            self.backgroundService.enqueueRequest("radon_raw", lang, filename, [source])

    def rawMetricsBatch(self, argumentsList):
        """
        Public method to prepare raw code metrics calculation on multiple
        Python source files.

        @param argumentsList list of arguments tuples with each tuple
            containing filename and source
        @type (str, str)
        """
        data = {
            "Python3": [],
        }
        for filename, source in argumentsList:
            data["Python3"].append((filename, source))

        self.queuedBatches["raw"] = []
        if data["Python3"]:
            self.queuedBatches["raw"].append("Python3")
            self.backgroundService.enqueueRequest(
                "batch_radon_raw", "Python3", "", data["Python3"]
            )
            self.batchesFinished["raw"] = False

    def cancelRawMetricsBatch(self):
        """
        Public method to cancel all batch jobs.
        """
        self.backgroundService.requestCancel("batch_radon_raw", "Python3")

    def maintainabilityIndex(self, lang, filename, source):
        """
        Public method to prepare maintainability index calculation on one
        Python source file.

        @param lang language of the file or None to determine by internal
            algorithm
        @type str or None
        @param filename source filename
        @type str
        @param source string containing the code
        @type str
        """
        if lang is None:
            lang = "Python3"
        if lang == "Python3":
            self.backgroundService.enqueueRequest("radon_mi", lang, filename, [source])

    def maintainabilityIndexBatch(self, argumentsList):
        """
        Public method to prepare maintainability index calculation on multiple
        Python source files.

        @param argumentsList list of arguments tuples with each tuple
            containing filename and source
        @type (str, str)
        """
        data = {
            "Python3": [],
        }
        for filename, source in argumentsList:
            data["Python3"].append((filename, source))

        self.queuedBatches["mi"] = []
        if data["Python3"]:
            self.queuedBatches["mi"].append("Python3")
            self.backgroundService.enqueueRequest(
                "batch_radon_mi", "Python3", "", data["Python3"]
            )
            self.batchesFinished["mi"] = False

    def cancelMaintainabilityIndexBatch(self):
        """
        Public method to cancel all batch jobs.
        """
        self.backgroundService.requestCancel("batch_radon_mi", "Python3")

    def cyclomaticComplexity(self, lang, filename, source):
        """
        Public method to prepare cyclomatic complexity calculation on one
        Python source file.

        @param lang language of the file or None to determine by internal
            algorithm
        @type str or None
        @param filename source filename
        @type str
        @param source string containing the code
        @type str
        """
        if lang is None:
            lang = "Python3"
        if lang == "Python3":
            self.backgroundService.enqueueRequest("radon_cc", lang, filename, [source])

    def cyclomaticComplexityBatch(self, argumentsList):
        """
        Public method to prepare cyclomatic complexity calculation on multiple
        Python source files.

        @param argumentsList list of arguments tuples with each tuple
            containing filename and source
        @type (str, str)
        """
        data = {
            "Python3": [],
        }
        for filename, source in argumentsList:
            data["Python3"].append((filename, source))

        self.queuedBatches["raw"] = []
        if data["Python3"]:
            self.queuedBatches["cc"].append("Python3")
            self.backgroundService.enqueueRequest(
                "batch_radon_cc", "Python3", "", data["Python3"]
            )
            self.batchesFinished["cc"] = False

    def cancelComplexityBatch(self):
        """
        Public method to cancel all batch jobs.
        """
        self.backgroundService.requestCancel("batch_radon_cc", "Python3")

    def activate(self):
        """
        Public method to activate this plug-in.

        @return tuple of None and activation status
        @rtype (None, bool)
        """
        global error
        error = ""  # clear previous error

        # Project menu actions
        menu = ericApp().getObject("Project").getMenu("Show")
        if menu:
            if not menu.isEmpty():
                act = menu.addSeparator()
                self.__projectSeparatorActs.append(act)

            # header action
            act = QAction(self.tr("Radon"), self)
            font = act.font()
            font.setBold(True)
            act.setFont(font)
            act.triggered.connect(self.__showRadonVersion)
            menu.addAction(act)
            self.__projectMetricsActs.append(act)

            act = EricAction(
                self.tr("Code Metrics"),
                self.tr("Code &Metrics..."),
                0,
                0,
                self,
                "project_show_radon_raw",
            )
            act.setStatusTip(self.tr("Show raw code metrics."))
            act.setWhatsThis(
                self.tr(
                    """<b>Code Metrics...</b>"""
                    """<p>This calculates raw code metrics of Python files"""
                    """ and shows the amount of lines of code, logical lines"""
                    """ of code, source lines of code, comment lines,"""
                    """ multi-line strings and blank lines.</p>"""
                )
            )
            act.triggered.connect(self.__projectRawMetrics)
            menu.addAction(act)
            self.__projectMetricsActs.append(act)

            act = EricAction(
                self.tr("Maintainability Index"),
                self.tr("Maintainability &Index..."),
                0,
                0,
                self,
                "project_show_radon_mi",
            )
            act.setStatusTip(
                self.tr("Show the maintainability index for Python files.")
            )
            act.setWhatsThis(
                self.tr(
                    """<b>Maintainability Index...</b>"""
                    """<p>This calculates the maintainability index of Python"""
                    """ files and shows it together with a ranking.</p>"""
                )
            )
            act.triggered.connect(self.__projectMaintainabilityIndex)
            menu.addAction(act)
            self.__projectMetricsActs.append(act)

            act = EricAction(
                self.tr("Cyclomatic Complexity"),
                self.tr("Cyclomatic &Complexity..."),
                0,
                0,
                self,
                "project_show_radon_cc",
            )
            act.setStatusTip(
                self.tr("Show the cyclomatic complexity for Python files.")
            )
            act.setWhatsThis(
                self.tr(
                    """<b>Cyclomatic Complexity...</b>"""
                    """<p>This calculates the cyclomatic complexity of Python"""
                    """ files and shows it together with a ranking.</p>"""
                )
            )
            act.triggered.connect(self.__projectCyclomaticComplexity)
            menu.addAction(act)
            self.__projectMetricsActs.append(act)

            act = menu.addSeparator()
            self.__projectSeparatorActs.append(act)

            ericApp().getObject("Project").addEricActions(self.__projectMetricsActs[1:])

        # Editor menu actions (one separator each above and below)
        act = QAction(self)
        act.setSeparator(True)
        self.__editorSeparatorActs.append(act)
        act = QAction(self)
        act.setSeparator(True)
        self.__editorSeparatorActs.append(act)

        # header action
        act = QAction(self.tr("Radon"), self)
        font = act.font()
        font.setBold(True)
        act.setFont(font)
        act.triggered.connect(self.__showRadonVersion)
        self.__editorMetricsActs.append(act)

        act = EricAction(
            self.tr("Code Metrics"), self.tr("Code &Metrics..."), 0, 0, self, ""
        )
        act.setStatusTip(self.tr("Show raw code metrics."))
        act.setWhatsThis(
            self.tr(
                """<b>Code Metrics...</b>"""
                """<p>This calculates raw code metrics of Python files"""
                """ and shows the amount of lines of code, logical lines"""
                """ of code, source lines of code, comment lines,"""
                """ multi-line strings and blank lines.</p>"""
            )
        )
        act.triggered.connect(self.__editorRawMetrics)
        self.__editorMetricsActs.append(act)

        act = EricAction(
            self.tr("Maintainability Index"),
            self.tr("Maintainability &Index..."),
            0,
            0,
            self,
            "",
        )
        act.setStatusTip(self.tr("Show the maintainability index for Python files."))
        act.setWhatsThis(
            self.tr(
                """<b>Maintainability Index...</b>"""
                """<p>This calculates the maintainability index of Python"""
                """ files and shows it together with a ranking.</p>"""
            )
        )
        act.triggered.connect(self.__editorMaintainabilityIndex)
        self.__editorMetricsActs.append(act)

        act = EricAction(
            self.tr("Cyclomatic Complexity"),
            self.tr("Cyclomatic &Complexity..."),
            0,
            0,
            self,
            "",
        )
        act.setStatusTip(self.tr("Show the cyclomatic complexity for Python files."))
        act.setWhatsThis(
            self.tr(
                """<b>Cyclomatic Complexity...</b>"""
                """<p>This calculates the cyclomatic complexity of Python"""
                """ files and shows it together with a ranking.</p>"""
            )
        )
        act.triggered.connect(self.__editorCyclomaticComplexity)
        self.__editorMetricsActs.append(act)

        ericApp().getObject("Project").showMenu.connect(self.__projectShowMenu)
        ericApp().getObject("Project").projectClosed.connect(self.__projectClosed)
        ericApp().getObject("ProjectBrowser").getProjectBrowser(
            "sources"
        ).showMenu.connect(self.__projectBrowserShowMenu)
        ericApp().getObject("ViewManager").editorOpenedEd.connect(self.__editorOpened)
        ericApp().getObject("ViewManager").editorClosedEd.connect(self.__editorClosed)

        for editor in ericApp().getObject("ViewManager").getOpenEditors():
            self.__editorOpened(editor)

        return None, True

    def deactivate(self):
        """
        Public method to deactivate this plug-in.
        """
        ericApp().getObject("Project").showMenu.disconnect(self.__projectShowMenu)
        ericApp().getObject("Project").projectClosed.disconnect(self.__projectClosed)
        ericApp().getObject("ProjectBrowser").getProjectBrowser(
            "sources"
        ).showMenu.disconnect(self.__projectBrowserShowMenu)
        ericApp().getObject("ViewManager").editorOpenedEd.disconnect(
            self.__editorOpened
        )
        ericApp().getObject("ViewManager").editorClosedEd.disconnect(
            self.__editorClosed
        )

        menu = ericApp().getObject("Project").getMenu("Show")
        if menu:
            for sep in self.__projectSeparatorActs:
                menu.removeAction(sep)
            for act in self.__projectMetricsActs:
                menu.removeAction(act)
            ericApp().getObject("Project").removeEricActions(
                self.__projectMetricsActs[1:]
            )

        if self.__projectBrowserMenu:
            for sep in self.__projectBrowserSeparatorActs:
                self.__projectBrowserMenu.removeAction(sep)
            for act in self.__projectBrowserMetricsActs:
                self.__projectBrowserMenu.removeAction(act)

        for editor in self.__editors:
            editor.showMenu.disconnect(self.__editorShowMenu)
            menu = editor.getMenu("Show")
            if menu is not None:
                for sep in self.__editorSeparatorActs:
                    menu.removeAction(sep)
                for act in self.__editorMetricsActs:
                    menu.removeAction(act)

        self.__initialize()

    def __loadTranslator(self):
        """
        Private method to load the translation file.
        """
        if self.__ui is not None:
            loc = self.__ui.getLocale()
            if loc and loc != "C":
                locale_dir = os.path.join(
                    os.path.dirname(__file__), "RadonMetrics", "i18n"
                )
                translation = "radon_{0}".format(loc)
                translator = QTranslator(None)
                loaded = translator.load(translation, locale_dir)
                if loaded:
                    self.__translator = translator
                    ericApp().installTranslator(self.__translator)
                else:
                    print(
                        "Warning: translation file '{0}' could not be"
                        " loaded.".format(translation)
                    )
                    print("Using default.")

    def __projectShowMenu(
        self,
        menuName,
        menu,  # noqa: U100
    ):
        """
        Private slot called, when the the project menu or a submenu is
        about to be shown.

        @param menuName name of the menu to be shown
        @type str
        @param menu reference to the menu
        @type QMenu
        """
        if menuName == "Show":
            for act in self.__projectMetricsActs[1:]:
                act.setEnabled(
                    ericApp().getObject("Project").getProjectLanguage() == "Python3"
                    and not FileSystemUtilities.isRemoteFileName(
                        ericApp().getObject("Project").getProjectPath()
                    )
                )

    def __projectBrowserShowMenu(self, menuName, menu):
        """
        Private slot called, when the the project browser context menu or a
        submenu is about to be shown.

        @param menuName name of the menu to be shown
        @type str
        @param menu reference to the menu
        @type QMenu
        """
        if (
            menuName == "Show"
            and ericApp().getObject("Project").getProjectLanguage() == "Python3"
            and not FileSystemUtilities.isRemoteFileName(
                ericApp().getObject("Project").getProjectPath()
            )
            and self.__projectBrowserMenu is None
        ):
            self.__projectBrowserMenu = menu

            act = menu.addSeparator()
            self.__projectBrowserSeparatorActs.append(act)

            # header action
            act = QAction(self.tr("Radon"), self)
            font = act.font()
            font.setBold(True)
            act.setFont(font)
            act.triggered.connect(self.__showRadonVersion)
            menu.addAction(act)
            self.__projectBrowserMetricsActs.append(act)

            act = EricAction(
                self.tr("Code Metrics"), self.tr("Code &Metrics..."), 0, 0, self, ""
            )
            act.setStatusTip(self.tr("Show raw code metrics."))
            act.setWhatsThis(
                self.tr(
                    """<b>Code Metrics...</b>"""
                    """<p>This calculates raw code metrics of Python files"""
                    """ and shows the amount of lines of code, logical lines"""
                    """ of code, source lines of code, comment lines,"""
                    """ multi-line strings and blank lines.</p>"""
                )
            )
            act.triggered.connect(self.__projectBrowserRawMetrics)
            menu.addAction(act)
            self.__projectBrowserMetricsActs.append(act)

            act = EricAction(
                self.tr("Maintainability Index"),
                self.tr("Maintainability &Index..."),
                0,
                0,
                self,
                "",
            )
            act.setStatusTip(
                self.tr("Show the maintainability index for Python files.")
            )
            act.setWhatsThis(
                self.tr(
                    """<b>Maintainability Index...</b>"""
                    """<p>This calculates the maintainability index of"""
                    """ Python files and shows it together with a ranking."""
                    """</p>"""
                )
            )
            act.triggered.connect(self.__projectBrowserMaintainabilityIndex)
            menu.addAction(act)
            self.__projectBrowserMetricsActs.append(act)

            act = EricAction(
                self.tr("Cyclomatic Complexity"),
                self.tr("Cyclomatic &Complexity..."),
                0,
                0,
                self,
                "",
            )
            act.setStatusTip(
                self.tr("Show the cyclomatic complexity for Python files.")
            )
            act.setWhatsThis(
                self.tr(
                    """<b>Cyclomatic Complexity...</b>"""
                    """<p>This calculates the cyclomatic complexity of"""
                    """ Python files and shows it together with a ranking."""
                    """</p>"""
                )
            )
            act.triggered.connect(self.__projectBrowserCyclomaticComplexity)
            menu.addAction(act)
            self.__projectBrowserMetricsActs.append(act)

            act = menu.addSeparator()
            self.__projectBrowserSeparatorActs.append(act)

    def __editorOpened(self, editor):
        """
        Private slot called, when a new editor was opened.

        @param editor reference to the new editor
        @type Editor
        """
        menu = editor.getMenu("Show")
        if menu is not None:
            menu.addAction(self.__editorSeparatorActs[0])
            menu.addActions(self.__editorMetricsActs)
            menu.addAction(self.__editorSeparatorActs[1])
            editor.showMenu.connect(self.__editorShowMenu)
            editor.editorRenamed.connect(lambda: self.__editorRenamed(editor))
            self.__editors.append(editor)

    def __editorClosed(self, editor):
        """
        Private slot called, when an editor was closed.

        @param editor reference to the editor
        @type Editor
        """
        with contextlib.suppress(ValueError):
            self.__editors.remove(editor)

    def __editorRenamed(self, editor):
        """
        Private slot called, when an editor was renamed.

        @param editor reference to the renamed editor
        @type Editor
        """
        menu = editor.getMenu("Show")
        if menu is not None:
            menu.addAction(self.__editorSeparatorActs[0])
            menu.addActions(self.__editorMetricsActs)
            menu.addAction(self.__editorSeparatorActs[1])

    def __editorShowMenu(
        self,
        menuName,
        menu,  # noqa: U100
        editor,
    ):
        """
        Private slot called, when the the editor context menu or a submenu is
        about to be shown.

        @param menuName name of the menu to be shown
        @type str
        @param menu reference to the menu
        @type QMenu
        @param editor reference to the editor
        @type Editor
        """
        if menuName == "Show":
            enable = editor.isPyFile() and not FileSystemUtilities.isRemoteFileName(
                editor.getFileName()
            )
            for act in self.__editorMetricsActs:
                act.setEnabled(enable)

    ##################################################################
    ## Raw code metrics calculations
    ##################################################################

    def __projectRawMetrics(self):
        """
        Private slot used to calculate raw code metrics for the project.
        """
        project = ericApp().getObject("Project")
        project.saveAllScripts()
        ppath = project.getProjectPath()
        files = [
            os.path.join(ppath, file)
            for file in project.getSources()
            if file.endswith(tuple(Preferences.getPython("Python3Extensions")))
        ]

        if self.__projectRawMetricsDialog is None:
            from RadonMetrics.RawMetricsDialog import RawMetricsDialog  # noqa: I101

            self.__projectRawMetricsDialog = RawMetricsDialog(self)
        self.__projectRawMetricsDialog.show()
        self.__projectRawMetricsDialog.prepare(files, project)

    def __projectBrowserRawMetrics(self):
        """
        Private method to handle the code metrics context menu action of the
        project sources browser.
        """
        browser = ericApp().getObject("ProjectBrowser").getProjectBrowser("sources")
        if browser.getSelectedItemsCount([ProjectBrowserFileItem]) > 1:
            fn = []
            for itm in browser.getSelectedItems([ProjectBrowserFileItem]):
                fn.append(itm.fileName())
        else:
            itm = browser.model().item(browser.currentIndex())
            try:
                fn = itm.fileName()
            except AttributeError:
                fn = itm.dirName()

        if self.__projectBrowserRawMetricsDialog is None:
            from RadonMetrics.RawMetricsDialog import RawMetricsDialog  # noqa: I101

            self.__projectBrowserRawMetricsDialog = RawMetricsDialog(self)
        self.__projectBrowserRawMetricsDialog.show()
        self.__projectBrowserRawMetricsDialog.start(fn)

    def __editorRawMetrics(self):
        """
        Private slot to handle the raw code metrics action of the editor show
        menu.
        """
        editor = ericApp().getObject("ViewManager").activeWindow()
        if (
            editor is not None
            and editor.checkDirty()
            and editor.getFileName() is not None
        ):
            if self.__editorRawMetricsDialog is None:
                from RadonMetrics.RawMetricsDialog import RawMetricsDialog  # noqa: I101

                self.__editorRawMetricsDialog = RawMetricsDialog(self)
            self.__editorRawMetricsDialog.show()
            self.__editorRawMetricsDialog.start(editor.getFileName())

    ##################################################################
    ## Maintainability index calculations
    ##################################################################

    def __projectMaintainabilityIndex(self):
        """
        Private slot used to calculate the maintainability indexes for the
        project.
        """
        project = ericApp().getObject("Project")
        project.saveAllScripts()
        ppath = project.getProjectPath()
        files = [
            os.path.join(ppath, file)
            for file in project.getSources()
            if file.endswith(tuple(Preferences.getPython("Python3Extensions")))
        ]

        if self.__projectMIDialog is None:
            from RadonMetrics.MaintainabilityIndexDialog import (  # noqa: I101
                MaintainabilityIndexDialog,
            )

            self.__projectMIDialog = MaintainabilityIndexDialog(self)
        self.__projectMIDialog.show()
        self.__projectMIDialog.prepare(files, project)

    def __projectBrowserMaintainabilityIndex(self):
        """
        Private method to handle the maintainability index context menu action
        of the project sources browser.
        """
        browser = ericApp().getObject("ProjectBrowser").getProjectBrowser("sources")
        if browser.getSelectedItemsCount([ProjectBrowserFileItem]) > 1:
            fn = []
            for itm in browser.getSelectedItems([ProjectBrowserFileItem]):
                fn.append(itm.fileName())
        else:
            itm = browser.model().item(browser.currentIndex())
            try:
                fn = itm.fileName()
            except AttributeError:
                fn = itm.dirName()

        if self.__projectBrowserMIDialog is None:
            from RadonMetrics.MaintainabilityIndexDialog import (  # noqa: I101
                MaintainabilityIndexDialog,
            )

            self.__projectBrowserMIDialog = MaintainabilityIndexDialog(self)
        self.__projectBrowserMIDialog.show()
        self.__projectBrowserMIDialog.start(fn)

    def __editorMaintainabilityIndex(self):
        """
        Private slot to handle the maintainability index action of the editor
        show menu.
        """
        editor = ericApp().getObject("ViewManager").activeWindow()
        if (
            editor is not None
            and editor.checkDirty()
            and editor.getFileName() is not None
        ):
            if self.__editorMIDialog is None:
                from RadonMetrics.MaintainabilityIndexDialog import (  # noqa: I101
                    MaintainabilityIndexDialog,
                )

                self.__editorMIDialog = MaintainabilityIndexDialog(self)
            self.__editorMIDialog.show()
            self.__editorMIDialog.start(editor.getFileName())

    ##################################################################
    ## Cyclomatic complexity calculations
    ##################################################################

    def __projectCyclomaticComplexity(self):
        """
        Private slot used to calculate the cyclomatic complexity for the
        project.
        """
        project = ericApp().getObject("Project")
        project.saveAllScripts()
        ppath = project.getProjectPath()
        files = [
            os.path.join(ppath, file)
            for file in project.getSources()
            if file.endswith(tuple(Preferences.getPython("Python3Extensions")))
        ]

        if self.__projectCCDialog is None:
            from RadonMetrics.CyclomaticComplexityDialog import (  # noqa: I101
                CyclomaticComplexityDialog,
            )

            self.__projectCCDialog = CyclomaticComplexityDialog(self)
        self.__projectCCDialog.show()
        self.__projectCCDialog.prepare(files, project)

    def __projectBrowserCyclomaticComplexity(self):
        """
        Private method to handle the cyclomatic complexity context menu action
        of the project sources browser.
        """
        browser = ericApp().getObject("ProjectBrowser").getProjectBrowser("sources")
        if browser.getSelectedItemsCount([ProjectBrowserFileItem]) > 1:
            fn = []
            for itm in browser.getSelectedItems([ProjectBrowserFileItem]):
                fn.append(itm.fileName())
        else:
            itm = browser.model().item(browser.currentIndex())
            try:
                fn = itm.fileName()
            except AttributeError:
                fn = itm.dirName()

        if self.__projectBrowserCCDialog is None:
            from RadonMetrics.CyclomaticComplexityDialog import (  # noqa: I101
                CyclomaticComplexityDialog,
            )

            self.__projectBrowserCCDialog = CyclomaticComplexityDialog(
                self, isSingle=True
            )
        self.__projectBrowserCCDialog.show()
        self.__projectBrowserCCDialog.start(fn)

    def __editorCyclomaticComplexity(self):
        """
        Private slot to handle the cyclomatic complexity action of the editor
        show menu.
        """
        editor = ericApp().getObject("ViewManager").activeWindow()
        if (
            editor is not None
            and editor.checkDirty()
            and editor.getFileName() is not None
        ):
            if self.__editorCCDialog is None:
                from RadonMetrics.CyclomaticComplexityDialog import (  # noqa: I101
                    CyclomaticComplexityDialog,
                )

                self.__editorCCDialog = CyclomaticComplexityDialog(self, isSingle=True)
            self.__editorCCDialog.show()
            self.__editorCCDialog.start(editor.getFileName())

    ##################################################################
    ## Radon info display
    ##################################################################

    def __showRadonVersion(self):
        """
        Private slot to show the version number of the used radon library.
        """
        from radon import __version__  # noqa: I102

        EricMessageBox.information(
            None,
            self.tr("Radon"),
            self.tr(
                """<p><b>Radon Version {0}</b></p>"""
                """<p>Radon is a Python tool that computes various metrics"""
                """ from the source code. Radon can compute:"""
                """<ul>"""
                """<li><b>Raw</b> metrics (these include SLOC, comment"""
                """ lines, blank lines, multi line strings, ...)</li>"""
                """<li><b>Maintainability Index</b> (the one used in Visual"""
                """ Studio)</li>"""
                """<li><b>McCabe's complexity</b>, i.e. cyclomatic"""
                """ complexity</li>"""
                """</ul></p>"""
            ).format(__version__),
        )

    ##################################################################
    ## Project handling methods
    ##################################################################

    def __projectClosed(self):
        """
        Private slot to handle closing a project.
        """
        self.__projectCCDialog and self.__projectCCDialog.clear()
        self.__projectMIDialog and self.__projectMIDialog.clear()
        if self.__projectRawMetricsDialog:
            self.__projectRawMetricsDialog.clear()


def installDependencies(pipInstall):
    """
    Function to install dependencies of this plug-in.

    @param pipInstall function to be called with a list of package names.
    @type function
    """
    try:
        from radon import __version__ as radon_version  # noqa: I101, I102

        if Globals.versionToTuple(radon_version) < (4, 5, 0):
            # force an upgrade
            pipInstall(["radon>=4.5.0"])
    except ImportError:
        pipInstall(["radon>=4.5.0"])


#
# eflag: noqa = M801

eric ide

mercurial