Addon Manager: Add <bundle> and <other>

This commit is contained in:
Chris Hennes
2025-02-22 22:03:32 -06:00
parent 6d353393ff
commit e80ee07d63
11 changed files with 109 additions and 23 deletions

View File

@@ -489,23 +489,17 @@ class Addon:
if self.repo_type == Addon.Kind.WORKBENCH:
return True
if self.repo_type == Addon.Kind.PACKAGE:
if self.metadata is None:
fci.Console.PrintLog(
f"Addon Manager internal error: lost metadata for package {self.name}\n"
)
return False
content = self.metadata.content
if not content:
return False
return "workbench" in content
return False
return self.contains_packaged_content("workbench")
def contains_macro(self) -> bool:
"""Determine if this package contains (or is) a macro"""
if self.repo_type == Addon.Kind.MACRO:
return True
return self.contains_packaged_content("macro")
def contains_packaged_content(self, content_type: str):
"""Determine if the package contains content_type"""
if self.repo_type == Addon.Kind.PACKAGE:
if self.metadata is None:
fci.Console.PrintLog(
@@ -513,21 +507,20 @@ class Addon:
)
return False
content = self.metadata.content
return "macro" in content
return content_type in content
return False
def contains_preference_pack(self) -> bool:
"""Determine if this package contains a preference pack"""
return self.contains_packaged_content("preferencepack")
if self.repo_type == Addon.Kind.PACKAGE:
if self.metadata is None:
fci.Console.PrintLog(
f"Addon Manager internal error: lost metadata for package {self.name}\n"
)
return False
content = self.metadata.content
return "preferencepack" in content
return False
def contains_bundle(self) -> bool:
"""Determine if this package contains a bundle"""
return self.contains_packaged_content("bundle")
def contains_other(self) -> bool:
"""Determine if this package contains an "other" content item"""
return self.contains_packaged_content("other")
def get_best_icon_relative_path(self) -> str:
"""Get the path within the repo the addon's icon. Usually specified by

View File

@@ -93,6 +93,8 @@ class TestAddon(unittest.TestCase):
self.assertTrue(addon_with_workbench.contains_workbench())
self.assertFalse(addon_with_workbench.contains_macro())
self.assertFalse(addon_with_workbench.contains_preference_pack())
self.assertFalse(addon_with_workbench.contains_bundle())
self.assertFalse(addon_with_workbench.contains_other())
# Macros
addon_with_macro = Addon(
@@ -105,6 +107,8 @@ class TestAddon(unittest.TestCase):
self.assertFalse(addon_with_macro.contains_workbench())
self.assertTrue(addon_with_macro.contains_macro())
self.assertFalse(addon_with_macro.contains_preference_pack())
self.assertFalse(addon_with_workbench.contains_bundle())
self.assertFalse(addon_with_workbench.contains_other())
# Preference Packs
addon_with_prefpack = Addon(
@@ -117,6 +121,8 @@ class TestAddon(unittest.TestCase):
self.assertFalse(addon_with_prefpack.contains_workbench())
self.assertFalse(addon_with_prefpack.contains_macro())
self.assertTrue(addon_with_prefpack.contains_preference_pack())
self.assertFalse(addon_with_workbench.contains_bundle())
self.assertFalse(addon_with_workbench.contains_other())
# Combination
addon_with_all = Addon(
@@ -129,6 +135,8 @@ class TestAddon(unittest.TestCase):
self.assertTrue(addon_with_all.contains_workbench())
self.assertTrue(addon_with_all.contains_macro())
self.assertTrue(addon_with_all.contains_preference_pack())
self.assertTrue(addon_with_all.contains_bundle())
self.assertTrue(addon_with_all.contains_other())
# Now do the simple, explicitly-set cases
addon_wb = Addon(

View File

@@ -616,6 +616,22 @@ class TestMetadataReaderIntegration(unittest.TestCase):
expected_packs.remove(wb.name)
self.assertEqual(len(expected_packs), 0)
def test_bundle(self):
from addonmanager_metadata import MetadataReader
filename = os.path.join(self.test_data_dir, "bundle_only.xml")
metadata = MetadataReader.from_file(filename)
self.assertIn("bundle", metadata.content)
self.assertEqual(len(metadata.content["bundle"]), 1)
def test_other(self):
from addonmanager_metadata import MetadataReader
filename = os.path.join(self.test_data_dir, "other_only.xml")
metadata = MetadataReader.from_file(filename)
self.assertIn("other", metadata.content)
self.assertEqual(len(metadata.content["other"]), 1)
def test_content_combination(self):
from addonmanager_metadata import MetadataReader

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" standalone="no" ?>
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
<name>Test Bundle</name>
<description>A package.xml file for unit testing.</description>
<version>1.0.0</version>
<date>2025-02-22</date>
<maintainer email="developer@freecad.org">FreeCAD Developer</maintainer>
<license file="LICENSE">LGPL-2.1</license>
<url type="repository" branch="main">https://github.com/chennes/FreeCAD-Package</url>
<url type="readme">https://github.com/chennes/FreeCAD-Package/blob/main/README.md</url>
<content>
<bundle>
<name>A bunch of great addons you should install</name>
<depend type="addon">TestAddon1</depend>
<depend type="addon">TestAddon2</depend>
<depend type="addon">TestAddon3</depend>
<depend type="addon">TestAddon4</depend>
<depend type="addon">TestAddon5</depend>
</bundle>
</content>
</package>

View File

@@ -23,6 +23,12 @@
<preferencepack>
<name>MyFirstPack</name>
</preferencepack>
<bundle>
<name>A bundle that bundles nothing</name>
</bundle>
<other>
<name>Mysterious Object</name>
</other>
</content>
</package>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" standalone="no" ?>
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
<name>Test Other</name>
<description>A package.xml file for unit testing.</description>
<version>1.0.0</version>
<date>2025-02-22</date>
<maintainer email="developer@freecad.org">FreeCAD Developer</maintainer>
<license file="LICENSE">LGPL-2.1</license>
<url type="repository" branch="main">https://github.com/chennes/FreeCAD-Package</url>
<url type="readme">https://github.com/chennes/FreeCAD-Package/blob/main/README.md</url>
<content>
<other>
<name>A thing that's not a workbench, macro, preference pack, or bundle</name>
</other>
</content>
</package>

View File

@@ -118,6 +118,7 @@ SET(AddonManagerTestsGui_SRCS
SET(AddonManagerTestsFiles_SRCS
AddonManagerTest/data/__init__.py
AddonManagerTest/data/addon_update_stats.json
AddonManagerTest/data/bundle_only.xml
AddonManagerTest/data/combination.xml
AddonManagerTest/data/corrupted_metadata.zip
AddonManagerTest/data/depends_on_all_workbenches.xml
@@ -131,6 +132,7 @@ SET(AddonManagerTestsFiles_SRCS
AddonManagerTest/data/MacrosRecipesWikiPage.zip
AddonManagerTest/data/metadata.zip
AddonManagerTest/data/missing_macro_metadata.FCStd
AddonManagerTest/data/other_only.xml
AddonManagerTest/data/prefpack_only.xml
AddonManagerTest/data/test_addon_with_fcmacro.zip
AddonManagerTest/data/test_github_style_repo.zip

View File

@@ -69,6 +69,8 @@ class ContentFilter(IntEnum):
WORKBENCH = 1
MACRO = 2
PREFERENCE_PACK = 3
BUNDLE = 4
OTHER = 5
class Filter:
@@ -116,6 +118,14 @@ class WidgetFilterSelector(QtWidgets.QComboBox):
translate("AddonsInstaller", "Preference Pack"),
(FilterType.PACKAGE_CONTENTS, ContentFilter.PREFERENCE_PACK),
)
self.addItem(
translate("AddonsInstaller", "Bundle"),
(FilterType.PACKAGE_CONTENTS, ContentFilter.BUNDLE),
)
self.addItem(
translate("AddonsInstaller", "Other"),
(FilterType.PACKAGE_CONTENTS, ContentFilter.OTHER),
)
self.insertSeparator(self.count())
self.addItem(translate("AddonsInstaller", "Installation Status"))
self.installation_status_index = self.count() - 1

View File

@@ -71,6 +71,8 @@ class AddContent:
self.dialog.addonKindComboBox.setItemData(0, "macro")
self.dialog.addonKindComboBox.setItemData(1, "preferencepack")
self.dialog.addonKindComboBox.setItemData(2, "workbench")
self.dialog.addonKindComboBox.setItemData(3, "bundle")
self.dialog.addonKindComboBox.setItemData(4, "other")
self.people_table = PeopleTable()
self.licenses_table = LicensesTable()
@@ -148,6 +150,8 @@ class AddContent:
self.dialog.macroFileLineEdit.setText(files[0])
elif addon_kind == "preferencepack":
self.dialog.prefPackNameLineEdit.setText(self.metadata.Name)
elif addon_kind == "bundle" or addon_kind == "other":
pass
else:
raise RuntimeError("Invalid data found for selection")

View File

@@ -367,7 +367,7 @@ class MetadataReader:
def _parse_content(namespace: str, metadata: Metadata, root: ET.Element):
"""Given a content node, loop over its children, and if they are a recognized
element type, recurse into each one to parse it."""
known_content_types = ["workbench", "macro", "preferencepack"]
known_content_types = ["workbench", "macro", "preferencepack", "bundle", "other"]
for child in root:
content_type = child.tag[len(namespace) :]
if content_type in known_content_types:

View File

@@ -569,7 +569,7 @@ class PackageListFilter(QtCore.QSortFilterProxyModel):
def setPackageFilter(
self, package_type: int
) -> None: # 0=All, 1=Workbenches, 2=Macros, 3=Preference Packs
) -> None: # 0=All, 1=Workbenches, 2=Macros, 3=Preference Packs, 4=Bundles, 5=Other
"""Set the package filter to package_type and refreshes."""
self.package_type = package_type
self.invalidateFilter()
@@ -634,6 +634,12 @@ class PackageListFilter(QtCore.QSortFilterProxyModel):
elif self.package_type == 3:
if not data.contains_preference_pack():
return False
elif self.package_type == 4:
if not data.contains_bundle():
return False
elif self.package_type == 5:
if not data.contains_other():
return False
if self.status == StatusFilter.INSTALLED:
if data.status() == Addon.Status.NOT_INSTALLED: