From eaeeb7349504d83cd3251daba4c557854c46cd00 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Tue, 20 Oct 2026 12:29:03 +0200 Subject: Packages: orphaned filter chip SBo marks an orphaned package MAINTAINER="orphaned - no maintainer" (1936 in the tree today); the inventory already carries the maintainer, so the chip filters in the GUI alone. The shown/selected count moves next to the search field so the chip row fits the default window width. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- sb_builder/hints_tab.py | 8 +++++--- sb_builder/packages.py | 32 ++++++++++++++++++++++---------- sb_builder/ui.py | 2 +- tests/test_gui.py | 7 ++++++- 5 files changed, 35 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index ccf5edd..ca3613f 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ reopens it. - **Packages**: every package of the repository, with built version and status (`ok`, `failed`, `never`, `outdated`). Search by name or summary, or with `cat:`, `maint:`, `req:`, `status:`; toggle the overlay and status - chips. The detail pane shows the package's versions, its dependencies after + chips, and **orphaned** for the packages SBo lists without a maintainer. The detail pane shows the package's versions, its dependencies after hints (dropped ones struck through), what its hint file does, and a link to it in Hints. **Build**, **Update**, **Rebuild**, **Remove** and **Update all** run a slackrepo `--preview` first; **New hint** creates hints, pinned diff --git a/sb_builder/hints_tab.py b/sb_builder/hints_tab.py index 071e304..fbf8a70 100644 --- a/sb_builder/hints_tab.py +++ b/sb_builder/hints_tab.py @@ -61,8 +61,6 @@ class HintsTab(QWidget): self.chips[name].toggled.connect(self.refilter) chips.addWidget(self.chips[name]) chips.addStretch() - self.status = QLabel() - chips.addWidget(self.status) self.table = QTableWidget(0, len(COLUMNS)) self.table.setHorizontalHeaderLabels(COLUMNS) @@ -97,8 +95,12 @@ class HintsTab(QWidget): for key, on in (("Ctrl+A", True), ("Ctrl+Shift+A", False))] self.loaded = False + self.status = QLabel() + search = QHBoxLayout() + search.addWidget(self.query) + search.addWidget(self.status) layout = QVBoxLayout(self) - layout.addWidget(self.query) + layout.addLayout(search) layout.addLayout(chips) layout.addWidget(self.table, 1) self.refilter() diff --git a/sb_builder/packages.py b/sb_builder/packages.py index a4d1f1b..ab6f3ce 100644 --- a/sb_builder/packages.py +++ b/sb_builder/packages.py @@ -64,12 +64,19 @@ def parse_query(text): return terms -def matches(row, terms, overlays=(), statuses=()): +def orphaned(row): + # SBo marks them MAINTAINER="orphaned - no maintainer", EMAIL="NOEMAIL" + return (row["maintainer"] or "").startswith("orphaned") + + +def matches(row, terms, overlays=(), statuses=(), orphans=False): """All terms match, and the row is in a toggled chip of each non-empty group.""" if overlays and overlay(row) not in overlays: return False if statuses and row["status"] not in statuses: return False + if orphans and not orphaned(row): + return False for field, term in terms: if field is None: hay = f"{row['name']} {row['summary'] or ''}" @@ -148,15 +155,16 @@ class Model(QAbstractTableModel): class Filter(QSortFilterProxyModel): def __init__(self): super().__init__() - self.terms, self.overlays, self.statuses = [], set(), set() + self.terms, self.overlays, self.statuses, self.orphans = [], set(), set(), False - def update(self, text, overlays, statuses): + def update(self, text, overlays, statuses, orphans=False): self.terms, self.overlays, self.statuses = parse_query(text), overlays, statuses + self.orphans = orphans self.invalidateFilter() def filterAcceptsRow(self, source_row, _parent): row = self.sourceModel().rows[source_row] - return matches(row, self.terms, self.overlays, self.statuses) + return matches(row, self.terms, self.overlays, self.statuses, self.orphans) def detail_html(row): @@ -214,16 +222,15 @@ class PackagesTab(QWidget): self.query.textChanged.connect(self.refilter) self.chips = {} chips = QHBoxLayout() - for label, group in (("overlay", ("personal", "pentesting", "upstream")), ("status", STATUSES)): + for label, group in (("overlay", ("personal", "pentesting", "upstream")), ("status", STATUSES), + ("maint", ("orphaned",))): # the query prefix's name chips.addWidget(group_label(label)) for name in group: self.chips[name] = chip(name, STATUS_COLOR.get(name) if group is STATUSES else None) self.chips[name].toggled.connect(self.refilter) chips.addWidget(self.chips[name]) - chips.addSpacing(16) + chips.addSpacing(12) chips.addStretch() - self.count = QLabel() - chips.addWidget(self.count) self.table = QTableView(sortingEnabled=True) self.table.setModel(self.proxy) @@ -279,8 +286,12 @@ class PackagesTab(QWidget): for key, slot in (("Ctrl+A", self.select_visible), ("Ctrl+Shift+A", self.clear))] self.loaded = False + self.count = QLabel() + search = QHBoxLayout() + search.addWidget(self.query) + search.addWidget(self.count) layout = QVBoxLayout(self) - layout.addWidget(self.query) + layout.addLayout(search) layout.addLayout(chips) layout.addWidget(split, 1) self.model.dataChanged.connect(lambda *_: self.update_count()) @@ -300,7 +311,8 @@ class PackagesTab(QWidget): def refilter(self): on = {n for n, b in self.chips.items() if b.isChecked()} - self.proxy.update(self.query.text(), on - set(STATUSES), on & set(STATUSES)) + self.proxy.update(self.query.text(), on - set(STATUSES) - {"orphaned"}, on & set(STATUSES), + "orphaned" in on) self.update_count() def update_count(self): diff --git a/sb_builder/ui.py b/sb_builder/ui.py index 82ddc6d..3f02515 100644 --- a/sb_builder/ui.py +++ b/sb_builder/ui.py @@ -50,7 +50,7 @@ SP = QStyle.StandardPixmap # ── filter bars: search field, group labels, chips ────────────────────────── CHIP_STYLE = ("QPushButton { background: palette(button); border: 1px solid palette(mid);" - " border-radius: 11px; padding: 3px 12px; }" + " border-radius: 11px; padding: 3px 10px; }" "QPushButton:hover { border-color: palette(highlight); }" "QPushButton:checked { background: palette(highlight);" " color: palette(highlighted-text); border-color: palette(highlight); }") diff --git a/tests/test_gui.py b/tests/test_gui.py index 9d3e565..d5a1128 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -151,7 +151,7 @@ INVENTORY = {"columns": COLS, "rows": [ requires=["go"]), inv_row("personal/foo", built="1.0", result="ok", hints={"DELREQUIRES": "x"}, requires=["x", "y"]), inv_row("pentesting/nmap", built="7.9", result="bad"), - inv_row("network/curl"), + inv_row("network/curl", maintainer="orphaned - no maintainer"), ]} RESULTS = {"rows": [["games/pcsx2", "failed", 1789992366, None, True], # newest first, as the helper ["development/whisper.cpp", "aborted", 1789753147, None, False], @@ -260,6 +260,8 @@ class FilterTest(unittest.TestCase): self.assertEqual(self.names(overlays={"personal", "pentesting"}), ["foo", "nmap"]) self.assertEqual(self.names(overlays={"upstream"}, statuses={"never", "failed"}), ["curl"]) self.assertEqual(self.names("cat:misc", statuses={"ok"}), []) + terms = pk.parse_query("") + self.assertEqual([n for n, r in self.rows.items() if pk.matches(r, terms, orphans=True)], ["curl"]) class MeterTest(unittest.TestCase): @@ -376,6 +378,9 @@ class SmokeTest(unittest.TestCase): tab.chips["failed"].setChecked(True) self.assertEqual(tab.proxy.rowCount(), 1) tab.chips["failed"].setChecked(False) + tab.chips["orphaned"].setChecked(True) + self.assertEqual(tab.proxy.rowCount(), 1) # curl + tab.chips["orphaned"].setChecked(False) started = [] tab.start_requested.connect(started.append) tab.act("update") -- cgit v1.2.3