|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a dialog to edit the metadata of the CycloneDX SBOM. |
|
8 """ |
|
9 |
|
10 import os |
|
11 |
|
12 from PyQt6.QtCore import pyqtSlot, QCoreApplication |
|
13 from PyQt6.QtWidgets import QDialog, QDialogButtonBox |
|
14 |
|
15 from EricWidgets import EricMessageBox |
|
16 |
|
17 from cyclonedx.model.component import ComponentType |
|
18 |
|
19 from .Ui_CycloneDXMetaDataDialog import Ui_CycloneDXMetaDataDialog |
|
20 |
|
21 |
|
22 class CycloneDXMetaDataDialog(QDialog, Ui_CycloneDXMetaDataDialog): |
|
23 """ |
|
24 Class implementing a dialog to edit the metadata of the CycloneDX SBOM. |
|
25 """ |
|
26 ComponentTypeMapping = { |
|
27 ComponentType.APPLICATION: QCoreApplication.translate( |
|
28 "CycloneDXMetaDataDialog", "Application"), |
|
29 ComponentType.CONTAINER: QCoreApplication.translate( |
|
30 "CycloneDXMetaDataDialog", "Container"), |
|
31 ComponentType.DEVICE: QCoreApplication.translate( |
|
32 "CycloneDXMetaDataDialog", "Device"), |
|
33 ComponentType.FILE: QCoreApplication.translate( |
|
34 "CycloneDXMetaDataDialog", "File"), |
|
35 ComponentType.FIRMWARE: QCoreApplication.translate( |
|
36 "CycloneDXMetaDataDialog", "Firmware"), |
|
37 ComponentType.FRAMEWORK: QCoreApplication.translate( |
|
38 "CycloneDXMetaDataDialog", "Framework"), |
|
39 ComponentType.LIBRARY: QCoreApplication.translate( |
|
40 "CycloneDXMetaDataDialog", "Library"), |
|
41 ComponentType.OPERATING_SYSTEM: QCoreApplication.translate( |
|
42 "CycloneDXMetaDataDialog", "Operating System"), |
|
43 } |
|
44 |
|
45 def __init__(self, metadata=None, parent=None): |
|
46 """ |
|
47 Constructor |
|
48 |
|
49 @param metadata dictionary containing metadata to populate the dialog |
|
50 (defaults to None) |
|
51 @type dict (optional) |
|
52 @param parent reference to the parent widget (defaults to None) |
|
53 @type QWidget (optional) |
|
54 """ |
|
55 super().__init__(parent) |
|
56 self.setupUi(self) |
|
57 |
|
58 self.__populateComponentTypeSelector() |
|
59 self.__populateLicenseSelector() |
|
60 |
|
61 if metadata: |
|
62 # populate the dialog from given metadata dictionary |
|
63 self.nameEdit.setText(metadata["Name"]) |
|
64 self.versionEdit.setText(metadata["Version"]) |
|
65 self.descriptionEdit.setPlainText(metadata["Description"]) |
|
66 self.authorEdit.setText(metadata["AuthorName"]) |
|
67 self.emailEdit.setText(metadata["AuthorEmail"]) |
|
68 self.licenseComboBox.setCurrentText(metadata["License"]) |
|
69 self.manufacturerEdit.setText(metadata["Manufacturer"]) |
|
70 self.supplierEdit.setText(metadata["Supplier"]) |
|
71 index = self.typeComboBox.findData(metadata["Type"]) |
|
72 self.typeComboBox.setCurrentIndex(index) |
|
73 |
|
74 self.nameEdit.textChanged.connect(self.__updateOkButton) |
|
75 self.typeComboBox.currentTextChanged.connect(self.__updateOkButton) |
|
76 self.licenseComboBox.currentTextChanged.connect(self.__updateOkButton) |
|
77 |
|
78 self.__updateOkButton() |
|
79 |
|
80 def __populateComponentTypeSelector(self): |
|
81 """ |
|
82 Private method to populate the component type selector. |
|
83 """ |
|
84 self.typeComboBox.addItem("", "") |
|
85 for componentType, displayStr in sorted( |
|
86 CycloneDXMetaDataDialog.ComponentTypeMapping.items(), |
|
87 key=lambda x: x[1] |
|
88 ): |
|
89 self.typeComboBox.addItem(displayStr, componentType) |
|
90 |
|
91 def __populateLicenseSelector(self): |
|
92 """ |
|
93 Private method to populate the license selector with the list of trove |
|
94 license types. |
|
95 |
|
96 Note: The trove licanese list file was created from querying |
|
97 "https://pypi.org/pypi?%3Aaction=list_classifiers". |
|
98 """ |
|
99 filename = os.path.join( |
|
100 os.path.dirname(__file__), "..", "data", |
|
101 "trove_license_classifiers.txt") |
|
102 try: |
|
103 with open(filename, "r") as f: |
|
104 lines = f.readlines() |
|
105 except OSError as err: |
|
106 EricMessageBox.warning( |
|
107 self, |
|
108 self.tr("Reading Trove License Classifiers"), |
|
109 self.tr("""<p>The Trove License Classifiers file <b>{0}</b>""" |
|
110 """ could not be read.</p><p>Reason: {1}</p>""") |
|
111 .format(filename, str(err))) |
|
112 return |
|
113 |
|
114 self.licenseComboBox.addItem("") |
|
115 self.licenseComboBox.addItems(sorted( |
|
116 line.split("::")[-1].strip() |
|
117 for line in lines |
|
118 if line.startswith("License ") # play it safe |
|
119 )) |
|
120 |
|
121 @pyqtSlot() |
|
122 def __updateOkButton(self): |
|
123 """ |
|
124 Private slot to update the enabled state of the OK button. |
|
125 """ |
|
126 self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled( |
|
127 bool(self.nameEdit.text()) and |
|
128 bool(self.typeComboBox.currentText()) and |
|
129 bool(self.licenseComboBox.currentText()) |
|
130 ) |
|
131 |
|
132 def getMetaData(self): |
|
133 """ |
|
134 Public method to get the entered data. |
|
135 |
|
136 @return dictionary containing the metadata. |
|
137 @rtype dict |
|
138 """ |
|
139 return { |
|
140 "Name": self.nameEdit.text(), |
|
141 "Type": self.typeComboBox.currentData(), |
|
142 "Version": self.versionEdit.text(), |
|
143 "Description": self.descriptionEdit.toPlainText(), |
|
144 "AuthorName": self.authorEdit.text(), |
|
145 "AuthorEmail": self.emailEdit.text(), |
|
146 "License": self.licenseComboBox.currentText(), |
|
147 "Manufacturer": self.manufacturerEdit.text(), |
|
148 "Supplier": self.supplierEdit.text(), |
|
149 } |