Thu, 17 Sep 2015 19:57:14 +0200
Continued implementing the maintainability index stuff.
--- a/PluginMetricsRadon.py Wed Sep 16 20:07:48 2015 +0200 +++ b/PluginMetricsRadon.py Thu Sep 17 19:57:14 2015 +0200 @@ -49,11 +49,17 @@ @signal metricsDone(str, dict) emitted when the code metrics were determined for a file - @signal metricsError(str, str) emitted in case of an error + @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) emitted in case of an error @signal batchFinished() emitted when a code metrics batch is done """ metricsDone = pyqtSignal(str, dict) - metricsError = pyqtSignal(str, str) + maintainabilityIndexDone = pyqtSignal(str, dict) + complexityDone = pyqtSignal(str, dict) + error = pyqtSignal(str, str) batchFinished = pyqtSignal() def __init__(self, ui): @@ -73,23 +79,23 @@ try: self.backgroundService.serviceConnect( 'radon', 'Python2', path, 'CodeMetricsCalculator', - lambda *args: self.metricsDone.emit(*args), + self.metricsCalculationDone, onErrorCallback=self.serviceErrorPy2, onBatchDone=self.batchJobDone) self.backgroundService.serviceConnect( 'radon', 'Python3', path, 'CodeMetricsCalculator', - lambda *args: self.metricsDone.emit(*args), + self.metricsCalculationDone, onErrorCallback=self.serviceErrorPy3, onBatchDone=self.batchJobDone) self.hasBatch = True except TypeError: self.backgroundService.serviceConnect( 'radon', 'Python2', path, 'CodeMetricsCalculator', - lambda *args: self.metricsDone.emit(*args), + self.metricsCalculationDone, onErrorCallback=self.serviceErrorPy2) self.backgroundService.serviceConnect( 'radon', 'Python3', path, 'CodeMetricsCalculator', - lambda *args: self.metricsDone.emit(*args), + self.metricsCalculationDone, onErrorCallback=self.serviceErrorPy3) self.hasBatch = False @@ -108,7 +114,7 @@ @param msg message text @type str """ - self.metricsError.emit(fn, msg) + self.error.emit(fn, msg) def serviceErrorPy2(self, fx, lang, fn, msg): """ @@ -167,6 +173,30 @@ self.batchFinished.emit() self.batchesFinished = True + def metricsCalculationDone(self, filename, metricsType, result): + """ + Public slot to dispatch the result. + + @param filename name of the file the results belong to + @type str + @param metricsType type of the calculated metrics + @type str, one of ["raw", "mi", "cc"] + @param result result dictionary + @type dict + """ + if metricsType == "raw": + self.metricsDone.emit(filename, result) + elif metricsType == "mi": + self.maintainabilityIndexDone.emit(filename, result) + elif metricsType == "cc": + self.complexityDone.emit(filename, result) + else: + self.error.emit( + filename, + self.tr("Unknown metrics result received ({0}).").format( + metricsType) + ) + def __initialize(self): """ Private slot to (re)initialize the plugin.
--- a/RadonMetrics/CodeMetricsCalculator.py Wed Sep 16 20:07:48 2015 +0200 +++ b/RadonMetrics/CodeMetricsCalculator.py Thu Sep 17 19:57:14 2015 +0200 @@ -11,6 +11,7 @@ pass import multiprocessing +import sys def initService(): @@ -136,7 +137,7 @@ res = __raw2Dict(analyze(text)) except Exception as err: res = {"error": str(err)} - return (res, ) + return ("raw", res) def __raw2Dict(obj): @@ -170,10 +171,18 @@ @rtype (tuple of dict) """ from radon.metrics import mi_visit, mi_rank + + # Check type for py2: if not str it's unicode + if sys.version_info[0] == 2: + try: + text = text.encode('utf-8') + except UnicodeError: + pass + try: - mi = mi_visit(text) + mi = mi_visit(text, True) rank = mi_rank(mi) res = {"mi": mi, "rank": rank} except Exception as err: res = {"error": str(err)} - return (res, ) + return ("mi", res)
--- a/RadonMetrics/MaintainabilityIndexDialog.py Wed Sep 16 20:07:48 2015 +0200 +++ b/RadonMetrics/MaintainabilityIndexDialog.py Thu Sep 17 19:57:14 2015 +0200 @@ -53,14 +53,16 @@ self.resultList.headerItem().setText(self.resultList.columnCount(), "") self.radonService = radonService - self.radonService.metricsDone.connect(self.__processResult) - self.radonService.metricsError.connect(self.__processError) + self.radonService.maintainabilityIndexDone.connect( + self.__processResult) + self.radonService.error.connect(self.__processError) self.radonService.batchFinished.connect(self.__batchFinished) self.cancelled = False self.__project = e5App().getObject("Project") self.__locale = QLocale() + self.__finished = True self.__fileList = [] self.filterFrame.setVisible(False) @@ -90,6 +92,7 @@ @param values values to be displayed @type dict """ + # TODO: colorize the rank column according to rank (green, orange, red) data = [self.__project.getRelativePath(filename)] try: data.append(self.__locale.toString(float(values["mi"]), "f", 2)) @@ -99,6 +102,9 @@ itm = QTreeWidgetItem(self.resultList, data) itm.setTextAlignment(1, Qt.Alignment(Qt.AlignRight)) itm.setTextAlignment(2, Qt.Alignment(Qt.AlignHCenter)) + + if values["rank"] in ["A", "B", "C"]: + self.__summary[values["rank"]] += 1 def __createErrorItem(self, filename, message): """ @@ -172,9 +178,11 @@ if not os.path.exists(f): self.files.remove(f) - self.__summary = {"files": 0} - for key in ['loc', 'sloc', 'lloc', 'comments', 'multi', 'blank']: - self.__summary[key] = 0 + self.__summary = { + "A": 0, + "B": 0, + "C": 0, + } if len(self.files) > 0: # disable updates of the list for speed @@ -194,3 +202,208 @@ else: self.__batch = True self.maintainabilityIndexBatch() + + def maintainabilityIndex(self, codestring=''): + """ + Public method to start a maintainability index calculation for one + Python file. + + The results are reported to the __processResult slot. + + @keyparam codestring optional sourcestring + @type str + """ + if not self.files: + self.checkProgressLabel.setPath("") + self.checkProgress.setMaximum(1) + self.checkProgress.setValue(1) + self.__finish() + return + + self.filename = self.files.pop(0) + self.checkProgress.setValue(self.progress) + self.checkProgressLabel.setPath(self.filename) + QApplication.processEvents() + + if self.cancelled: + return + + try: + self.source = Utilities.readEncodedFile(self.filename)[0] + self.source = Utilities.normalizeCode(self.source) + except (UnicodeError, IOError) as msg: + self.__createErrorItem(self.filename, str(msg).rstrip()) + self.progress += 1 + # Continue with next file + self.rawMetrics() + return + + self.__finished = False + self.radonService.maintainabilityIndex( + None, self.filename, self.source) + + def maintainabilityIndexBatch(self): + """ + Public method to start a maintainability index calculation batch job. + + The results are reported to the __processResult slot. + """ + self.__lastFileItem = None + + self.checkProgressLabel.setPath(self.tr("Preparing files...")) + progress = 0 + + argumentsList = [] + for filename in self.files: + progress += 1 + self.checkProgress.setValue(progress) + QApplication.processEvents() + + try: + source = Utilities.readEncodedFile(filename)[0] + source = Utilities.normalizeCode(source) + except (UnicodeError, IOError) as msg: + self.__createErrorItem(filename, str(msg).rstrip()) + continue + + argumentsList.append((filename, source)) + + # reset the progress bar to the checked files + self.checkProgress.setValue(self.progress) + QApplication.processEvents() + + self.__finished = False + self.radonService.maintainabilityIndexBatch(argumentsList) + + def __batchFinished(self): + """ + Private slot handling the completion of a batch job. + """ + self.checkProgressLabel.setPath("") + self.checkProgress.setMaximum(1) + self.checkProgress.setValue(1) + self.__finish() + + def __processError(self, fn, msg): + """ + Private slot to process an error indication from the service. + + @param fn filename of the file + @type str + @param msg error message + @type str + """ + self.__createErrorItem(fn, msg) + + def __processResult(self, fn, result): + """ + Private slot called after perfoming a maintainability index calculation + on one file. + + @param fn filename of the file + @type str + @param result result dict + @type dict + """ + if self.__finished: + return + + # Check if it's the requested file, otherwise ignore signal if not + # in batch mode + if not self.__batch and fn != self.filename: + return + + if "error" in result: + self.__createErrorItem(fn, result["error"]) + else: + self.__createResultItem(fn, result) + + self.progress += 1 + + self.checkProgress.setValue(self.progress) + self.checkProgressLabel.setPath(fn) + QApplication.processEvents() + + if not self.__batch: + self.maintainabilityIndex() + + def __finish(self): + """ + Private slot called when the action or the user pressed the button. + """ + if not self.__finished: + self.__finished = True + + # reenable updates of the list + self.resultList.setSortingEnabled(True) + self.resultList.setUpdatesEnabled(True) + + self.cancelled = True + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) + + self.resultList.header().resizeSections( + QHeaderView.ResizeToContents) + self.resultList.header().setStretchLastSection(True) + if qVersion() >= "5.0.0": + self.resultList.header().setSectionResizeMode( + QHeaderView.Interactive) + else: + self.resultList.header().setResizeMode(QHeaderView.Interactive) + + self.summaryLabel.setText(self.tr( + "<table>" + "<tr><td colspan=2><b>Summary:</b></td></tr>" + "<tr><td><b>A</b></td><td>{0} files</td></tr>" + "<tr><td><b>B</b></td><td>{1} files</td></tr>" + "<tr><td><b>C</b></td><td>{2} files</td></tr>" + "</table>" + ).format(self.__summary["A"], + self.__summary["B"], + self.__summary["C"]) + ) + + self.checkProgress.setVisible(False) + self.checkProgressLabel.setVisible(False) + + @pyqtSlot(QAbstractButton) + def on_buttonBox_clicked(self, button): + """ + Private slot called by a button of the button box clicked. + + @param button button that was clicked + @type QAbstractButton + """ + if button == self.buttonBox.button(QDialogButtonBox.Close): + self.close() + elif button == self.buttonBox.button(QDialogButtonBox.Cancel): + if self.__batch: + self.radonService.cancelMaintainabilityIndexBatch() + QTimer.singleShot(1000, self.__finish) + else: + self.__finish() + + @pyqtSlot() + def on_startButton_clicked(self): + """ + Private slot to start a maintainability index run. + """ + fileList = self.__fileList[:] + + filterString = self.excludeFilesEdit.text() + if "ExcludeFiles" not in self.__data or \ + filterString != self.__data["ExcludeFiles"]: + self.__data["ExcludeFiles"] = filterString + self.__project.setData( + "OTHERTOOLSPARMS", "RadonCodeMetrics", self.__data) + filterList = [f.strip() for f in filterString.split(",") + if f.strip()] + if filterList: + for filter in filterList: + fileList = \ + [f for f in fileList if not fnmatch.fnmatch(f, filter)] + + self.resultList.clear() + self.cancelled = False + self.start(fileList)
--- a/RadonMetrics/MaintainabilityIndexDialog.ui Wed Sep 16 20:07:48 2015 +0200 +++ b/RadonMetrics/MaintainabilityIndexDialog.ui Thu Sep 17 19:57:14 2015 +0200 @@ -102,14 +102,28 @@ </widget> </item> <item> - <widget class="QLabel" name="explanationLabel"> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="summaryLabel"> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="explanationLabel"> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> </item> <item> <widget class="E5SqueezeLabelPath" name="checkProgressLabel">
--- a/RadonMetrics/RawMetricsDialog.py Wed Sep 16 20:07:48 2015 +0200 +++ b/RadonMetrics/RawMetricsDialog.py Thu Sep 17 19:57:14 2015 +0200 @@ -60,13 +60,14 @@ self.radonService = radonService self.radonService.metricsDone.connect(self.__processResult) - self.radonService.metricsError.connect(self.__processError) + self.radonService.error.connect(self.__processError) self.radonService.batchFinished.connect(self.__batchFinished) self.cancelled = False self.__project = e5App().getObject("Project") self.__locale = QLocale() + self.__finished = True self.__fileList = [] self.filterFrame.setVisible(False)