|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2015 - 2017 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a dialog to show details about a package. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 try: |
|
12 basestring # __IGNORE_WARNING__ |
|
13 except NameError: |
|
14 basestring = str |
|
15 |
|
16 from PyQt5.QtCore import Qt, QLocale |
|
17 from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QTreeWidgetItem, \ |
|
18 QLabel, QHeaderView |
|
19 |
|
20 from .Ui_PipPackageDetailsDialog import Ui_PipPackageDetailsDialog |
|
21 |
|
22 |
|
23 class PipPackageDetailsDialog(QDialog, Ui_PipPackageDetailsDialog): |
|
24 """ |
|
25 Class documentation goes here. |
|
26 """ |
|
27 def __init__(self, detailsData, downloadsData, parent=None): |
|
28 """ |
|
29 Constructor |
|
30 |
|
31 @param detailsData package details (dict) |
|
32 @param downloadsData downloads information (dict) |
|
33 @param parent reference to the parent widget (QWidget) |
|
34 """ |
|
35 super(PipPackageDetailsDialog, self).__init__(parent) |
|
36 self.setupUi(self) |
|
37 |
|
38 self.__locale = QLocale() |
|
39 self.__packageTypeMap = { |
|
40 "sdist": self.tr("Source"), |
|
41 "bdist_wheel": self.tr("Python Wheel"), |
|
42 "bdist_egg": self.tr("Python Egg"), |
|
43 "bdist_wininst": self.tr("MS Windows Installer"), |
|
44 "bdist_msi": self.tr("MS Windows Installer"), |
|
45 "bdist_rpm": self.tr("Unix Installer"), |
|
46 "bdist_deb": self.tr("Unix Installer"), |
|
47 "bdist_dumb": self.tr("Archive"), |
|
48 } |
|
49 |
|
50 self.__populateDetails(detailsData) |
|
51 self.__populateDownloadUrls(downloadsData) |
|
52 self.__populateRequiresProvides(detailsData) |
|
53 |
|
54 def __populateDetails(self, detailsData): |
|
55 """ |
|
56 Private method to populate the details tab. |
|
57 |
|
58 @param detailsData package details (dict) |
|
59 """ |
|
60 self.packageNameLabel.setText( |
|
61 "<h1>{0} {1}</h1".format(self.__sanitize(detailsData["name"]), |
|
62 self.__sanitize(detailsData["version"]))) |
|
63 self.summaryLabel.setText( |
|
64 self.__sanitize(detailsData["summary"][:240])) |
|
65 self.descriptionEdit.setPlainText( |
|
66 self.__sanitize(detailsData["description"])) |
|
67 self.authorLabel.setText(self.__sanitize(detailsData["author"])) |
|
68 self.authorEmailLabel.setText( |
|
69 '<a href="mailto:{0}">{0}</a>'.format( |
|
70 self.__sanitize(detailsData["author_email"]))) |
|
71 self.licenseLabel.setText(self.__sanitize(detailsData["license"])) |
|
72 self.platformLabel.setText(self.__sanitize(detailsData["platform"])) |
|
73 self.homePageLabel.setText( |
|
74 '<a href="{0}">{0}</a>'.format( |
|
75 self.__sanitize(detailsData["home_page"], forUrl=True))) |
|
76 self.packageUrlLabel.setText( |
|
77 '<a href="{0}">{0}</a>'.format( |
|
78 self.__sanitize(detailsData["package_url"], forUrl=True))) |
|
79 self.releaseUrlLabel.setText( |
|
80 '<a href="{0}">{0}</a>'.format( |
|
81 self.__sanitize(detailsData["release_url"], forUrl=True))) |
|
82 self.docsUrlLabel.setText( |
|
83 '<a href="{0}">{0}</a>'.format( |
|
84 self.__sanitize(detailsData["docs_url"], forUrl=True))) |
|
85 self.downloadsDayLabel.setText(self.__locale.toString( |
|
86 detailsData["downloads"]["last_day"])) |
|
87 self.downloadsWeekLabel.setText(self.__locale.toString( |
|
88 detailsData["downloads"]["last_week"])) |
|
89 self.downloadsMonthLabel.setText(self.__locale.toString( |
|
90 detailsData["downloads"]["last_month"])) |
|
91 self.classifiersList.addItems(detailsData["classifiers"]) |
|
92 |
|
93 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) |
|
94 self.buttonBox.button(QDialogButtonBox.Close).setFocus( |
|
95 Qt.OtherFocusReason) |
|
96 |
|
97 def __populateDownloadUrls(self, downloadsData): |
|
98 """ |
|
99 Private method to populate the download URLs tab. |
|
100 |
|
101 @param downloadsData downloads information (dict) |
|
102 """ |
|
103 index = self.infoWidget.indexOf(self.urls) |
|
104 if downloadsData: |
|
105 self.infoWidget.setTabEnabled(index, True) |
|
106 for download in downloadsData: |
|
107 itm = QTreeWidgetItem(self.downloadUrlsList, [ |
|
108 "", |
|
109 self.__packageTypeMap[download["packagetype"]] |
|
110 if download["packagetype"] in self.__packageTypeMap |
|
111 else "", |
|
112 download["python_version"] |
|
113 if download["python_version"] != "source" |
|
114 else "", |
|
115 self.__locale.toString(download["downloads"]), |
|
116 self.__formatUploadDate(download["upload_time"]), |
|
117 self.__formatSize(download["size"]), |
|
118 ]) |
|
119 if download["has_sig"]: |
|
120 pgpLink = ' (<a href="{0}">pgp</a>)'.format( |
|
121 download["url"] + ".asc") |
|
122 else: |
|
123 pgpLink = "" |
|
124 urlLabel = QLabel('<a href="{0}#md5={2}">{1}</a>{3}'.format( |
|
125 download["url"], download["filename"], |
|
126 download["md5_digest"], pgpLink)) |
|
127 urlLabel.setTextInteractionFlags(Qt.LinksAccessibleByMouse) |
|
128 urlLabel.setOpenExternalLinks(True) |
|
129 self.downloadUrlsList.setItemWidget(itm, 0, urlLabel) |
|
130 header = self.downloadUrlsList.header() |
|
131 header.resizeSections(QHeaderView.ResizeToContents) |
|
132 else: |
|
133 self.infoWidget.setTabEnabled(index, False) |
|
134 |
|
135 def __populateRequiresProvides(self, detailsData): |
|
136 """ |
|
137 Private method to populate the requires/provides tab. |
|
138 |
|
139 @param detailsData package details (dict) |
|
140 """ |
|
141 populated = False |
|
142 |
|
143 if "requires" in detailsData: |
|
144 self.requiredPackagesList.addItems(detailsData["requires"]) |
|
145 populated = True |
|
146 if "requires_dist" in detailsData: |
|
147 self.requiredDistributionsList.addItems( |
|
148 detailsData["requires_dist"]) |
|
149 populated = True |
|
150 if "provides" in detailsData: |
|
151 self.providedPackagesList.addItems(detailsData["provides"]) |
|
152 populated = True |
|
153 if "provides_dist" in detailsData: |
|
154 self.providedDistributionsList.addItems( |
|
155 detailsData["provides_dist"]) |
|
156 populated = True |
|
157 |
|
158 index = self.infoWidget.indexOf(self.requires) |
|
159 self.infoWidget.setTabEnabled(index, populated) |
|
160 |
|
161 def __sanitize(self, text, forUrl=False): |
|
162 """ |
|
163 Private method to clean-up the given text. |
|
164 |
|
165 @param text raw text (string) |
|
166 @param forUrl flag indicating to sanitize an URL text (boolean) |
|
167 @return processed text (string) |
|
168 """ |
|
169 if text == "UNKNOWN": |
|
170 text = "" |
|
171 elif text == "any": |
|
172 text = self.tr("any") |
|
173 elif text is None: |
|
174 text = "" |
|
175 if forUrl: |
|
176 if not isinstance(text, basestring) or \ |
|
177 not text.startswith(("http://", "https://", "ftp://")): |
|
178 # ignore if the schema is not one of the listed ones |
|
179 text = "" |
|
180 |
|
181 return text |
|
182 |
|
183 def __formatUploadDate(self, datetime): |
|
184 """ |
|
185 Private method to format the upload date. |
|
186 |
|
187 @param datetime upload date and time (xmlrpc.DateTime) |
|
188 @return formatted date string (string) |
|
189 """ |
|
190 date = datetime.value.split("T")[0] |
|
191 return "{0}-{1}-{2}".format(date[:4], date[4:6], date[6:]) |
|
192 |
|
193 def __formatSize(self, size): |
|
194 """ |
|
195 Private slot to format the size. |
|
196 |
|
197 @param size size to be formatted (integer) |
|
198 @return formatted size (string) |
|
199 """ |
|
200 unit = "" |
|
201 if size < 1024: |
|
202 unit = self.tr("B") |
|
203 elif size < 1024 * 1024: |
|
204 size /= 1024 |
|
205 unit = self.tr("KB") |
|
206 elif size < 1024 * 1024 * 1024: |
|
207 size /= 1024 * 1024 |
|
208 unit = self.tr("MB") |
|
209 else: |
|
210 size /= 1024 * 1024 * 1024 |
|
211 unit = self.tr("GB") |
|
212 return self.tr("{0:.1f} {1}", "value, unit").format(size, unit) |