src/eric7/PipInterface/PipLicensesDialog.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9108
19a57544f32c
child 9221
bf71ee032bb4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/PipInterface/PipLicensesDialog.py	Thu Jul 07 11:23:56 2022 +0200
@@ -0,0 +1,213 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to show the licenses of an environment.
+"""
+
+import os
+import re
+
+from PyQt6.QtCore import pyqtSlot, Qt
+from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QTreeWidgetItem
+
+from EricGui.EricOverrideCursor import EricOverrideCursor
+from EricWidgets import EricFileDialog, EricMessageBox
+
+from .Ui_PipLicensesDialog import Ui_PipLicensesDialog
+
+
+class PipLicensesDialog(QDialog, Ui_PipLicensesDialog):
+    """
+    Class implementing a dialog to show the licenses of an environment.
+    """
+    LicensesPackageColumn = 0
+    LicensesVersionColumn = 1
+    LicensesLicenseColumn = 2
+    
+    SummaryCountColumn = 0
+    SummaryLicenseColumn = 1
+    
+    def __init__(self, pip, environment, localPackages=True, usersite=False,
+                 parent=None):
+        """
+        Constructor
+        
+        @param pip reference to the pip interface object
+        @type Pip
+        @param environment name of the environment to show the licenses for
+        @type str
+        @param localPackages flag indicating to show the licenses for local
+            packages only
+        @type bool
+        @param usersite flag indicating to show the licenses for packages
+            installed in user-site directory only
+        @type bool
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+        
+        self.__pip = pip
+        self.__environment = environment
+        
+        self.__allFilter = self.tr("<All>")
+        
+        self.__saveCSVButton = self.buttonBox.addButton(
+            self.tr("Save as CSV..."), QDialogButtonBox.ButtonRole.ActionRole)
+        self.__saveCSVButton.clicked.connect(self.__saveAsCSV)
+        
+        self.buttonBox.button(
+            QDialogButtonBox.StandardButton.Close).setDefault(True)
+        
+        self.localCheckBox.setChecked(localPackages)
+        self.userCheckBox.setChecked(usersite)
+        
+        self.localCheckBox.toggled.connect(self.__refreshLicenses)
+        self.userCheckBox.toggled.connect(self.__refreshLicenses)
+        
+        if environment:
+            self.environmentLabel.setText("<b>{0}</b>".format(
+                self.tr('Licenses of "{0}"').format(environment)
+            ))
+        else:
+            # That should never happen; play it safe.
+            self.environmentLabel.setText(self.tr("No environment specified."))
+        
+        self.licenseFilterComboBox.currentTextChanged.connect(
+            self.__filterPackagesByLicense)
+        
+        self.__refreshLicenses()
+        
+    @pyqtSlot()
+    def __refreshLicenses(self):
+        """
+        Private slot to refresh the license lists.
+        """
+        with EricOverrideCursor():
+            self.licensesList.clear()
+            self.summaryList.clear()
+            self.licenseFilterComboBox.clear()
+            
+            licensesForFilter = set()
+            
+            # step 1: show the licenses per package
+            self.licensesList.setUpdatesEnabled(False)
+            licenses = self.__pip.getLicenses(
+                self.__environment,
+                localPackages=self.localCheckBox.isChecked(),
+                usersite=self.userCheckBox.isChecked(),
+            )
+            for lic in licenses:
+                QTreeWidgetItem(self.licensesList, [
+                    lic["Name"],
+                    lic["Version"],
+                    lic["License"].replace("; ", "\n"),
+                ])
+            
+            self.licensesList.sortItems(
+                PipLicensesDialog.LicensesPackageColumn,
+                Qt.SortOrder.AscendingOrder)
+            for col in range(self.licensesList.columnCount()):
+                self.licensesList.resizeColumnToContents(col)
+            self.licensesList.setUpdatesEnabled(True)
+            
+            # step 2: show the licenses summary
+            self.summaryList.setUpdatesEnabled(False)
+            licenses = self.__pip.getLicensesSummary(
+                self.__environment,
+                localPackages=self.localCheckBox.isChecked(),
+                usersite=self.userCheckBox.isChecked(),
+            )
+            for lic in licenses:
+                QTreeWidgetItem(self.summaryList, [
+                    "{0:4d}".format(lic["Count"]),
+                    lic["License"].replace("; ", "\n"),
+                ])
+                licensesForFilter |= set(lic["License"].split("; "))
+            
+            self.summaryList.sortItems(
+                PipLicensesDialog.SummaryLicenseColumn,
+                Qt.SortOrder.AscendingOrder)
+            for col in range(self.summaryList.columnCount()):
+                self.summaryList.resizeColumnToContents(col)
+            self.summaryList.setUpdatesEnabled(True)
+            
+            self.licenseFilterComboBox.addItems(
+                [self.__allFilter] + sorted(licensesForFilter))
+        
+        enable = bool(self.licensesList.topLevelItemCount())
+        self.__saveCSVButton.setEnabled(enable)
+    
+    @pyqtSlot(str)
+    def __filterPackagesByLicense(self, licenseName):
+        """
+        Private slot to filter the list of packages by license.
+        
+        @param licenseName license name
+        @type str
+        """
+        pattern = r"\b{0}".format(re.escape(licenseName))
+        if not licenseName.endswith((")", "]", "}")):
+            pattern += r"\b"
+        regexp = re.compile(pattern)
+        for row in range(self.licensesList.topLevelItemCount()):
+            itm = self.licensesList.topLevelItem(row)
+            if licenseName == self.__allFilter:
+                itm.setHidden(False)
+            else:
+                itm.setHidden(
+                    regexp.search(
+                        itm.text(PipLicensesDialog.LicensesLicenseColumn)
+                    ) is None
+                )
+    
+    @pyqtSlot()
+    def __saveAsCSV(self):
+        """
+        Private slot to save the license information as a CSV file.
+        """
+        import csv
+        
+        fileName, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
+            self,
+            self.tr("Save as CSV"),
+            os.path.expanduser("~"),
+            self.tr("CSV Files (*.csv);;All Files (*)"),
+            None,
+            EricFileDialog.DontConfirmOverwrite
+        )
+        if fileName:
+            ext = os.path.splitext(fileName)[1]
+            if not ext:
+                ex = selectedFilter.split("(*")[1].split(")")[0]
+                if ex:
+                    fileName += ex
+            
+            try:
+                with open(fileName, "w", newline="",
+                          encoding="utf-8") as csvFile:
+                    fieldNames = ["Name", "Version", "License"]
+                    writer = csv.DictWriter(csvFile, fieldnames=fieldNames)
+                    
+                    writer.writeheader()
+                    for row in range(self.licensesList.topLevelItemCount()):
+                        itm = self.licensesList.topLevelItem(row)
+                        writer.writerow({
+                            "Name": itm.text(0),
+                            "Version": itm.text(1),
+                            "License": itm.text(2),
+                        })
+            except OSError as err:
+                EricMessageBox.critical(
+                    self,
+                    self.tr("Save as CSV"),
+                    self.tr(
+                        """<p>The license information could not be saved"""
+                        """ into the CSV file <b>{0}</b>.</p>"""
+                        """<p>Reason: {1}</p>"""
+                    ).format(fileName, str(err))
+                )

eric ide

mercurial