Addon Manager: Cleanup

This commit is contained in:
Chris Hennes
2022-09-08 22:19:30 -05:00
parent 354a29d98a
commit 139b99d371
11 changed files with 77 additions and 169 deletions

View File

@@ -655,7 +655,9 @@ void Metadata::appendToElement(DOMElement* root) const
{
appendSimpleXMLNode(root, "name", _name);
appendSimpleXMLNode(root, "description", _description);
appendSimpleXMLNode(root, "version", _version.str());
if (_version != Meta::Version())
// Only append version if it's not 0.0.0
appendSimpleXMLNode(root, "version", _version.str());
for (const auto& maintainer : _maintainer) {
auto element = appendSimpleXMLNode(root, "maintainer", maintainer.name);
@@ -977,6 +979,8 @@ Meta::Version::Version(const std::string& versionString) :
std::string Meta::Version::str() const
{
if (*this == Meta::Version())
return "";
std::ostringstream stream;
stream << major << "." << minor << "." << patch << suffix;
return stream.str();

View File

@@ -365,6 +365,13 @@ Add a new File. </UserDocu>
Remove the File. </UserDocu>
</Documentation>
</Methode>
<Methode Name="write">
<Documentation>
<UserDocu>write(filename)\n
Write the metadata to the given file as XML data.</UserDocu>
</Documentation>
</Methode>
<ClassDeclarations>
public:

View File

@@ -139,7 +139,7 @@ void MetadataPy::setVersion(Py::Object args)
const char *name = nullptr;
if (!PyArg_Parse(args.ptr(), "z", &name))
throw Py::Exception();
if (name)
if (name && name[0] != '\0')
getMetadataPtr()->setVersion(App::Meta::Version(std::string(name)));
else
getMetadataPtr()->setVersion(App::Meta::Version());
@@ -934,6 +934,18 @@ PyObject *MetadataPy::removeContentItem(PyObject *arg)
return Py_None;
}
PyObject* MetadataPy::write(PyObject* args)
{
char *filename = nullptr;
if (!PyArg_ParseTuple(args, "s", &filename))
return nullptr;
getMetadataPtr()->write(filename);
Py_INCREF(Py_None);
return Py_None;
}
PyObject *MetadataPy::getCustomAttributes(const char * /*attr*/) const
{
return nullptr;

View File

@@ -9,7 +9,6 @@ SET(AddonManager_SRCS
AddonManager.ui
addonmanager_devmode.py
addonmanager_devmode_add_content.py
addonmanager_devmode_dependencies.py
addonmanager_devmode_license_selector.py
addonmanager_devmode_licenses_table.py
addonmanager_devmode_person_editor.py

View File

@@ -30,7 +30,6 @@ import FreeCADGui
from PySide2.QtWidgets import (
QFileDialog,
QTableWidgetItem,
QListWidgetItem,
QDialog,
QSizePolicy,
@@ -90,12 +89,14 @@ class AddonGitInterface:
return AddonGitInterface.git_manager.get_last_authors(self.path, 10)
return []
#pylint: disable=too-many-instance-attributes
class DeveloperMode:
"""The main Developer Mode dialog, for editing package.xml metadata graphically."""
def __init__(self):
# In the UI we want to show a translated string for the person type, but the underlying
# string must be the one expected by the metadata parser, in English
self.person_type_translation = {

View File

@@ -41,19 +41,11 @@ from PySide2.QtWidgets import (
from PySide2.QtGui import QIcon
from PySide2.QtCore import Qt
from addonmanager_devmode_license_selector import LicenseSelector
from addonmanager_devmode_person_editor import PersonEditor
from addonmanager_devmode_validators import (
VersionValidator,
NameValidator,
PythonIdentifierValidator,
)
from addonmanager_devmode_utilities import (
populate_people_from_metadata,
populate_licenses_from_metadata,
add_license_row,
add_person_row,
)
from addonmanager_devmode_people_table import PeopleTable
from addonmanager_devmode_licenses_table import LicensesTable
@@ -261,10 +253,10 @@ class AddContent:
licenses = []
for row in range(self.dialog.licensesTableWidget.rowCount()):
license = {}
license["name"] = self.dialog.licensesTableWidget.item(row, 0).text
license["file"] = self.dialog.licensesTableWidget.item(row, 1).text()
licenses.append(license)
new_license = {}
new_license["name"] = self.dialog.licensesTableWidget.item(row, 0).text
new_license["file"] = self.dialog.licensesTableWidget.item(row, 1).text()
licenses.append(new_license)
self.metadata.License = licenses
return (self.dialog.addonKindComboBox.currentData(), self.metadata)
@@ -370,98 +362,7 @@ class AddContent:
if not self.metadata:
self.metadata = FreeCAD.Metadata()
dlg = EditDependencies()
result = dlg.exec(self.metadata)
def _person_selection_changed(self):
"""Callback: the current selection in the peopleTableWidget changed"""
items = self.dialog.peopleTableWidget.selectedItems()
if items:
self.dialog.removePersonToolButton.setDisabled(False)
else:
self.dialog.removePersonToolButton.setDisabled(True)
def _license_selection_changed(self):
"""Callback: the current selection in the licensesTableWidget changed"""
items = self.dialog.licensesTableWidget.selectedItems()
if items:
self.dialog.removeLicenseToolButton.setDisabled(False)
else:
self.dialog.removeLicenseToolButton.setDisabled(True)
def _add_license_clicked(self):
"""Callback: The Add License button was clicked"""
license_selector = LicenseSelector(self.current_mod)
short_code, path = license_selector.exec()
if short_code:
add_license_row(
self.dialog.licensesTableWidget.rowCount(),
short_code,
path,
self.path_to_addon,
self.dialog.licensesTableWidget,
)
def _remove_license_clicked(self):
"""Callback: the Remove License button was clicked"""
items = self.dialog.licensesTableWidget.selectedIndexes()
if items:
# We only support single-selection, so can just pull the row # from
# the first entry
self.dialog.licensesTableWidget.removeRow(items[0].row())
def _edit_license(self, item):
"""Callback: a license row was double-clicked"""
row = item.row()
short_code = self.dialog.licensesTableWidget.item(row, 0).text()
path = self.dialog.licensesTableWidget.item(row, 1).text()
license_selector = LicenseSelector(self.current_mod)
short_code, path = license_selector.exec(short_code, path)
if short_code:
self.dialog.licensesTableWidget.removeRow(row)
add_license_row(
row,
short_code,
path,
self.path_to_addon,
self.dialog.licensesTableWidget,
)
def _add_person_clicked(self):
"""Callback: the Add Person button was clicked"""
dlg = PersonEditor()
person_type, name, email = dlg.exec()
if person_type and name:
add_person_row(
self.dialog.peopleTableWidget.rowCount(),
person_type,
name,
email,
self.dialog.peopleTableWidget,
)
def _remove_person_clicked(self):
"""Callback: the Remove Person button was clicked"""
items = self.dialog.peopleTableWidget.selectedIndexes()
if items:
# We only support single-selection, so can just pull the row # from
# the first entry
self.dialog.peopleTableWidget.removeRow(items[0].row())
def _edit_person(self, item):
"""Callback: a row in the peopleTableWidget was double-clicked"""
row = item.row()
person_type = self.dialog.peopleTableWidget.item(row, 0).data(Qt.UserRole)
name = self.dialog.peopleTableWidget.item(row, 1).text()
email = self.dialog.peopleTableWidget.item(row, 2).text()
dlg = PersonEditor()
dlg.setup(person_type, name, email)
person_type, name, email = dlg.exec()
if person_type and name:
self.dialog.peopleTableWidget.removeRow(row)
add_person_row(row, person_type, name, email, self.dialog.peopleTableWidget)
self.dialog.peopleTableWidget.selectRow(row)
dlg.exec(self.metadata) # Modifies metadata directly
class EditTags:
@@ -513,6 +414,7 @@ class EditDependencies:
)
self.dialog.removeDependencyToolButton.setDisabled(True)
self.metadata = None
def exec(self, metadata: FreeCAD.Metadata):
"""Execute the dialog"""

View File

@@ -1,21 +0,0 @@
# ***************************************************************************
# * *
# * Copyright (c) 2022 FreeCAD Project Association *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************

View File

@@ -26,7 +26,6 @@ import os
from PySide2.QtWidgets import QTableWidgetItem
from PySide2.QtGui import QIcon
from PySide2.QtCore import Qt
import FreeCAD
import FreeCADGui
@@ -35,6 +34,7 @@ from addonmanager_devmode_license_selector import LicenseSelector
translate = FreeCAD.Qt.translate
#pylint: disable=too-few-public-methods
class LicensesTable:
"""A QTableWidget and associated buttons for managing the list of authors and maintainers."""
@@ -55,6 +55,8 @@ class LicensesTable:
self.widget.removeButton.clicked.connect(self._remove_clicked)
self.widget.tableWidget.itemSelectionChanged.connect(self._selection_changed)
self.widget.tableWidget.itemDoubleClicked.connect(self._edit)
self.metadata = None
self.path_to_addon = ""
def show(self, metadata, path_to_addon):
"""Set up the widget based on incoming metadata"""
@@ -68,9 +70,9 @@ class LicensesTable:
"""Use the passed metadata object to populate the maintainers and authors"""
self.widget.tableWidget.setRowCount(0)
row = 0
for license in self.metadata.License:
shortcode = license["name"]
path = license["file"]
for l in self.metadata.License:
shortcode = l["name"]
path = l["file"]
self._add_row(row, shortcode, path)
row += 1

View File

@@ -35,6 +35,8 @@ from addonmanager_devmode_person_editor import PersonEditor
translate = FreeCAD.Qt.translate
#pylint: disable=too-few-public-methods
class PeopleTable:
"""A QTableWidget and associated buttons for managing the list of authors and maintainers."""
@@ -55,6 +57,7 @@ class PeopleTable:
self.widget.removeButton.clicked.connect(self._remove_clicked)
self.widget.tableWidget.itemSelectionChanged.connect(self._selection_changed)
self.widget.tableWidget.itemDoubleClicked.connect(self._edit)
self.metadata = None
def show(self, metadata):
"""Set up the widget based on incoming metadata"""

View File

@@ -32,11 +32,13 @@ import datetime
import os
import FreeCAD
from addonmanager_git import initialize_git, GitFailed, GitManager
from addonmanager_git import initialize_git, GitManager
from addonmanager_utilities import get_readme_url
translate = FreeCAD.Qt.translate
#pylint: disable=too-few-public-methods
class AddonSlice:
"""A tiny class to implement duck-typing for the URL-parsing utility functions"""
@@ -54,6 +56,7 @@ class Predictor:
self.path = None
self.metadata = FreeCAD.Metadata()
self.license_data = None
self.readme_data = None
self.license_file = ""
self.git_manager: GitManager = initialize_git()
if not self.git_manager:
@@ -86,28 +89,26 @@ class Predictor:
# is common for there to be multiple entries representing the same human being,
# so a passing attempt is made to reconcile:
filtered_committers = {}
for key in committers:
emails = committers[key]["email"]
for key,committer in committers.items():
if "github" in key.lower():
# Robotic merge commit (or other similar), ignore
continue
# Does any other committer share any of these emails?
for other_key in committers:
for other_key,other_committer in committers.items():
if other_key == key:
continue
other_emails = committers[other_key]["email"]
for other_email in other_emails:
if other_email in emails:
for other_email in other_committer["email"]:
if other_email in committer["email"]:
# There is overlap in the two email lists, so this is probably the
# same author, with a different name (username, pseudonym, etc.)
if not committers[key]["aka"]:
committers[key]["aka"] = set()
committers[key]["aka"].add(other_key)
committers[key]["count"] += committers[other_key]["count"]
committers[key]["email"].combine(committers[other_key]["email"])
committers.remove(other_key)
if not committer["aka"]:
committer["aka"] = set()
committer["aka"].add(other_key)
committer["count"] += other_committer["count"]
committer["email"].combine(other_committer["email"])
committers.pop(other_key)
break
filtered_committers[key] = committers[key]
filtered_committers[key] = committer
maintainers = []
for name, info in filtered_committers.items():
if "aka" in info:
@@ -181,8 +182,8 @@ class Predictor:
def _predict_license(self):
"""Predict the license based on any existing license file."""
# These are processed in order, so the BSD 3 clause must come before the 2, for example, because
# the only difference between them is the additional clause.
# These are processed in order, so the BSD 3 clause must come before the 2, for example,
# because the only difference between them is the additional clause.
known_strings = {
"Apache-2.0": (
"Apache License, Version 2.0",
@@ -190,15 +191,20 @@ class Predictor:
),
"BSD-3-Clause": (
"The 3-Clause BSD License",
"3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.",
"3. Neither the name of the copyright holder nor the names of its contributors \
may be used to endorse or promote products derived from this software without \
specific prior written permission.",
),
"BSD-2-Clause": (
"The 2-Clause BSD License",
"2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.",
"2. Redistributions in binary form must reproduce the above copyright notice, \
this list of conditions and the following disclaimer in the documentation and/or \
other materials provided with the distribution.",
),
"CC0v1": (
"CC0 1.0 Universal",
"voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms",
"voluntarily elects to apply CC0 to the Work and publicly distribute the Work \
under its terms",
),
"GPLv2": (
"GNU General Public License version 2",
@@ -206,7 +212,8 @@ class Predictor:
),
"GPLv3": (
"GNU General Public License version 3",
"The GNU General Public License is a free, copyleft license for software and other kinds of works.",
"The GNU General Public License is a free, copyleft license for software and \
other kinds of works.",
),
"LGPLv2.1": (
"GNU Lesser General Public License version 2.1",
@@ -218,7 +225,8 @@ class Predictor:
),
"MIT": (
"The MIT License",
"including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software",
"including without limitation the rights to use, copy, modify, merge, publish, \
distribute, sublicense, and/or sell copies of the Software",
),
"MPL-2.0": (
"Mozilla Public License 2.0",
@@ -227,9 +235,9 @@ class Predictor:
}
self._load_license()
if self.license_data:
for license, test_data in known_strings.items():
if license.lower() in self.license_data.lower():
self.metadata.addLicense(license, self.license_file)
for shortcode, test_data in known_strings.items():
if shortcode.lower() in self.license_data.lower():
self.metadata.addLicense(shortcode, self.license_file)
return
for test_text in test_data:
# Do the comparison without regard to whitespace or capitalization
@@ -237,7 +245,7 @@ class Predictor:
"".join(test_text.split()).lower()
in "".join(self.license_data.split()).lower()
):
self.metadata.addLicense(license, self.license_file)
self.metadata.addLicense(shortcode, self.license_file)
return
def _predict_version(self):

View File

@@ -30,17 +30,7 @@ from PySide2.QtGui import (
)
from PySide2.QtCore import QRegularExpression
def isidentifier(ident: str) -> bool:
if not ident.isidentifier():
return False
if keyword.iskeyword(ident):
return False
return True
#pylint: disable=too-few-public-methods
class NameValidator(QValidator):
"""Simple validator to exclude characters that are not valid in filenames."""
@@ -67,6 +57,7 @@ class PythonIdentifierValidator(QValidator):
"""Validates whether input is a valid Python identifier."""
def validate(self, value: str, _: int):
""" The function that does the validation. """
if not value:
return QValidator.Intermediate