src/eric7/Plugins/WizardPlugins/SetupWizard/SetupWizardDialog.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9205
b75da2ba2a1a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2013 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the setup.py wizard dialog.
8 """
9
10 import configparser
11 import datetime
12 import io
13 import os
14 import pathlib
15
16 import tomlkit
17
18 import trove_classifiers
19
20 from PyQt6.QtCore import pyqtSlot, Qt
21 from PyQt6.QtWidgets import (
22 QDialog, QDialogButtonBox, QTreeWidgetItem, QListWidgetItem
23 )
24
25 from EricWidgets.EricApplication import ericApp
26 from EricWidgets import EricFileDialog
27 from EricWidgets.EricPathPicker import EricPathPickerModes
28
29 from .AddEntryPointDialog import AddEntryPointDialog
30 from .AddProjectUrlDialog import AddProjectUrlDialog
31 from .Ui_SetupWizardDialog import Ui_SetupWizardDialog
32
33 import Utilities
34 import Preferences
35
36
37 class SetupWizardDialog(QDialog, Ui_SetupWizardDialog):
38 """
39 Class implementing the setup.py wizard dialog.
40
41 It displays a dialog for entering the parameters for the setup.py code
42 generator.
43 """
44 def __init__(self, category, editor, parent=None):
45 """
46 Constructor
47
48 @param category category of setup file to create
49 @type str
50 @param editor reference to the editor object to receive the code
51 @type Editor
52 @param parent reference to the parent widget (defaults to None)
53 @type QWidget (optional)
54 @exception ValueError raised for an illegal setup file category
55 """
56 if category not in ("setup.py", "setup.cfg", "pyproject.toml"):
57 raise ValueError("illegal setup file category given")
58
59 super().__init__(parent)
60 self.setupUi(self)
61
62 self.setWindowTitle(self.tr("{0} Wizard").format(category))
63
64 self.__replies = []
65 self.__category = category
66 self.__editor = editor
67
68 if category != "setup.py":
69 self.introCheckBox.setVisible(False)
70 self.importCheckBox.setVisible(False)
71 self.metaDataCheckBox.setVisible(False)
72
73 self.dataTabWidget.setCurrentIndex(0)
74
75 self.packageRootPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
76 self.sourceDirectoryPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
77
78 self.__mandatoryStyleSheet = (
79 "QLineEdit {border: 2px solid; border-color: #dd8888}"
80 if ericApp().usesDarkPalette() else
81 "QLineEdit {border: 2px solid; border-color: #800000}"
82 )
83 for lineEdit in [self.nameEdit, self.versionEdit,
84 self.homePageUrlEdit, self.authorEdit,
85 self.authorEmailEdit, self.maintainerEdit,
86 self.maintainerEmailEdit]:
87 lineEdit.setStyleSheet(self.__mandatoryStyleSheet)
88
89 self.__populateClassifiers()
90
91 self.__okButton = self.buttonBox.button(
92 QDialogButtonBox.StandardButton.Ok)
93 self.__okButton.setEnabled(False)
94
95 projectOpen = ericApp().getObject("Project").isOpen()
96 self.projectButton.setEnabled(projectOpen)
97
98 self.projectUrlsList.header().setSortIndicator(0, Qt.SortOrder.AscendingOrder)
99 self.entryPointsList.header().setSortIndicator(0, Qt.SortOrder.AscendingOrder)
100
101 self.descriptionContentTypeComboBox.addItem("", "")
102 for contentType, mimetype in sorted([
103 (self.tr("Plain Text"), "text/plain"),
104 (self.tr("Markdown"), "text/markdown"),
105 (self.tr("reStructuredText"), "text/x-rst")
106
107 ]):
108 self.descriptionContentTypeComboBox.addItem(contentType, mimetype)
109
110 self.homePageUrlEdit.textChanged.connect(self.__enableOkButton)
111 self.nameEdit.textChanged.connect(self.__enableOkButton)
112 self.versionEdit.textChanged.connect(self.__enableOkButton)
113 self.authorEdit.textChanged.connect(self.__enableOkButton)
114 self.authorEmailEdit.textChanged.connect(self.__enableOkButton)
115 self.maintainerEdit.textChanged.connect(self.__enableOkButton)
116 self.maintainerEmailEdit.textChanged.connect(self.__enableOkButton)
117
118 def __enableOkButton(self):
119 """
120 Private slot to set the state of the OK button.
121 """
122 enable = (
123 bool(self.nameEdit.text()) and
124 bool(self.versionEdit.text()) and
125 bool(self.homePageUrlEdit.text()) and
126 ((bool(self.authorEdit.text()) and
127 bool(self.authorEmailEdit.text())) or
128 (bool(self.maintainerEdit.text()) and
129 bool(self.maintainerEmailEdit.text()))) and
130 self.homePageUrlEdit.text().startswith(("http://", "https://"))
131 )
132
133 self.__okButton.setEnabled(enable)
134
135 def __populateClassifiers(self):
136 """
137 Private method to populate the classifiers.
138 """
139 self.licenseClassifierComboBox.clear()
140 self.classifiersList.clear()
141 self.developmentStatusComboBox.clear()
142
143 self.developmentStatusComboBox.addItem("", "")
144
145 self.__classifiersDict = {}
146 for classifier in trove_classifiers.sorted_classifiers:
147 if classifier.startswith("License ::"):
148 self.licenseClassifierComboBox.addItem(
149 "/".join(classifier.split(" :: ")[1:]),
150 classifier
151 )
152 elif classifier.startswith("Development Status ::"):
153 self.developmentStatusComboBox.addItem(
154 classifier.split(" :: ")[1], classifier)
155 else:
156 self.__addClassifierEntry(classifier)
157 self.__classifiersDict = {}
158
159 self.licenseClassifierComboBox.setCurrentIndex(
160 self.licenseClassifierComboBox.findText(
161 "(GPLv3)",
162 Qt.MatchFlag.MatchContains | Qt.MatchFlag.MatchCaseSensitive
163 )
164 )
165
166 def __addClassifierEntry(self, classifier):
167 """
168 Private method to add a new entry to the list of trove classifiers.
169
170 @param classifier classifier containing the data for the entry
171 @type str
172 """
173 itm = None
174 pitm = None
175 dataList = classifier.split(" :: ")
176 for index in range(len(dataList)):
177 key = " :: ".join(dataList[:index + 1])
178 if key not in self.__classifiersDict:
179 if pitm is None:
180 itm = QTreeWidgetItem(
181 self.classifiersList, [dataList[index]])
182 pitm = itm
183 else:
184 itm = QTreeWidgetItem(pitm, [dataList[index]])
185 itm.setExpanded(True)
186 self.__classifiersDict[key] = itm
187 else:
188 pitm = self.__classifiersDict[key]
189 itm.setCheckState(0, Qt.CheckState.Unchecked)
190 itm.setData(0, Qt.ItemDataRole.UserRole, classifier)
191
192 def __getLicenseText(self):
193 """
194 Private method to get the license text.
195
196 @return license text
197 @rtype str
198 """
199 if not self.licenseClassifierCheckBox.isChecked():
200 return self.licenseEdit.text()
201 else:
202 lic = self.licenseClassifierComboBox.currentText()
203 if "(" in lic:
204 lic = lic.rsplit("(", 1)[1].split(")", 1)[0]
205 return lic
206
207 def __getSetupPyCode(self, indLevel, indString):
208 """
209 Private method to get the source code for a 'setup.py' file.
210
211 @param indLevel indentation level
212 @type int
213 @param indString string used for indentation (space or tab)
214 @type str
215 @return generated code
216 @rtype str
217 """
218 # Note: all paths are created with '/'; setup will do the right thing
219
220 # calculate our indentation level and the indentation string
221 il = indLevel + 1
222 istring = il * indString
223 i1string = (il + 1) * indString
224 i2string = (il + 2) * indString
225 estring = os.linesep + indLevel * indString
226
227 # now generate the code
228 if self.introCheckBox.isChecked():
229 sourceCode = "#!/usr/bin/env python3{0}".format(os.linesep)
230 sourceCode += "# -*- coding: utf-8 -*-{0}{0}".format(os.linesep)
231 else:
232 sourceCode = ""
233
234 if self.metaDataCheckBox.isChecked():
235 sourceCode += '# metadata{0}'.format(os.linesep)
236 sourceCode += '"{0}"{1}'.format(
237 self.summaryEdit.text() or "Setup routine",
238 os.linesep
239 )
240 sourceCode += '__version__ = "{0}"{1}'.format(
241 self.versionEdit.text(), os.linesep)
242 sourceCode += '__license__ = "{0}"{1}'.format(
243 self.__getLicenseText(), os.linesep)
244 sourceCode += '__author__ = "{0}"{1}'.format(
245 self.authorEdit.text() or self.maintainerEdit.text(),
246 os.linesep)
247 sourceCode += '__email__ = "{0}"{1}'.format(
248 self.authorEmailEdit.text() or self.maintainerEmailEdit.text(),
249 os.linesep)
250 sourceCode += '__url__ = "{0}"{1}'.format(
251 self.homePageUrlEdit.text(), os.linesep)
252 sourceCode += '__date__ = "{0}"{1}'.format(
253 datetime.datetime.now().isoformat().split('.')[0], os.linesep)
254 sourceCode += '__prj__ = "{0}"{1}'.format(
255 self.nameEdit.text(), os.linesep)
256 sourceCode += os.linesep
257
258 if self.importCheckBox.isChecked():
259 additionalImport = ", find_packages"
260 sourceCode += "from setuptools import setup{0}{1}".format(
261 additionalImport, os.linesep)
262 if sourceCode:
263 sourceCode += "{0}{0}".format(os.linesep)
264
265 if self.descriptionFromFilesCheckBox.isChecked():
266 sourceCode += 'def get_long_description():{0}'.format(os.linesep)
267 sourceCode += '{0}descr = []{1}'.format(istring, os.linesep)
268 sourceCode += '{0}for fname in ("{1}"):{2}'.format(
269 istring,
270 '", "'.join(self.descriptionEdit.toPlainText().splitlines()),
271 os.linesep)
272 sourceCode += (
273 '{0}with open(fname, "r", encoding="utf-8") as f:{1}'
274 ).format(i1string, os.linesep)
275 sourceCode += '{0}descr.append(f.read()){1}'.format(
276 i2string, os.linesep)
277 sourceCode += '{0}return "\\n\\n".join(descr){1}'.format(
278 istring, os.linesep)
279 sourceCode += "{0}{0}".format(os.linesep)
280
281 sourceCode += 'setup({0}'.format(os.linesep)
282 sourceCode += '{0}name="{1}",{2}'.format(
283 istring, self.nameEdit.text(), os.linesep)
284 sourceCode += '{0}version="{1}",{2}'.format(
285 istring, self.versionEdit.text(), os.linesep)
286
287 if self.summaryEdit.text():
288 sourceCode += '{0}description="{1}",{2}'.format(
289 istring, self.summaryEdit.text(), os.linesep)
290
291 if self.descriptionFromFilesCheckBox.isChecked():
292 sourceCode += '{0}long_description=get_long_description(),{1}'.format(
293 istring, os.linesep)
294 elif self.descriptionEdit.toPlainText():
295 sourceCode += '{0}long_description="""{1}""",{2}'.format(
296 istring, self.descriptionEdit.toPlainText(), os.linesep)
297
298 if self.descriptionContentTypeComboBox.currentData():
299 sourceCode += '{0}long_description_content_type="{1}",{2}'.format(
300 istring, self.descriptionContentTypeComboBox.currentData(), os.linesep)
301
302 if self.authorEdit.text():
303 sourceCode += '{0}author="{1}",{2}'.format(
304 istring, self.authorEdit.text(), os.linesep)
305 sourceCode += '{0}author_email="{1}",{2}'.format(
306 istring, self.authorEmailEdit.text(), os.linesep)
307
308 if self.maintainerEdit.text():
309 sourceCode += '{0}maintainer="{1}",{2}'.format(
310 istring, self.maintainerEdit.text(), os.linesep)
311 sourceCode += '{0}maintainer_email="{1}",{2}'.format(
312 istring, self.maintainerEmailEdit.text(), os.linesep)
313
314 sourceCode += '{0}url="{1}",{2}'.format(
315 istring, self.homePageUrlEdit.text(), os.linesep)
316 if self.downloadUrlEdit.text():
317 sourceCode += '{0}download_url="{1}",{2}'.format(
318 istring, self.downloadUrlEdit.text(), os.linesep)
319
320 if self.projectUrlsList.topLevelItemCount():
321 sourceCode += '{0}project_urls={{{1}'.format(istring, os.linesep)
322 for row in range(self.projectUrlsList.topLevelItemCount()):
323 urlItem = self.projectUrlsList.topLevelItem(row)
324 sourceCode += '{0}"{1}": "{2}",{3}'.format(
325 i1string, urlItem.text(0), urlItem.text(1), os.linesep)
326 sourceCode += '{0}}},{1}'.format(istring, os.linesep)
327
328 classifiers = []
329 if not self.licenseClassifierCheckBox.isChecked():
330 sourceCode += '{0}license="{1}",{2}'.format(
331 istring, self.licenseEdit.text(), os.linesep)
332 else:
333 classifiers.append(
334 self.licenseClassifierComboBox.itemData(
335 self.licenseClassifierComboBox.currentIndex()))
336
337 platforms = self.platformsEdit.toPlainText().splitlines()
338 if platforms:
339 sourceCode += '{0}platforms=[{1}'.format(istring, os.linesep)
340 sourceCode += '{0}"{1}"{2}'.format(
341 i1string,
342 '",{0}{1}"'.format(os.linesep, i1string).join(platforms),
343 os.linesep)
344 sourceCode += '{0}],{1}'.format(istring, os.linesep)
345
346 if self.developmentStatusComboBox.currentIndex() != 0:
347 classifiers.append(self.developmentStatusComboBox.currentData())
348
349 itm = self.classifiersList.topLevelItem(0)
350 while itm:
351 itm.setExpanded(True)
352 if itm.checkState(0) == Qt.CheckState.Checked:
353 classifiers.append(itm.data(0, Qt.ItemDataRole.UserRole))
354 itm = self.classifiersList.itemBelow(itm)
355
356 # cleanup classifiers list - remove all invalid entries
357 classifiers = [c for c in classifiers if bool(c)]
358 if classifiers:
359 sourceCode += '{0}classifiers=[{1}'.format(istring, os.linesep)
360 sourceCode += '{0}"{1}"{2}'.format(
361 i1string,
362 '",{0}{1}"'.format(os.linesep, i1string).join(classifiers),
363 os.linesep)
364 sourceCode += '{0}],{1}'.format(istring, os.linesep)
365 del classifiers
366
367 if self.keywordsEdit.text():
368 sourceCode += '{0}keywords="{1}",{2}'.format(
369 istring, self.keywordsEdit.text(), os.linesep)
370
371 if self.pyVersionEdit.text():
372 sourceCode += '{0}python_requires="{1}",{2}'.format(
373 istring, self.pyVersionEdit.text(), os.linesep)
374
375 sourceCode += '{0}packages=find_packages('.format(istring)
376 src = Utilities.fromNativeSeparators(
377 self.sourceDirectoryPicker.text())
378 excludePatterns = []
379 for row in range(self.excludePatternList.count()):
380 excludePatterns.append(
381 self.excludePatternList.item(row).text())
382 if src:
383 sourceCode += '{0}{1}"{2}"'.format(os.linesep, i1string, src)
384 if excludePatterns:
385 sourceCode += ','
386 else:
387 sourceCode += '{0}{1}'.format(os.linesep, istring)
388 if excludePatterns:
389 sourceCode += '{0}{1}exclude=[{0}'.format(os.linesep, i1string)
390 sourceCode += '{0}"{1}"{2}'.format(
391 i2string,
392 '",{0}{1}"'.format(os.linesep, i2string)
393 .join(excludePatterns),
394 os.linesep)
395 sourceCode += '{0}]{1}{2}'.format(i1string, os.linesep, istring)
396 sourceCode += '),{0}'.format(os.linesep)
397
398 if self.includePackageDataCheckBox.isChecked():
399 sourceCode += '{0}include_package_data = True,{1}'.format(
400 istring, os.linesep)
401
402 modules = []
403 for row in range(self.modulesList.count()):
404 modules.append(self.modulesList.item(row).text())
405 if modules:
406 sourceCode += '{0}py_modules=[{1}'.format(istring, os.linesep)
407 sourceCode += '{0}"{1}"{2}'.format(
408 i1string,
409 '",{0}{1}"'.format(os.linesep, i1string).join(modules),
410 os.linesep)
411 sourceCode += '{0}],{1}'.format(istring, os.linesep)
412 del modules
413
414 if self.entryPointsList.topLevelItemCount():
415 entryPoints = {
416 "console_scripts": [],
417 "gui_scripts": [],
418 }
419 for row in range(self.entryPointsList.topLevelItemCount()):
420 itm = self.entryPointsList.topLevelItem(row)
421 entryPoints[itm.data(0, Qt.ItemDataRole.UserRole)].append(
422 "{0} = {1}".format(itm.text(1), itm.text(2))
423 )
424 sourceCode += '{0}entry_points={{{1}'.format(istring, os.linesep)
425 for epCategory in entryPoints:
426 if entryPoints[epCategory]:
427 sourceCode += '{0}"{1}": [{2}'.format(
428 i1string, epCategory, os.linesep)
429 for entryPoint in entryPoints[epCategory]:
430 sourceCode += '{0}"{1}",{2}'.format(
431 i2string, entryPoint, os.linesep)
432 sourceCode += '{0}],{1}'.format(i1string, os.linesep)
433 sourceCode += '{0}}},{1}'.format(istring, os.linesep)
434
435 sourceCode += "){0}".format(estring)
436 return sourceCode
437
438 def __getSetupCfgCode(self):
439 """
440 Private method to get the source code for a 'setup.cfg' file.
441
442 @return generated code
443 @rtype str
444 """
445 from . import SetupCfgUtilities
446 metadata = {
447 "name": self.nameEdit.text(),
448 "version": self.versionEdit.text(),
449 }
450
451 if self.summaryEdit.text():
452 metadata["description"] = self.summaryEdit.text()
453
454 if self.descriptionEdit.toPlainText():
455 metadata["long_description"] = (
456 "file: {0}".format(
457 ", ".join(self.descriptionEdit.toPlainText().splitlines())
458 )
459 if self.descriptionFromFilesCheckBox.isChecked() else
460 self.descriptionEdit.toPlainText()
461 )
462
463 if self.descriptionContentTypeComboBox.currentData():
464 metadata["long_description_content_type"] = (
465 self.descriptionContentTypeComboBox.currentData()
466 )
467
468 if self.authorEdit.text():
469 metadata["author"] = self.authorEdit.text()
470 metadata["author_email"] = self.authorEmailEdit.text()
471
472 if self.maintainerEdit.text():
473 metadata["maintainer"] = self.maintainerEdit.text()
474 metadata["maintainer_email"] = self.maintainerEmailEdit.text()
475
476 metadata["url"] = self.homePageUrlEdit.text()
477 if self.downloadUrlEdit.text():
478 metadata["download_url"] = self.downloadUrlEdit.text()
479
480 if self.projectUrlsList.topLevelItemCount():
481 projectURLs = {}
482 for row in range(self.projectUrlsList.topLevelItemCount()):
483 urlItem = self.projectUrlsList.topLevelItem(row)
484 projectURLs[urlItem.text(0)] = urlItem.text(1)
485 metadata["project_urls"] = SetupCfgUtilities.toString(projectURLs)
486
487 classifiers = []
488 if not self.licenseClassifierCheckBox.isChecked():
489 metadata["license"] = self.licenseEdit.text()
490 else:
491 classifiers.append(
492 self.licenseClassifierComboBox.itemData(
493 self.licenseClassifierComboBox.currentIndex()))
494
495 platforms = self.platformsEdit.toPlainText().splitlines()
496 if platforms:
497 metadata["platforms"] = SetupCfgUtilities.toString(platforms)
498
499 if self.developmentStatusComboBox.currentIndex() != 0:
500 classifiers.append(self.developmentStatusComboBox.currentData())
501
502 itm = self.classifiersList.topLevelItem(0)
503 while itm:
504 itm.setExpanded(True)
505 if itm.checkState(0) == Qt.CheckState.Checked:
506 classifiers.append(itm.data(0, Qt.ItemDataRole.UserRole))
507 itm = self.classifiersList.itemBelow(itm)
508
509 # cleanup classifiers list - remove all invalid entries
510 classifiers = [c for c in classifiers if bool(c)]
511 if classifiers:
512 metadata["classifiers"] = SetupCfgUtilities.toString(classifiers)
513
514 if self.keywordsEdit.text():
515 metadata["keywords"] = SetupCfgUtilities.toString(
516 self.keywordsEdit.text().split())
517
518 options = {
519 "packages": "find:"
520 }
521
522 if self.pyVersionEdit.text():
523 options["python_requires"] = self.pyVersionEdit.text()
524
525 findOptions = {}
526 src = Utilities.fromNativeSeparators(self.sourceDirectoryPicker.text())
527 excludePatterns = []
528 for row in range(self.excludePatternList.count()):
529 excludePatterns.append(
530 self.excludePatternList.item(row).text())
531 if src:
532 options["package_dir"] = SetupCfgUtilities.toString({"": src})
533 findOptions["where"] = src
534 if excludePatterns:
535 findOptions["exclude"] = SetupCfgUtilities.toString(excludePatterns)
536
537 if self.includePackageDataCheckBox.isChecked():
538 options["include_package_data"] = SetupCfgUtilities.toString(True)
539 packageData = {} # placeholder section
540 else:
541 packageData = None
542
543 modules = []
544 for row in range(self.modulesList.count()):
545 modules.append(self.modulesList.item(row).text())
546 if modules:
547 options["py_modules"] = SetupCfgUtilities.toString(modules)
548
549 if self.entryPointsList.topLevelItemCount():
550 entryPoints = {
551 "console_scripts": {},
552 "gui_scripts": {},
553 }
554 for row in range(self.entryPointsList.topLevelItemCount()):
555 itm = self.entryPointsList.topLevelItem(row)
556 entryPoints[itm.data(0, Qt.ItemDataRole.UserRole)][
557 itm.text(1)] = itm.text(2)
558 for epType in list(entryPoints.keys()):
559 if entryPoints[epType]:
560 entryPoints[epType] = SetupCfgUtilities.toString(
561 entryPoints[epType])
562 else:
563 del entryPoints[epType]
564 else:
565 entryPoints = {}
566
567 configDict = {
568 "metadata": metadata,
569 "options": options,
570 "options.packages.find": findOptions,
571 }
572 if packageData is not None:
573 configDict["options.package_data"] = packageData
574 if entryPoints:
575 configDict["options.entry_points"] = entryPoints
576
577 cparser = configparser.ConfigParser()
578 cparser.read_dict(configDict)
579 sio = io.StringIO()
580 cparser.write(sio)
581 sourceCode = sio.getvalue()
582 return sourceCode
583
584 def __getPyprojectCode(self):
585 """
586 Private method to get the source code for a 'pyproject.toml' file.
587
588 @return generated code
589 @rtype str
590 """
591 doc = tomlkit.document()
592
593 buildSystem = tomlkit.table()
594 buildSystem["requires"] = ["setuptools>=61.0.0", "wheel"]
595 buildSystem["build-backend"] = "setuptools.build_meta"
596 doc["build-system"] = buildSystem
597
598 project = tomlkit.table()
599 project["name"] = self.nameEdit.text()
600 project["version"] = self.versionEdit.text()
601
602 if self.summaryEdit.text():
603 project["description"] = self.summaryEdit.text()
604
605 if self.descriptionEdit.toPlainText():
606 if self.descriptionFromFilesCheckBox.isChecked():
607 project["readme"] = self.descriptionEdit.toPlainText().splitlines()[0]
608 else:
609 readme = tomlkit.table()
610 readme["text"] = self.descriptionEdit.toPlainText()
611 readme["content-type"] = (
612 self.descriptionContentTypeComboBox.currentData()
613 )
614 project["readme"] = readme
615
616 if self.authorEdit.text():
617 authors = tomlkit.array()
618 author = tomlkit.inline_table()
619 author["name"] = self.authorEdit.text()
620 author["email"] = self.authorEmailEdit.text()
621 authors.append(author)
622 project["authors"] = authors
623
624 if self.maintainerEdit.text():
625 maintainers = tomlkit.array()
626 maintainer = tomlkit.inline_table()
627 maintainer["name"] = self.maintainerEdit.text()
628 maintainer["email"] = self.maintainerEmailEdit.text()
629 maintainers.append(maintainer)
630 project["maintainers"] = maintainers
631
632 urls = tomlkit.table()
633 urls["Homepage"] = self.homePageUrlEdit.text()
634 if self.downloadUrlEdit.text():
635 urls["Download"] = self.downloadUrlEdit.text()
636
637 if self.projectUrlsList.topLevelItemCount():
638 for row in range(self.projectUrlsList.topLevelItemCount()):
639 urlItem = self.projectUrlsList.topLevelItem(row)
640 urls[urlItem.text(0)] = urlItem.text(1)
641 project["urls"] = urls
642
643 classifiers = []
644 if not self.licenseClassifierCheckBox.isChecked():
645 licenseTbl = tomlkit.table()
646 licenseTbl["text"] = self.licenseEdit.text()
647 project["license"] = licenseTbl
648 else:
649 classifiers.append(
650 self.licenseClassifierComboBox.itemData(
651 self.licenseClassifierComboBox.currentIndex()))
652
653 if self.developmentStatusComboBox.currentIndex() != 0:
654 classifiers.append(self.developmentStatusComboBox.currentData())
655
656 itm = self.classifiersList.topLevelItem(0)
657 while itm:
658 itm.setExpanded(True)
659 if itm.checkState(0) == Qt.CheckState.Checked:
660 classifiers.append(itm.data(0, Qt.ItemDataRole.UserRole))
661 itm = self.classifiersList.itemBelow(itm)
662
663 # cleanup classifiers list - remove all invalid entries
664 classifiers = [c for c in classifiers if bool(c)]
665 if classifiers:
666 classifiersArray = tomlkit.array()
667 for classifier in classifiers:
668 classifiersArray.add_line(classifier)
669 classifiersArray.append(tomlkit.nl())
670 project["classifiers"] = classifiersArray
671
672 if self.keywordsEdit.text():
673 keywords = tomlkit.array()
674 for kw in self.keywordsEdit.text().split():
675 keywords.add_line(kw)
676 keywords.append(tomlkit.nl())
677 project["keywords"] = keywords
678
679 if self.pyVersionEdit.text():
680 project["requires-python"] = self.pyVersionEdit.text()
681
682 if self.entryPointsList.topLevelItemCount():
683 entryPoints = {
684 "console_scripts": {},
685 "gui_scripts": {},
686 }
687 for row in range(self.entryPointsList.topLevelItemCount()):
688 itm = self.entryPointsList.topLevelItem(row)
689 entryPoints[itm.data(0, Qt.ItemDataRole.UserRole)][
690 itm.text(1)] = itm.text(2)
691
692 if entryPoints["console_scripts"]:
693 scripts = tomlkit.table()
694 for name, function in entryPoints["console_scripts"].items():
695 scripts[name] = function
696 project["scripts"] = scripts
697
698 if entryPoints["gui_scripts"]:
699 guiScripts = tomlkit.table()
700 for name, function in entryPoints["gui_scripts"].items():
701 guiScripts[name] = function
702 project["gui-scripts"] = guiScripts
703
704 # placeholder
705 dependencies = tomlkit.array()
706 dependencies.append(tomlkit.comment(
707 "TODO: enter project dependencies " # __NO-TASK__
708 ))
709 project["dependencies"] = dependencies
710
711 doc["project"] = project
712
713 setuptools = tomlkit.table()
714
715 platforms = self.platformsEdit.toPlainText().splitlines()
716 if platforms:
717 platformsArray = tomlkit.array()
718 for plt in platforms:
719 platformsArray.add_line(plt)
720 platformsArray.append(tomlkit.nl())
721 setuptools["platforms"] = platformsArray
722
723 setuptools["include-package-data"] = self.includePackageDataCheckBox.isChecked()
724 if self.includePackageDataCheckBox.isChecked():
725 # placeholder
726 setuptools["package-data"] = tomlkit.table()
727 setuptools["package-data"].add(tomlkit.comment(
728 "TODO: enter package data patterns" # __NO-TASK__
729 ))
730
731 if self.modulesList.count():
732 modulesArray = tomlkit.array()
733 for row in range(self.modulesList.count()):
734 modulesArray.add_line(self.modulesList.item(row).text())
735 modulesArray.append(tomlkit.nl())
736 setuptools["py-modules"] = modulesArray
737
738 findspec = tomlkit.table()
739 src = Utilities.fromNativeSeparators(self.sourceDirectoryPicker.text())
740 excludePatterns = []
741 for row in range(self.excludePatternList.count()):
742 excludePatterns.append(
743 self.excludePatternList.item(row).text())
744 if src:
745 findspec["where"] = [ericApp().getObject("Project").getRelativePath(src)]
746 if excludePatterns:
747 excludePatternsArray = tomlkit.array()
748 for pattern in excludePatterns:
749 excludePatternsArray.add_line(pattern)
750 excludePatternsArray.append(tomlkit.nl())
751 findspec["exclude"] = excludePatternsArray
752
753 if bool(findspec):
754 setuptools["packages"] = tomlkit.table(is_super_table=True)
755 setuptools["packages"]["find"] = findspec
756
757 doc["tool"] = tomlkit.table(is_super_table=True)
758 doc["tool"]["setuptools"] = setuptools
759
760 sourceCode = tomlkit.dumps(doc)
761 return sourceCode
762
763 @pyqtSlot()
764 def accept(self):
765 """
766 Public slot to handle pressing the OK button.
767 """
768 line, index = self.__editor.getCursorPosition()
769 indLevel = self.__editor.indentation(line) // self.__editor.indentationWidth()
770 indString = (
771 '\t'
772 if self.__editor.indentationsUseTabs() else
773 self.__editor.indentationWidth() * ' '
774 )
775
776 if self.__category == "setup.py":
777 sourceCode = self.__getSetupPyCode(indLevel, indString)
778 elif self.__category == "setup.cfg":
779 sourceCode = self.__getSetupCfgCode()
780 elif self.__category == "pyproject.toml":
781 sourceCode = self.__getPyprojectCode()
782 else:
783 # should not happen, but play it safe
784 sourceCode = ""
785
786 if sourceCode:
787 line, index = self.__editor.getCursorPosition()
788 # It should be done this way to allow undo
789 self.__editor.beginUndoAction()
790 self.__editor.insertAt(sourceCode, line, index)
791 self.__editor.endUndoAction()
792
793 super().accept()
794
795 @pyqtSlot()
796 def on_projectButton_clicked(self):
797 """
798 Private slot to populate some fields with data retrieved from the
799 current project.
800 """
801 project = ericApp().getObject("Project")
802
803 self.nameEdit.setText(project.getProjectName())
804 try:
805 self.versionEdit.setText(project.getProjectVersion())
806 self.authorEdit.setText(project.getProjectAuthor())
807 self.authorEmailEdit.setText(project.getProjectAuthorEmail())
808 description = project.getProjectDescription()
809 except AttributeError:
810 self.versionEdit.setText(project.pdata["VERSION"][0])
811 self.authorEdit.setText(project.pdata["AUTHOR"][0])
812 self.authorEmailEdit.setText(project.pdata["EMAIL"][0])
813 description = project.pdata["DESCRIPTION"][0]
814
815 summary = (
816 description.split(".", 1)[0].replace("\r", "").replace("\n", "") +
817 "."
818 )
819 self.summaryEdit.setText(summary)
820 self.descriptionEdit.setPlainText(description)
821
822 self.packageRootPicker.setText(project.getProjectPath())
823
824 # prevent overwriting of entries by disabling the button
825 self.projectButton.setEnabled(False)
826
827 def __getStartDir(self):
828 """
829 Private method to get the start directory for selection dialogs.
830
831 @return start directory
832 @rtype str
833 """
834 return (Preferences.getMultiProject("Workspace") or
835 Utilities.getHomeDir())
836
837 @pyqtSlot()
838 def on_entryPointsList_itemSelectionChanged(self):
839 """
840 Private slot to handle a change of selected items of the
841 entry points list.
842 """
843 self.deleteEntryPointButton.setEnabled(
844 bool(self.entryPointsList.selectedItems()))
845 self.editEntryPointButton.setEnabled(
846 len(self.entryPointsList.selectedItems()) == 1)
847
848 @pyqtSlot()
849 def on_deleteEntryPointButton_clicked(self):
850 """
851 Private slot to delete the selected entry point items.
852 """
853 for itm in self.entryPointsList.selectedItems():
854 self.entryPointsList.takeTopLevelItem(self.entryPointsList.row(itm))
855 del itm
856
857 @pyqtSlot()
858 def on_addEntryPointButton_clicked(self):
859 """
860 Private slot to add an entry point to the list.
861 """
862 project = ericApp().getObject("Project")
863 rootDir = (
864 project.getProjectPath()
865 if project.isOpen() else
866 ""
867 )
868 dlg = AddEntryPointDialog(rootDir, parent=self)
869 if dlg.exec() == QDialog.DialogCode.Accepted:
870 epType, epCategory, name, script = dlg.getEntryPoint()
871 itm = QTreeWidgetItem(self.entryPointsList, [epType, name, script])
872 itm.setData(0, Qt.ItemDataRole.UserRole, epCategory)
873
874 @pyqtSlot()
875 def on_editEntryPointButton_clicked(self):
876 """
877 Private slot to edit the selected entry point.
878 """
879 project = ericApp().getObject("Project")
880 rootDir = (
881 project.getProjectPath()
882 if project.isOpen() else
883 ""
884 )
885 itm = self.entryPointsList.selectedItems()[0]
886 dlg = AddEntryPointDialog(rootDir, epType=itm.text(0), name=itm.text(1),
887 script=itm.text(2), parent=self)
888 if dlg.exec() == QDialog.DialogCode.Accepted:
889 epType, epCategory, name, script = dlg.getEntryPoint()
890 itm.setText(0, epType)
891 itm.setText(1, name)
892 itm.setText(2, script)
893 itm.setData(0, Qt.ItemDataRole.UserRole, epCategory)
894
895 @pyqtSlot()
896 def on_modulesList_itemSelectionChanged(self):
897 """
898 Private slot to handle a change of selected items of the
899 modules list.
900 """
901 self.deleteModuleButton.setEnabled(
902 bool(self.modulesList.selectedItems()))
903
904 @pyqtSlot()
905 def on_deleteModuleButton_clicked(self):
906 """
907 Private slot to delete the selected module items.
908 """
909 for itm in self.modulesList.selectedItems():
910 self.modulesList.takeItem(self.modulesList.row(itm))
911 del itm
912
913 @pyqtSlot()
914 def on_addModuleButton_clicked(self):
915 """
916 Private slot to add Python modules to the list.
917 """
918 startDir = self.packageRootPicker.text() or self.__getStartDir()
919 modulesList = EricFileDialog.getOpenFileNames(
920 self,
921 self.tr("Add Python Modules"),
922 startDir,
923 self.tr("Python Files (*.py)"))
924 for module in modulesList:
925 module = module.replace(
926 Utilities.toNativeSeparators(startDir), "")
927 if module.startswith(("\\", "/")):
928 module = module[1:]
929 if module:
930 QListWidgetItem(
931 str(pathlib.Path(module).with_suffix(""))
932 .replace("\\", ".")
933 .replace("/", "."),
934 self.modulesList
935 )
936
937 @pyqtSlot()
938 def on_excludePatternList_itemSelectionChanged(self):
939 """
940 Private slot to handle a change of selected items of the
941 exclude pattern list.
942 """
943 self.deleteExcludePatternButton.setEnabled(
944 bool(self.excludePatternList.selectedItems()))
945
946 @pyqtSlot()
947 def on_deleteExcludePatternButton_clicked(self):
948 """
949 Private slot to delete the selected exclude pattern items.
950 """
951 for itm in self.excludePatternList.selectedItems():
952 self.excludePatternList.takeItem(
953 self.excludePatternList.row(itm))
954 del itm
955
956 @pyqtSlot()
957 def on_addExludePatternButton_clicked(self):
958 """
959 Private slot to add an exclude pattern to the list.
960 """
961 pattern = (
962 self.excludePatternEdit.text().replace("\\", ".").replace("/", ".")
963 )
964 if not self.excludePatternList.findItems(
965 pattern,
966 Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive
967 ):
968 QListWidgetItem(pattern, self.excludePatternList)
969
970 @pyqtSlot(str)
971 def on_excludePatternEdit_textChanged(self, txt):
972 """
973 Private slot to handle a change of the exclude pattern text.
974
975 @param txt text of the line edit
976 @type str
977 """
978 self.addExludePatternButton.setEnabled(bool(txt))
979
980 @pyqtSlot()
981 def on_excludePatternEdit_returnPressed(self):
982 """
983 Private slot handling a press of the return button of the
984 exclude pattern edit.
985 """
986 self.on_addExludePatternButton_clicked()
987
988 @pyqtSlot()
989 def on_urlDeleteButton_clicked(self):
990 """
991 Private slot to delete the selected URL items.
992 """
993 for itm in self.projectUrlsList.selectedItems():
994 self.projectUrlsList.takeTopLevelItem(self.projectUrlsList.row(itm))
995 del itm
996
997 @pyqtSlot()
998 def on_urlAddButton_clicked(self):
999 """
1000 Private slot to add a project URL to the list.
1001 """
1002 dlg = AddProjectUrlDialog(parent=self)
1003 if dlg.exec() == QDialog.DialogCode.Accepted:
1004 name, url = dlg.getUrl()
1005 QTreeWidgetItem(self.projectUrlsList, [name, url])
1006
1007 @pyqtSlot()
1008 def on_urlEditButton_clicked(self):
1009 """
1010 Private slot to edit the selected project URL.
1011 """
1012 itm = self.projectUrlsList.selectedItems()[0]
1013 dlg = AddProjectUrlDialog(name=itm.text(0), url=itm.text(1), parent=self)
1014 if dlg.exec() == QDialog.DialogCode.Accepted:
1015 name, url = dlg.getUrl()
1016 itm.setText(0, name)
1017 itm.setText(1, url)
1018
1019 @pyqtSlot()
1020 def on_projectUrlsList_itemSelectionChanged(self):
1021 """
1022 Private slot to handle a change of selected items of the
1023 project URLs list.
1024 """
1025 self.urlDeleteButton.setEnabled(bool(self.projectUrlsList.selectedItems()))
1026 self.urlEditButton.setEnabled(len(self.projectUrlsList.selectedItems()) == 1)

eric ide

mercurial