|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a dialog to show the licenses of an environment. |
|
8 """ |
|
9 |
|
10 import os |
|
11 import re |
|
12 |
|
13 from PyQt6.QtCore import pyqtSlot, Qt |
|
14 from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QTreeWidgetItem |
|
15 |
|
16 from EricGui.EricOverrideCursor import EricOverrideCursor |
|
17 from EricWidgets import EricFileDialog, EricMessageBox |
|
18 |
|
19 from .Ui_PipLicensesDialog import Ui_PipLicensesDialog |
|
20 |
|
21 |
|
22 class PipLicensesDialog(QDialog, Ui_PipLicensesDialog): |
|
23 """ |
|
24 Class implementing a dialog to show the licenses of an environment. |
|
25 """ |
|
26 LicensesPackageColumn = 0 |
|
27 LicensesVersionColumn = 1 |
|
28 LicensesLicenseColumn = 2 |
|
29 |
|
30 SummaryCountColumn = 0 |
|
31 SummaryLicenseColumn = 1 |
|
32 |
|
33 def __init__(self, pip, environment, localPackages=True, usersite=False, |
|
34 parent=None): |
|
35 """ |
|
36 Constructor |
|
37 |
|
38 @param pip reference to the pip interface object |
|
39 @type Pip |
|
40 @param environment name of the environment to show the licenses for |
|
41 @type str |
|
42 @param localPackages flag indicating to show the licenses for local |
|
43 packages only |
|
44 @type bool |
|
45 @param usersite flag indicating to show the licenses for packages |
|
46 installed in user-site directory only |
|
47 @type bool |
|
48 @param parent reference to the parent widget (defaults to None) |
|
49 @type QWidget (optional) |
|
50 """ |
|
51 super().__init__(parent) |
|
52 self.setupUi(self) |
|
53 |
|
54 self.__pip = pip |
|
55 self.__environment = environment |
|
56 |
|
57 self.__allFilter = self.tr("<All>") |
|
58 |
|
59 self.__saveCSVButton = self.buttonBox.addButton( |
|
60 self.tr("Save as CSV..."), QDialogButtonBox.ButtonRole.ActionRole) |
|
61 self.__saveCSVButton.clicked.connect(self.__saveAsCSV) |
|
62 |
|
63 self.buttonBox.button( |
|
64 QDialogButtonBox.StandardButton.Close).setDefault(True) |
|
65 |
|
66 self.localCheckBox.setChecked(localPackages) |
|
67 self.userCheckBox.setChecked(usersite) |
|
68 |
|
69 self.localCheckBox.toggled.connect(self.__refreshLicenses) |
|
70 self.userCheckBox.toggled.connect(self.__refreshLicenses) |
|
71 |
|
72 if environment: |
|
73 self.environmentLabel.setText("<b>{0}</b>".format( |
|
74 self.tr('Licenses of "{0}"').format(environment) |
|
75 )) |
|
76 else: |
|
77 # That should never happen; play it safe. |
|
78 self.environmentLabel.setText(self.tr("No environment specified.")) |
|
79 |
|
80 self.licenseFilterComboBox.currentTextChanged.connect( |
|
81 self.__filterPackagesByLicense) |
|
82 |
|
83 self.__refreshLicenses() |
|
84 |
|
85 @pyqtSlot() |
|
86 def __refreshLicenses(self): |
|
87 """ |
|
88 Private slot to refresh the license lists. |
|
89 """ |
|
90 with EricOverrideCursor(): |
|
91 self.licensesList.clear() |
|
92 self.summaryList.clear() |
|
93 self.licenseFilterComboBox.clear() |
|
94 |
|
95 licensesForFilter = set() |
|
96 |
|
97 # step 1: show the licenses per package |
|
98 self.licensesList.setUpdatesEnabled(False) |
|
99 licenses = self.__pip.getLicenses( |
|
100 self.__environment, |
|
101 localPackages=self.localCheckBox.isChecked(), |
|
102 usersite=self.userCheckBox.isChecked(), |
|
103 ) |
|
104 for lic in licenses: |
|
105 QTreeWidgetItem(self.licensesList, [ |
|
106 lic["Name"], |
|
107 lic["Version"], |
|
108 lic["License"].replace("; ", "\n"), |
|
109 ]) |
|
110 |
|
111 self.licensesList.sortItems( |
|
112 PipLicensesDialog.LicensesPackageColumn, |
|
113 Qt.SortOrder.AscendingOrder) |
|
114 for col in range(self.licensesList.columnCount()): |
|
115 self.licensesList.resizeColumnToContents(col) |
|
116 self.licensesList.setUpdatesEnabled(True) |
|
117 |
|
118 # step 2: show the licenses summary |
|
119 self.summaryList.setUpdatesEnabled(False) |
|
120 licenses = self.__pip.getLicensesSummary( |
|
121 self.__environment, |
|
122 localPackages=self.localCheckBox.isChecked(), |
|
123 usersite=self.userCheckBox.isChecked(), |
|
124 ) |
|
125 for lic in licenses: |
|
126 QTreeWidgetItem(self.summaryList, [ |
|
127 "{0:4d}".format(lic["Count"]), |
|
128 lic["License"].replace("; ", "\n"), |
|
129 ]) |
|
130 licensesForFilter |= set(lic["License"].split("; ")) |
|
131 |
|
132 self.summaryList.sortItems( |
|
133 PipLicensesDialog.SummaryLicenseColumn, |
|
134 Qt.SortOrder.AscendingOrder) |
|
135 for col in range(self.summaryList.columnCount()): |
|
136 self.summaryList.resizeColumnToContents(col) |
|
137 self.summaryList.setUpdatesEnabled(True) |
|
138 |
|
139 self.licenseFilterComboBox.addItems( |
|
140 [self.__allFilter] + sorted(licensesForFilter)) |
|
141 |
|
142 enable = bool(self.licensesList.topLevelItemCount()) |
|
143 self.__saveCSVButton.setEnabled(enable) |
|
144 |
|
145 @pyqtSlot(str) |
|
146 def __filterPackagesByLicense(self, licenseName): |
|
147 """ |
|
148 Private slot to filter the list of packages by license. |
|
149 |
|
150 @param licenseName license name |
|
151 @type str |
|
152 """ |
|
153 pattern = r"\b{0}".format(re.escape(licenseName)) |
|
154 if not licenseName.endswith((")", "]", "}")): |
|
155 pattern += r"\b" |
|
156 regexp = re.compile(pattern) |
|
157 for row in range(self.licensesList.topLevelItemCount()): |
|
158 itm = self.licensesList.topLevelItem(row) |
|
159 if licenseName == self.__allFilter: |
|
160 itm.setHidden(False) |
|
161 else: |
|
162 itm.setHidden( |
|
163 regexp.search( |
|
164 itm.text(PipLicensesDialog.LicensesLicenseColumn) |
|
165 ) is None |
|
166 ) |
|
167 |
|
168 @pyqtSlot() |
|
169 def __saveAsCSV(self): |
|
170 """ |
|
171 Private slot to save the license information as a CSV file. |
|
172 """ |
|
173 import csv |
|
174 |
|
175 fileName, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( |
|
176 self, |
|
177 self.tr("Save as CSV"), |
|
178 os.path.expanduser("~"), |
|
179 self.tr("CSV Files (*.csv);;All Files (*)"), |
|
180 None, |
|
181 EricFileDialog.DontConfirmOverwrite |
|
182 ) |
|
183 if fileName: |
|
184 ext = os.path.splitext(fileName)[1] |
|
185 if not ext: |
|
186 ex = selectedFilter.split("(*")[1].split(")")[0] |
|
187 if ex: |
|
188 fileName += ex |
|
189 |
|
190 try: |
|
191 with open(fileName, "w", newline="", |
|
192 encoding="utf-8") as csvFile: |
|
193 fieldNames = ["Name", "Version", "License"] |
|
194 writer = csv.DictWriter(csvFile, fieldnames=fieldNames) |
|
195 |
|
196 writer.writeheader() |
|
197 for row in range(self.licensesList.topLevelItemCount()): |
|
198 itm = self.licensesList.topLevelItem(row) |
|
199 writer.writerow({ |
|
200 "Name": itm.text(0), |
|
201 "Version": itm.text(1), |
|
202 "License": itm.text(2), |
|
203 }) |
|
204 except OSError as err: |
|
205 EricMessageBox.critical( |
|
206 self, |
|
207 self.tr("Save as CSV"), |
|
208 self.tr( |
|
209 """<p>The license information could not be saved""" |
|
210 """ into the CSV file <b>{0}</b>.</p>""" |
|
211 """<p>Reason: {1}</p>""" |
|
212 ).format(fileName, str(err)) |
|
213 ) |