--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/Plugins/WizardPlugins/SetupWizard/SetupWizardDialog.py Thu Jul 07 11:23:56 2022 +0200 @@ -0,0 +1,1026 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2013 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the setup.py wizard dialog. +""" + +import configparser +import datetime +import io +import os +import pathlib + +import tomlkit + +import trove_classifiers + +from PyQt6.QtCore import pyqtSlot, Qt +from PyQt6.QtWidgets import ( + QDialog, QDialogButtonBox, QTreeWidgetItem, QListWidgetItem +) + +from EricWidgets.EricApplication import ericApp +from EricWidgets import EricFileDialog +from EricWidgets.EricPathPicker import EricPathPickerModes + +from .AddEntryPointDialog import AddEntryPointDialog +from .AddProjectUrlDialog import AddProjectUrlDialog +from .Ui_SetupWizardDialog import Ui_SetupWizardDialog + +import Utilities +import Preferences + + +class SetupWizardDialog(QDialog, Ui_SetupWizardDialog): + """ + Class implementing the setup.py wizard dialog. + + It displays a dialog for entering the parameters for the setup.py code + generator. + """ + def __init__(self, category, editor, parent=None): + """ + Constructor + + @param category category of setup file to create + @type str + @param editor reference to the editor object to receive the code + @type Editor + @param parent reference to the parent widget (defaults to None) + @type QWidget (optional) + @exception ValueError raised for an illegal setup file category + """ + if category not in ("setup.py", "setup.cfg", "pyproject.toml"): + raise ValueError("illegal setup file category given") + + super().__init__(parent) + self.setupUi(self) + + self.setWindowTitle(self.tr("{0} Wizard").format(category)) + + self.__replies = [] + self.__category = category + self.__editor = editor + + if category != "setup.py": + self.introCheckBox.setVisible(False) + self.importCheckBox.setVisible(False) + self.metaDataCheckBox.setVisible(False) + + self.dataTabWidget.setCurrentIndex(0) + + self.packageRootPicker.setMode(EricPathPickerModes.DIRECTORY_MODE) + self.sourceDirectoryPicker.setMode(EricPathPickerModes.DIRECTORY_MODE) + + self.__mandatoryStyleSheet = ( + "QLineEdit {border: 2px solid; border-color: #dd8888}" + if ericApp().usesDarkPalette() else + "QLineEdit {border: 2px solid; border-color: #800000}" + ) + for lineEdit in [self.nameEdit, self.versionEdit, + self.homePageUrlEdit, self.authorEdit, + self.authorEmailEdit, self.maintainerEdit, + self.maintainerEmailEdit]: + lineEdit.setStyleSheet(self.__mandatoryStyleSheet) + + self.__populateClassifiers() + + self.__okButton = self.buttonBox.button( + QDialogButtonBox.StandardButton.Ok) + self.__okButton.setEnabled(False) + + projectOpen = ericApp().getObject("Project").isOpen() + self.projectButton.setEnabled(projectOpen) + + self.projectUrlsList.header().setSortIndicator(0, Qt.SortOrder.AscendingOrder) + self.entryPointsList.header().setSortIndicator(0, Qt.SortOrder.AscendingOrder) + + self.descriptionContentTypeComboBox.addItem("", "") + for contentType, mimetype in sorted([ + (self.tr("Plain Text"), "text/plain"), + (self.tr("Markdown"), "text/markdown"), + (self.tr("reStructuredText"), "text/x-rst") + + ]): + self.descriptionContentTypeComboBox.addItem(contentType, mimetype) + + self.homePageUrlEdit.textChanged.connect(self.__enableOkButton) + self.nameEdit.textChanged.connect(self.__enableOkButton) + self.versionEdit.textChanged.connect(self.__enableOkButton) + self.authorEdit.textChanged.connect(self.__enableOkButton) + self.authorEmailEdit.textChanged.connect(self.__enableOkButton) + self.maintainerEdit.textChanged.connect(self.__enableOkButton) + self.maintainerEmailEdit.textChanged.connect(self.__enableOkButton) + + def __enableOkButton(self): + """ + Private slot to set the state of the OK button. + """ + enable = ( + bool(self.nameEdit.text()) and + bool(self.versionEdit.text()) and + bool(self.homePageUrlEdit.text()) and + ((bool(self.authorEdit.text()) and + bool(self.authorEmailEdit.text())) or + (bool(self.maintainerEdit.text()) and + bool(self.maintainerEmailEdit.text()))) and + self.homePageUrlEdit.text().startswith(("http://", "https://")) + ) + + self.__okButton.setEnabled(enable) + + def __populateClassifiers(self): + """ + Private method to populate the classifiers. + """ + self.licenseClassifierComboBox.clear() + self.classifiersList.clear() + self.developmentStatusComboBox.clear() + + self.developmentStatusComboBox.addItem("", "") + + self.__classifiersDict = {} + for classifier in trove_classifiers.sorted_classifiers: + if classifier.startswith("License ::"): + self.licenseClassifierComboBox.addItem( + "/".join(classifier.split(" :: ")[1:]), + classifier + ) + elif classifier.startswith("Development Status ::"): + self.developmentStatusComboBox.addItem( + classifier.split(" :: ")[1], classifier) + else: + self.__addClassifierEntry(classifier) + self.__classifiersDict = {} + + self.licenseClassifierComboBox.setCurrentIndex( + self.licenseClassifierComboBox.findText( + "(GPLv3)", + Qt.MatchFlag.MatchContains | Qt.MatchFlag.MatchCaseSensitive + ) + ) + + def __addClassifierEntry(self, classifier): + """ + Private method to add a new entry to the list of trove classifiers. + + @param classifier classifier containing the data for the entry + @type str + """ + itm = None + pitm = None + dataList = classifier.split(" :: ") + for index in range(len(dataList)): + key = " :: ".join(dataList[:index + 1]) + if key not in self.__classifiersDict: + if pitm is None: + itm = QTreeWidgetItem( + self.classifiersList, [dataList[index]]) + pitm = itm + else: + itm = QTreeWidgetItem(pitm, [dataList[index]]) + itm.setExpanded(True) + self.__classifiersDict[key] = itm + else: + pitm = self.__classifiersDict[key] + itm.setCheckState(0, Qt.CheckState.Unchecked) + itm.setData(0, Qt.ItemDataRole.UserRole, classifier) + + def __getLicenseText(self): + """ + Private method to get the license text. + + @return license text + @rtype str + """ + if not self.licenseClassifierCheckBox.isChecked(): + return self.licenseEdit.text() + else: + lic = self.licenseClassifierComboBox.currentText() + if "(" in lic: + lic = lic.rsplit("(", 1)[1].split(")", 1)[0] + return lic + + def __getSetupPyCode(self, indLevel, indString): + """ + Private method to get the source code for a 'setup.py' file. + + @param indLevel indentation level + @type int + @param indString string used for indentation (space or tab) + @type str + @return generated code + @rtype str + """ + # Note: all paths are created with '/'; setup will do the right thing + + # calculate our indentation level and the indentation string + il = indLevel + 1 + istring = il * indString + i1string = (il + 1) * indString + i2string = (il + 2) * indString + estring = os.linesep + indLevel * indString + + # now generate the code + if self.introCheckBox.isChecked(): + sourceCode = "#!/usr/bin/env python3{0}".format(os.linesep) + sourceCode += "# -*- coding: utf-8 -*-{0}{0}".format(os.linesep) + else: + sourceCode = "" + + if self.metaDataCheckBox.isChecked(): + sourceCode += '# metadata{0}'.format(os.linesep) + sourceCode += '"{0}"{1}'.format( + self.summaryEdit.text() or "Setup routine", + os.linesep + ) + sourceCode += '__version__ = "{0}"{1}'.format( + self.versionEdit.text(), os.linesep) + sourceCode += '__license__ = "{0}"{1}'.format( + self.__getLicenseText(), os.linesep) + sourceCode += '__author__ = "{0}"{1}'.format( + self.authorEdit.text() or self.maintainerEdit.text(), + os.linesep) + sourceCode += '__email__ = "{0}"{1}'.format( + self.authorEmailEdit.text() or self.maintainerEmailEdit.text(), + os.linesep) + sourceCode += '__url__ = "{0}"{1}'.format( + self.homePageUrlEdit.text(), os.linesep) + sourceCode += '__date__ = "{0}"{1}'.format( + datetime.datetime.now().isoformat().split('.')[0], os.linesep) + sourceCode += '__prj__ = "{0}"{1}'.format( + self.nameEdit.text(), os.linesep) + sourceCode += os.linesep + + if self.importCheckBox.isChecked(): + additionalImport = ", find_packages" + sourceCode += "from setuptools import setup{0}{1}".format( + additionalImport, os.linesep) + if sourceCode: + sourceCode += "{0}{0}".format(os.linesep) + + if self.descriptionFromFilesCheckBox.isChecked(): + sourceCode += 'def get_long_description():{0}'.format(os.linesep) + sourceCode += '{0}descr = []{1}'.format(istring, os.linesep) + sourceCode += '{0}for fname in ("{1}"):{2}'.format( + istring, + '", "'.join(self.descriptionEdit.toPlainText().splitlines()), + os.linesep) + sourceCode += ( + '{0}with open(fname, "r", encoding="utf-8") as f:{1}' + ).format(i1string, os.linesep) + sourceCode += '{0}descr.append(f.read()){1}'.format( + i2string, os.linesep) + sourceCode += '{0}return "\\n\\n".join(descr){1}'.format( + istring, os.linesep) + sourceCode += "{0}{0}".format(os.linesep) + + sourceCode += 'setup({0}'.format(os.linesep) + sourceCode += '{0}name="{1}",{2}'.format( + istring, self.nameEdit.text(), os.linesep) + sourceCode += '{0}version="{1}",{2}'.format( + istring, self.versionEdit.text(), os.linesep) + + if self.summaryEdit.text(): + sourceCode += '{0}description="{1}",{2}'.format( + istring, self.summaryEdit.text(), os.linesep) + + if self.descriptionFromFilesCheckBox.isChecked(): + sourceCode += '{0}long_description=get_long_description(),{1}'.format( + istring, os.linesep) + elif self.descriptionEdit.toPlainText(): + sourceCode += '{0}long_description="""{1}""",{2}'.format( + istring, self.descriptionEdit.toPlainText(), os.linesep) + + if self.descriptionContentTypeComboBox.currentData(): + sourceCode += '{0}long_description_content_type="{1}",{2}'.format( + istring, self.descriptionContentTypeComboBox.currentData(), os.linesep) + + if self.authorEdit.text(): + sourceCode += '{0}author="{1}",{2}'.format( + istring, self.authorEdit.text(), os.linesep) + sourceCode += '{0}author_email="{1}",{2}'.format( + istring, self.authorEmailEdit.text(), os.linesep) + + if self.maintainerEdit.text(): + sourceCode += '{0}maintainer="{1}",{2}'.format( + istring, self.maintainerEdit.text(), os.linesep) + sourceCode += '{0}maintainer_email="{1}",{2}'.format( + istring, self.maintainerEmailEdit.text(), os.linesep) + + sourceCode += '{0}url="{1}",{2}'.format( + istring, self.homePageUrlEdit.text(), os.linesep) + if self.downloadUrlEdit.text(): + sourceCode += '{0}download_url="{1}",{2}'.format( + istring, self.downloadUrlEdit.text(), os.linesep) + + if self.projectUrlsList.topLevelItemCount(): + sourceCode += '{0}project_urls={{{1}'.format(istring, os.linesep) + for row in range(self.projectUrlsList.topLevelItemCount()): + urlItem = self.projectUrlsList.topLevelItem(row) + sourceCode += '{0}"{1}": "{2}",{3}'.format( + i1string, urlItem.text(0), urlItem.text(1), os.linesep) + sourceCode += '{0}}},{1}'.format(istring, os.linesep) + + classifiers = [] + if not self.licenseClassifierCheckBox.isChecked(): + sourceCode += '{0}license="{1}",{2}'.format( + istring, self.licenseEdit.text(), os.linesep) + else: + classifiers.append( + self.licenseClassifierComboBox.itemData( + self.licenseClassifierComboBox.currentIndex())) + + platforms = self.platformsEdit.toPlainText().splitlines() + if platforms: + sourceCode += '{0}platforms=[{1}'.format(istring, os.linesep) + sourceCode += '{0}"{1}"{2}'.format( + i1string, + '",{0}{1}"'.format(os.linesep, i1string).join(platforms), + os.linesep) + sourceCode += '{0}],{1}'.format(istring, os.linesep) + + if self.developmentStatusComboBox.currentIndex() != 0: + classifiers.append(self.developmentStatusComboBox.currentData()) + + itm = self.classifiersList.topLevelItem(0) + while itm: + itm.setExpanded(True) + if itm.checkState(0) == Qt.CheckState.Checked: + classifiers.append(itm.data(0, Qt.ItemDataRole.UserRole)) + itm = self.classifiersList.itemBelow(itm) + + # cleanup classifiers list - remove all invalid entries + classifiers = [c for c in classifiers if bool(c)] + if classifiers: + sourceCode += '{0}classifiers=[{1}'.format(istring, os.linesep) + sourceCode += '{0}"{1}"{2}'.format( + i1string, + '",{0}{1}"'.format(os.linesep, i1string).join(classifiers), + os.linesep) + sourceCode += '{0}],{1}'.format(istring, os.linesep) + del classifiers + + if self.keywordsEdit.text(): + sourceCode += '{0}keywords="{1}",{2}'.format( + istring, self.keywordsEdit.text(), os.linesep) + + if self.pyVersionEdit.text(): + sourceCode += '{0}python_requires="{1}",{2}'.format( + istring, self.pyVersionEdit.text(), os.linesep) + + sourceCode += '{0}packages=find_packages('.format(istring) + src = Utilities.fromNativeSeparators( + self.sourceDirectoryPicker.text()) + excludePatterns = [] + for row in range(self.excludePatternList.count()): + excludePatterns.append( + self.excludePatternList.item(row).text()) + if src: + sourceCode += '{0}{1}"{2}"'.format(os.linesep, i1string, src) + if excludePatterns: + sourceCode += ',' + else: + sourceCode += '{0}{1}'.format(os.linesep, istring) + if excludePatterns: + sourceCode += '{0}{1}exclude=[{0}'.format(os.linesep, i1string) + sourceCode += '{0}"{1}"{2}'.format( + i2string, + '",{0}{1}"'.format(os.linesep, i2string) + .join(excludePatterns), + os.linesep) + sourceCode += '{0}]{1}{2}'.format(i1string, os.linesep, istring) + sourceCode += '),{0}'.format(os.linesep) + + if self.includePackageDataCheckBox.isChecked(): + sourceCode += '{0}include_package_data = True,{1}'.format( + istring, os.linesep) + + modules = [] + for row in range(self.modulesList.count()): + modules.append(self.modulesList.item(row).text()) + if modules: + sourceCode += '{0}py_modules=[{1}'.format(istring, os.linesep) + sourceCode += '{0}"{1}"{2}'.format( + i1string, + '",{0}{1}"'.format(os.linesep, i1string).join(modules), + os.linesep) + sourceCode += '{0}],{1}'.format(istring, os.linesep) + del modules + + if self.entryPointsList.topLevelItemCount(): + entryPoints = { + "console_scripts": [], + "gui_scripts": [], + } + for row in range(self.entryPointsList.topLevelItemCount()): + itm = self.entryPointsList.topLevelItem(row) + entryPoints[itm.data(0, Qt.ItemDataRole.UserRole)].append( + "{0} = {1}".format(itm.text(1), itm.text(2)) + ) + sourceCode += '{0}entry_points={{{1}'.format(istring, os.linesep) + for epCategory in entryPoints: + if entryPoints[epCategory]: + sourceCode += '{0}"{1}": [{2}'.format( + i1string, epCategory, os.linesep) + for entryPoint in entryPoints[epCategory]: + sourceCode += '{0}"{1}",{2}'.format( + i2string, entryPoint, os.linesep) + sourceCode += '{0}],{1}'.format(i1string, os.linesep) + sourceCode += '{0}}},{1}'.format(istring, os.linesep) + + sourceCode += "){0}".format(estring) + return sourceCode + + def __getSetupCfgCode(self): + """ + Private method to get the source code for a 'setup.cfg' file. + + @return generated code + @rtype str + """ + from . import SetupCfgUtilities + metadata = { + "name": self.nameEdit.text(), + "version": self.versionEdit.text(), + } + + if self.summaryEdit.text(): + metadata["description"] = self.summaryEdit.text() + + if self.descriptionEdit.toPlainText(): + metadata["long_description"] = ( + "file: {0}".format( + ", ".join(self.descriptionEdit.toPlainText().splitlines()) + ) + if self.descriptionFromFilesCheckBox.isChecked() else + self.descriptionEdit.toPlainText() + ) + + if self.descriptionContentTypeComboBox.currentData(): + metadata["long_description_content_type"] = ( + self.descriptionContentTypeComboBox.currentData() + ) + + if self.authorEdit.text(): + metadata["author"] = self.authorEdit.text() + metadata["author_email"] = self.authorEmailEdit.text() + + if self.maintainerEdit.text(): + metadata["maintainer"] = self.maintainerEdit.text() + metadata["maintainer_email"] = self.maintainerEmailEdit.text() + + metadata["url"] = self.homePageUrlEdit.text() + if self.downloadUrlEdit.text(): + metadata["download_url"] = self.downloadUrlEdit.text() + + if self.projectUrlsList.topLevelItemCount(): + projectURLs = {} + for row in range(self.projectUrlsList.topLevelItemCount()): + urlItem = self.projectUrlsList.topLevelItem(row) + projectURLs[urlItem.text(0)] = urlItem.text(1) + metadata["project_urls"] = SetupCfgUtilities.toString(projectURLs) + + classifiers = [] + if not self.licenseClassifierCheckBox.isChecked(): + metadata["license"] = self.licenseEdit.text() + else: + classifiers.append( + self.licenseClassifierComboBox.itemData( + self.licenseClassifierComboBox.currentIndex())) + + platforms = self.platformsEdit.toPlainText().splitlines() + if platforms: + metadata["platforms"] = SetupCfgUtilities.toString(platforms) + + if self.developmentStatusComboBox.currentIndex() != 0: + classifiers.append(self.developmentStatusComboBox.currentData()) + + itm = self.classifiersList.topLevelItem(0) + while itm: + itm.setExpanded(True) + if itm.checkState(0) == Qt.CheckState.Checked: + classifiers.append(itm.data(0, Qt.ItemDataRole.UserRole)) + itm = self.classifiersList.itemBelow(itm) + + # cleanup classifiers list - remove all invalid entries + classifiers = [c for c in classifiers if bool(c)] + if classifiers: + metadata["classifiers"] = SetupCfgUtilities.toString(classifiers) + + if self.keywordsEdit.text(): + metadata["keywords"] = SetupCfgUtilities.toString( + self.keywordsEdit.text().split()) + + options = { + "packages": "find:" + } + + if self.pyVersionEdit.text(): + options["python_requires"] = self.pyVersionEdit.text() + + findOptions = {} + src = Utilities.fromNativeSeparators(self.sourceDirectoryPicker.text()) + excludePatterns = [] + for row in range(self.excludePatternList.count()): + excludePatterns.append( + self.excludePatternList.item(row).text()) + if src: + options["package_dir"] = SetupCfgUtilities.toString({"": src}) + findOptions["where"] = src + if excludePatterns: + findOptions["exclude"] = SetupCfgUtilities.toString(excludePatterns) + + if self.includePackageDataCheckBox.isChecked(): + options["include_package_data"] = SetupCfgUtilities.toString(True) + packageData = {} # placeholder section + else: + packageData = None + + modules = [] + for row in range(self.modulesList.count()): + modules.append(self.modulesList.item(row).text()) + if modules: + options["py_modules"] = SetupCfgUtilities.toString(modules) + + if self.entryPointsList.topLevelItemCount(): + entryPoints = { + "console_scripts": {}, + "gui_scripts": {}, + } + for row in range(self.entryPointsList.topLevelItemCount()): + itm = self.entryPointsList.topLevelItem(row) + entryPoints[itm.data(0, Qt.ItemDataRole.UserRole)][ + itm.text(1)] = itm.text(2) + for epType in list(entryPoints.keys()): + if entryPoints[epType]: + entryPoints[epType] = SetupCfgUtilities.toString( + entryPoints[epType]) + else: + del entryPoints[epType] + else: + entryPoints = {} + + configDict = { + "metadata": metadata, + "options": options, + "options.packages.find": findOptions, + } + if packageData is not None: + configDict["options.package_data"] = packageData + if entryPoints: + configDict["options.entry_points"] = entryPoints + + cparser = configparser.ConfigParser() + cparser.read_dict(configDict) + sio = io.StringIO() + cparser.write(sio) + sourceCode = sio.getvalue() + return sourceCode + + def __getPyprojectCode(self): + """ + Private method to get the source code for a 'pyproject.toml' file. + + @return generated code + @rtype str + """ + doc = tomlkit.document() + + buildSystem = tomlkit.table() + buildSystem["requires"] = ["setuptools>=61.0.0", "wheel"] + buildSystem["build-backend"] = "setuptools.build_meta" + doc["build-system"] = buildSystem + + project = tomlkit.table() + project["name"] = self.nameEdit.text() + project["version"] = self.versionEdit.text() + + if self.summaryEdit.text(): + project["description"] = self.summaryEdit.text() + + if self.descriptionEdit.toPlainText(): + if self.descriptionFromFilesCheckBox.isChecked(): + project["readme"] = self.descriptionEdit.toPlainText().splitlines()[0] + else: + readme = tomlkit.table() + readme["text"] = self.descriptionEdit.toPlainText() + readme["content-type"] = ( + self.descriptionContentTypeComboBox.currentData() + ) + project["readme"] = readme + + if self.authorEdit.text(): + authors = tomlkit.array() + author = tomlkit.inline_table() + author["name"] = self.authorEdit.text() + author["email"] = self.authorEmailEdit.text() + authors.append(author) + project["authors"] = authors + + if self.maintainerEdit.text(): + maintainers = tomlkit.array() + maintainer = tomlkit.inline_table() + maintainer["name"] = self.maintainerEdit.text() + maintainer["email"] = self.maintainerEmailEdit.text() + maintainers.append(maintainer) + project["maintainers"] = maintainers + + urls = tomlkit.table() + urls["Homepage"] = self.homePageUrlEdit.text() + if self.downloadUrlEdit.text(): + urls["Download"] = self.downloadUrlEdit.text() + + if self.projectUrlsList.topLevelItemCount(): + for row in range(self.projectUrlsList.topLevelItemCount()): + urlItem = self.projectUrlsList.topLevelItem(row) + urls[urlItem.text(0)] = urlItem.text(1) + project["urls"] = urls + + classifiers = [] + if not self.licenseClassifierCheckBox.isChecked(): + licenseTbl = tomlkit.table() + licenseTbl["text"] = self.licenseEdit.text() + project["license"] = licenseTbl + else: + classifiers.append( + self.licenseClassifierComboBox.itemData( + self.licenseClassifierComboBox.currentIndex())) + + if self.developmentStatusComboBox.currentIndex() != 0: + classifiers.append(self.developmentStatusComboBox.currentData()) + + itm = self.classifiersList.topLevelItem(0) + while itm: + itm.setExpanded(True) + if itm.checkState(0) == Qt.CheckState.Checked: + classifiers.append(itm.data(0, Qt.ItemDataRole.UserRole)) + itm = self.classifiersList.itemBelow(itm) + + # cleanup classifiers list - remove all invalid entries + classifiers = [c for c in classifiers if bool(c)] + if classifiers: + classifiersArray = tomlkit.array() + for classifier in classifiers: + classifiersArray.add_line(classifier) + classifiersArray.append(tomlkit.nl()) + project["classifiers"] = classifiersArray + + if self.keywordsEdit.text(): + keywords = tomlkit.array() + for kw in self.keywordsEdit.text().split(): + keywords.add_line(kw) + keywords.append(tomlkit.nl()) + project["keywords"] = keywords + + if self.pyVersionEdit.text(): + project["requires-python"] = self.pyVersionEdit.text() + + if self.entryPointsList.topLevelItemCount(): + entryPoints = { + "console_scripts": {}, + "gui_scripts": {}, + } + for row in range(self.entryPointsList.topLevelItemCount()): + itm = self.entryPointsList.topLevelItem(row) + entryPoints[itm.data(0, Qt.ItemDataRole.UserRole)][ + itm.text(1)] = itm.text(2) + + if entryPoints["console_scripts"]: + scripts = tomlkit.table() + for name, function in entryPoints["console_scripts"].items(): + scripts[name] = function + project["scripts"] = scripts + + if entryPoints["gui_scripts"]: + guiScripts = tomlkit.table() + for name, function in entryPoints["gui_scripts"].items(): + guiScripts[name] = function + project["gui-scripts"] = guiScripts + + # placeholder + dependencies = tomlkit.array() + dependencies.append(tomlkit.comment( + "TODO: enter project dependencies " # __NO-TASK__ + )) + project["dependencies"] = dependencies + + doc["project"] = project + + setuptools = tomlkit.table() + + platforms = self.platformsEdit.toPlainText().splitlines() + if platforms: + platformsArray = tomlkit.array() + for plt in platforms: + platformsArray.add_line(plt) + platformsArray.append(tomlkit.nl()) + setuptools["platforms"] = platformsArray + + setuptools["include-package-data"] = self.includePackageDataCheckBox.isChecked() + if self.includePackageDataCheckBox.isChecked(): + # placeholder + setuptools["package-data"] = tomlkit.table() + setuptools["package-data"].add(tomlkit.comment( + "TODO: enter package data patterns" # __NO-TASK__ + )) + + if self.modulesList.count(): + modulesArray = tomlkit.array() + for row in range(self.modulesList.count()): + modulesArray.add_line(self.modulesList.item(row).text()) + modulesArray.append(tomlkit.nl()) + setuptools["py-modules"] = modulesArray + + findspec = tomlkit.table() + src = Utilities.fromNativeSeparators(self.sourceDirectoryPicker.text()) + excludePatterns = [] + for row in range(self.excludePatternList.count()): + excludePatterns.append( + self.excludePatternList.item(row).text()) + if src: + findspec["where"] = [ericApp().getObject("Project").getRelativePath(src)] + if excludePatterns: + excludePatternsArray = tomlkit.array() + for pattern in excludePatterns: + excludePatternsArray.add_line(pattern) + excludePatternsArray.append(tomlkit.nl()) + findspec["exclude"] = excludePatternsArray + + if bool(findspec): + setuptools["packages"] = tomlkit.table(is_super_table=True) + setuptools["packages"]["find"] = findspec + + doc["tool"] = tomlkit.table(is_super_table=True) + doc["tool"]["setuptools"] = setuptools + + sourceCode = tomlkit.dumps(doc) + return sourceCode + + @pyqtSlot() + def accept(self): + """ + Public slot to handle pressing the OK button. + """ + line, index = self.__editor.getCursorPosition() + indLevel = self.__editor.indentation(line) // self.__editor.indentationWidth() + indString = ( + '\t' + if self.__editor.indentationsUseTabs() else + self.__editor.indentationWidth() * ' ' + ) + + if self.__category == "setup.py": + sourceCode = self.__getSetupPyCode(indLevel, indString) + elif self.__category == "setup.cfg": + sourceCode = self.__getSetupCfgCode() + elif self.__category == "pyproject.toml": + sourceCode = self.__getPyprojectCode() + else: + # should not happen, but play it safe + sourceCode = "" + + if sourceCode: + line, index = self.__editor.getCursorPosition() + # It should be done this way to allow undo + self.__editor.beginUndoAction() + self.__editor.insertAt(sourceCode, line, index) + self.__editor.endUndoAction() + + super().accept() + + @pyqtSlot() + def on_projectButton_clicked(self): + """ + Private slot to populate some fields with data retrieved from the + current project. + """ + project = ericApp().getObject("Project") + + self.nameEdit.setText(project.getProjectName()) + try: + self.versionEdit.setText(project.getProjectVersion()) + self.authorEdit.setText(project.getProjectAuthor()) + self.authorEmailEdit.setText(project.getProjectAuthorEmail()) + description = project.getProjectDescription() + except AttributeError: + self.versionEdit.setText(project.pdata["VERSION"][0]) + self.authorEdit.setText(project.pdata["AUTHOR"][0]) + self.authorEmailEdit.setText(project.pdata["EMAIL"][0]) + description = project.pdata["DESCRIPTION"][0] + + summary = ( + description.split(".", 1)[0].replace("\r", "").replace("\n", "") + + "." + ) + self.summaryEdit.setText(summary) + self.descriptionEdit.setPlainText(description) + + self.packageRootPicker.setText(project.getProjectPath()) + + # prevent overwriting of entries by disabling the button + self.projectButton.setEnabled(False) + + def __getStartDir(self): + """ + Private method to get the start directory for selection dialogs. + + @return start directory + @rtype str + """ + return (Preferences.getMultiProject("Workspace") or + Utilities.getHomeDir()) + + @pyqtSlot() + def on_entryPointsList_itemSelectionChanged(self): + """ + Private slot to handle a change of selected items of the + entry points list. + """ + self.deleteEntryPointButton.setEnabled( + bool(self.entryPointsList.selectedItems())) + self.editEntryPointButton.setEnabled( + len(self.entryPointsList.selectedItems()) == 1) + + @pyqtSlot() + def on_deleteEntryPointButton_clicked(self): + """ + Private slot to delete the selected entry point items. + """ + for itm in self.entryPointsList.selectedItems(): + self.entryPointsList.takeTopLevelItem(self.entryPointsList.row(itm)) + del itm + + @pyqtSlot() + def on_addEntryPointButton_clicked(self): + """ + Private slot to add an entry point to the list. + """ + project = ericApp().getObject("Project") + rootDir = ( + project.getProjectPath() + if project.isOpen() else + "" + ) + dlg = AddEntryPointDialog(rootDir, parent=self) + if dlg.exec() == QDialog.DialogCode.Accepted: + epType, epCategory, name, script = dlg.getEntryPoint() + itm = QTreeWidgetItem(self.entryPointsList, [epType, name, script]) + itm.setData(0, Qt.ItemDataRole.UserRole, epCategory) + + @pyqtSlot() + def on_editEntryPointButton_clicked(self): + """ + Private slot to edit the selected entry point. + """ + project = ericApp().getObject("Project") + rootDir = ( + project.getProjectPath() + if project.isOpen() else + "" + ) + itm = self.entryPointsList.selectedItems()[0] + dlg = AddEntryPointDialog(rootDir, epType=itm.text(0), name=itm.text(1), + script=itm.text(2), parent=self) + if dlg.exec() == QDialog.DialogCode.Accepted: + epType, epCategory, name, script = dlg.getEntryPoint() + itm.setText(0, epType) + itm.setText(1, name) + itm.setText(2, script) + itm.setData(0, Qt.ItemDataRole.UserRole, epCategory) + + @pyqtSlot() + def on_modulesList_itemSelectionChanged(self): + """ + Private slot to handle a change of selected items of the + modules list. + """ + self.deleteModuleButton.setEnabled( + bool(self.modulesList.selectedItems())) + + @pyqtSlot() + def on_deleteModuleButton_clicked(self): + """ + Private slot to delete the selected module items. + """ + for itm in self.modulesList.selectedItems(): + self.modulesList.takeItem(self.modulesList.row(itm)) + del itm + + @pyqtSlot() + def on_addModuleButton_clicked(self): + """ + Private slot to add Python modules to the list. + """ + startDir = self.packageRootPicker.text() or self.__getStartDir() + modulesList = EricFileDialog.getOpenFileNames( + self, + self.tr("Add Python Modules"), + startDir, + self.tr("Python Files (*.py)")) + for module in modulesList: + module = module.replace( + Utilities.toNativeSeparators(startDir), "") + if module.startswith(("\\", "/")): + module = module[1:] + if module: + QListWidgetItem( + str(pathlib.Path(module).with_suffix("")) + .replace("\\", ".") + .replace("/", "."), + self.modulesList + ) + + @pyqtSlot() + def on_excludePatternList_itemSelectionChanged(self): + """ + Private slot to handle a change of selected items of the + exclude pattern list. + """ + self.deleteExcludePatternButton.setEnabled( + bool(self.excludePatternList.selectedItems())) + + @pyqtSlot() + def on_deleteExcludePatternButton_clicked(self): + """ + Private slot to delete the selected exclude pattern items. + """ + for itm in self.excludePatternList.selectedItems(): + self.excludePatternList.takeItem( + self.excludePatternList.row(itm)) + del itm + + @pyqtSlot() + def on_addExludePatternButton_clicked(self): + """ + Private slot to add an exclude pattern to the list. + """ + pattern = ( + self.excludePatternEdit.text().replace("\\", ".").replace("/", ".") + ) + if not self.excludePatternList.findItems( + pattern, + Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive + ): + QListWidgetItem(pattern, self.excludePatternList) + + @pyqtSlot(str) + def on_excludePatternEdit_textChanged(self, txt): + """ + Private slot to handle a change of the exclude pattern text. + + @param txt text of the line edit + @type str + """ + self.addExludePatternButton.setEnabled(bool(txt)) + + @pyqtSlot() + def on_excludePatternEdit_returnPressed(self): + """ + Private slot handling a press of the return button of the + exclude pattern edit. + """ + self.on_addExludePatternButton_clicked() + + @pyqtSlot() + def on_urlDeleteButton_clicked(self): + """ + Private slot to delete the selected URL items. + """ + for itm in self.projectUrlsList.selectedItems(): + self.projectUrlsList.takeTopLevelItem(self.projectUrlsList.row(itm)) + del itm + + @pyqtSlot() + def on_urlAddButton_clicked(self): + """ + Private slot to add a project URL to the list. + """ + dlg = AddProjectUrlDialog(parent=self) + if dlg.exec() == QDialog.DialogCode.Accepted: + name, url = dlg.getUrl() + QTreeWidgetItem(self.projectUrlsList, [name, url]) + + @pyqtSlot() + def on_urlEditButton_clicked(self): + """ + Private slot to edit the selected project URL. + """ + itm = self.projectUrlsList.selectedItems()[0] + dlg = AddProjectUrlDialog(name=itm.text(0), url=itm.text(1), parent=self) + if dlg.exec() == QDialog.DialogCode.Accepted: + name, url = dlg.getUrl() + itm.setText(0, name) + itm.setText(1, url) + + @pyqtSlot() + def on_projectUrlsList_itemSelectionChanged(self): + """ + Private slot to handle a change of selected items of the + project URLs list. + """ + self.urlDeleteButton.setEnabled(bool(self.projectUrlsList.selectedItems())) + self.urlEditButton.setEnabled(len(self.projectUrlsList.selectedItems()) == 1)