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