--- a/eric7/DataViews/PyCoverageDialog.py Wed May 18 11:02:59 2022 +0200 +++ b/eric7/DataViews/PyCoverageDialog.py Thu May 19 14:37:01 2022 +0200 @@ -7,11 +7,11 @@ Module implementing a Python code coverage dialog. """ -import contextlib import os import time -from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt +from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl +from PyQt6.QtGui import QDesktopServices from PyQt6.QtWidgets import ( QDialog, QDialogButtonBox, QMenu, QHeaderView, QTreeWidgetItem, QApplication @@ -19,7 +19,6 @@ from EricWidgets import EricMessageBox from EricWidgets.EricApplication import ericApp -from EricWidgets.EricProgressDialog import EricProgressDialog from .Ui_PyCoverageDialog import Ui_PyCoverageDialog @@ -62,16 +61,17 @@ self.excludeList = ['# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]'] + self.__reportsMenu = QMenu(self.tr("Create Report"), self) + self.__reportsMenu.addAction(self.tr("HTML Report"), self.__htmlReport) + self.__reportsMenu.addAction(self.tr("JSON Report"), self.__jsonReport) + self.__reportsMenu.addAction(self.tr("LCOV Report"), self.__lcovReport) + self.__menu = QMenu(self) self.__menu.addSeparator() self.openAct = self.__menu.addAction( self.tr("Open"), self.__openFile) self.__menu.addSeparator() - self.annotate = self.__menu.addAction( - self.tr('Annotate'), self.__annotate) - self.__menu.addAction(self.tr('Annotate all'), self.__annotateAll) - self.__menu.addAction( - self.tr('Delete annotated files'), self.__deleteAnnotated) + self.__menu.addMenu(self.__reportsMenu) self.__menu.addSeparator() self.__menu.addAction(self.tr('Erase Coverage Info'), self.__erase) self.resultList.setContextMenuPolicy( @@ -314,11 +314,11 @@ """ itm = self.resultList.itemAt(coord) if itm: - self.annotate.setEnabled(True) self.openAct.setEnabled(True) else: - self.annotate.setEnabled(False) self.openAct.setEnabled(False) + self.__reportsMenu.setEnabled( + bool(self.resultList.topLevelItemCount())) self.__menu.popup(self.mapToGlobal(coord)) def __openFile(self, itm=None): @@ -340,58 +340,85 @@ except KeyError: self.openFile.emit(fn) - # TODO: Coverage.annotate is deprecated - def __annotate(self): + def __prepareReportGeneration(self): """ - Private slot to handle the annotate context menu action. - - This method produces an annotated coverage file of the - selected file. - """ - itm = self.resultList.currentItem() - fn = itm.text(0) + Private method to prepare a report generation. - cover = Coverage(data_file=self.cfn) - cover.exclude(self.excludeList[0]) - cover.load() - cover.annotate([fn], None, True) - - # TODO: Coverage.annotate is deprecated - def __annotateAll(self): + @return tuple containing a reference to the Coverage object and the + list of files to report + @rtype tuple of (Coverage, list of str) """ - Private slot to handle the annotate all context menu action. - - This method produce an annotated coverage file of every - file listed in the listview. - """ - amount = self.resultList.topLevelItemCount() - if amount == 0: - return + count = self.resultList.topLevelItemCount() + if count == 0: + return None, [] # get list of all filenames - files = [] - for index in range(amount): - itm = self.resultList.topLevelItem(index) - files.append(itm.text(0)) + files = [ + self.resultList.topLevelItem(index).text(0) + for index in range(count) + ] cover = Coverage(data_file=self.cfn) cover.exclude(self.excludeList[0]) cover.load() - # now process them - progress = EricProgressDialog( - self.tr("Annotating files..."), self.tr("Abort"), - 0, len(files), self.tr("%v/%m Files"), self) - progress.setMinimumDuration(0) - progress.setWindowTitle(self.tr("Coverage")) + return cover, files + + @pyqtSlot() + def __htmlReport(self): + """ + Private slot to generate a HTML report of the shown data. + """ + from .PyCoverageHtmlReportDialog import PyCoverageHtmlReportDialog + + dlg = PyCoverageHtmlReportDialog(os.path.dirname(self.cfn), self) + if dlg.exec() == QDialog.DialogCode.Accepted: + title, outputDirectory, extraCSS, openReport = dlg.getData() + + cover, files = self.__prepareReportGeneration() + cover.html_report(morfs=files, directory=outputDirectory, + ignore_errors=True, extra_css=extraCSS, + title=title) + + if openReport: + QDesktopServices.openUrl(QUrl.fromLocalFile(os.path.join( + outputDirectory, "index.html"))) + + @pyqtSlot() + def __jsonReport(self): + """ + Private slot to generate a JSON report of the shown data. + """ + from .PyCoverageJsonReportDialog import PyCoverageJsonReportDialog - for count, file in enumerate(files): - progress.setValue(count) - if progress.wasCanceled(): - break - cover.annotate([file], None) # , True) + dlg = PyCoverageJsonReportDialog(os.path.dirname(self.cfn), self) + if dlg.exec() == QDialog.DialogCode.Accepted: + filename, compact = dlg.getData() + cover, files = self.__prepareReportGeneration() + cover.json_report(morfs=files, outfile=filename, + ignore_errors=True, pretty_print=not compact) + + @pyqtSlot() + def __lcovReport(self): + """ + Private slot to generate a LCOV report of the shown data. + """ + from EricWidgets import EricPathPickerDialog + from EricWidgets.EricPathPicker import EricPathPickerModes - progress.setValue(len(files)) + filename, ok = EricPathPickerDialog.getPath( + self, + self.tr("LCOV Report"), + self.tr("Enter the path of the output file:"), + mode=EricPathPickerModes.SAVE_FILE_ENSURE_EXTENSION_MODE, + path=os.path.join(os.path.dirname(self.cfn), "coverage.lcov"), + defaultDirectory=os.path.dirname(self.cfn), + filters=self.tr("LCOV Files (*.lcov);;All Files (*)") + ) + if ok: + cover, files = self.__prepareReportGeneration() + cover.lcov_report(morfs=files, outfile=filename, + ignore_errors=True) def __erase(self): """ @@ -408,18 +435,6 @@ self.resultList.clear() self.summaryList.clear() - def __deleteAnnotated(self): - """ - Private slot to handle the delete annotated context menu action. - - This method deletes all annotated files. These are files - ending with ',cover'. - """ - files = Utilities.direntries(self.path, True, '*,cover', False) - for file in files: - with contextlib.suppress(OSError): - os.remove(file) - @pyqtSlot() def on_reloadButton_clicked(self): """