--- a/PluginMetricsRadon.py Sat Sep 19 11:54:33 2015 +0200 +++ b/PluginMetricsRadon.py Sat Sep 19 18:24:07 2015 +0200 @@ -34,7 +34,10 @@ 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.""" + """ 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 pyqtApi = 2 @@ -110,6 +113,22 @@ onBatchDone=lambda fx, lang: self.batchJobDone( "mi", fx, lang)) + # cyclomatic complexity + self.backgroundService.serviceConnect( + 'radon_cc', 'Python2', path, 'CyclomaticComplexityCalculator', + lambda fn, res: self.metricsCalculationDone("cc", fn, res), + onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy2( + "cc", fx, lang, fn, msg), + onBatchDone=lambda fx, lang: self.batchJobDone( + "c", fx, lang)) + 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.hasBatch = True except TypeError: # backward compatibility for eric 6.0 @@ -137,6 +156,18 @@ onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy3( "mi", fx, lang, fn, msg)) + # cyclomatic complexity + self.backgroundService.serviceConnect( + 'radon_cc', 'Python2', path, 'CyclomaticComplexityCalculator', + lambda fn, res: self.metricsCalculationDone("cc", fn, res), + onErrorCallback=lambda fx, lang, fn, msg: self.serviceErrorPy2( + "cc", fx, lang, fn, msg)) + 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)) + self.hasBatch = False self.queuedBatches = { @@ -263,11 +294,13 @@ """ 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 = [] @@ -275,6 +308,7 @@ self.__editors = [] self.__editorRawMetricsDialog = None self.__editorMIDialog = None + self.__editorCCDialog = None self.__editorMetricsActs = [] self.__editorSeparatorActs = [] @@ -390,6 +424,62 @@ for lang in ['Python2', 'Python3']: self.backgroundService.requestCancel('batch_radon_mi', lang) + 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 = 'Python{0}'.format(determinePythonVersion(filename, source)) + if lang not in ['Python2', 'Python3']: + return + + 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 = { + "Python2": [], + "Python3": [], + } + for filename, source in argumentsList: + lang = 'Python{0}'.format(determinePythonVersion(filename, source)) + if lang not in ['Python2', 'Python3']: + continue + else: + data[lang].append((filename, source)) + + self.queuedBatches["raw"] = [] + for lang in ['Python2', 'Python3']: + if data[lang]: + self.queuedBatches["cc"].append(lang) + self.backgroundService.enqueueRequest('batch_radon_cc', lang, + "", data[lang]) + self.batchesFinished["cc"] = False + + def cancelComplexityBatch(self): + """ + Public method to cancel all batch jobs. + """ + for lang in ['Python2', 'Python3']: + self.backgroundService.requestCancel('batch_radon_cc', lang) + def activate(self): """ Public method to activate this plug-in. @@ -448,6 +538,21 @@ menu.addAction(act) self.__projectMetricsActs.append(act) + act = E5Action( + 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) @@ -468,7 +573,6 @@ font.setBold(True) act.setFont(font) act.triggered.connect(self.__showRadonVersion) - menu.addAction(act) self.__editorMetricsActs.append(act) act = E5Action( @@ -501,6 +605,20 @@ act.triggered.connect(self.__editorMaintainabilityIndex) self.__editorMetricsActs.append(act) + act = E5Action( + 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) + e5App().getObject("Project").showMenu.connect(self.__projectShowMenu) e5App().getObject("ProjectBrowser").getProjectBrowser("sources")\ .showMenu.connect(self.__projectBrowserShowMenu) @@ -618,9 +736,9 @@ act = E5Action( self.tr('Code Metrics'), self.tr('Code &Metrics...'), 0, 0, - self, '') - act.setStatusTip( - self.tr('Show raw code metrics.')) + 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""" @@ -635,10 +753,9 @@ act = E5Action( 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.')) + 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""" @@ -650,6 +767,23 @@ menu.addAction(act) self.__projectBrowserMetricsActs.append(act) + act = E5Action( + 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) @@ -813,6 +947,75 @@ 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 = e5App().getObject("Project") + project.saveAllScripts() + ppath = project.getProjectPath() + files = [os.path.join(ppath, file) + for file in project.pdata["SOURCES"] + if file.endswith( + tuple(Preferences.getPython("Python3Extensions")) + + tuple(Preferences.getPython("PythonExtensions")))] + + if self.__projectCCDialog is None: + from RadonMetrics.CyclomaticComplexityDialog import \ + 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 = e5App().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 \ + CyclomaticComplexityDialog + self.__projectBrowserCCDialog = CyclomaticComplexityDialog(self) + self.__projectBrowserCCDialog.show() + self.__projectBrowserCCDialog.start(fn) + + def __editorCyclomaticComplexity(self): + """ + Private slot to handle the cyclomatic complexity action of the editor + show menu. + """ + editor = e5App().getObject("ViewManager").activeWindow() + if editor is not None: + if editor.checkDirty() and editor.getFileName() is not None: + if self.__editorCCDialog is None: + from RadonMetrics.CyclomaticComplexityDialog import \ + CyclomaticComplexityDialog + self.__editorCCDialog = CyclomaticComplexityDialog(self) + 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. @@ -834,4 +1037,3 @@ """ complexity</li>""" """</ul></p>""" ).format(__version__)) -