updating and refining plugin system refactor

This commit is contained in:
2026-04-28 22:20:54 -07:00
parent 8685fbb723
commit 323c24f4f4
33 changed files with 4287 additions and 3312 deletions
+59 -12
View File
@@ -724,7 +724,7 @@ class Table:
"""Table type (e.g., 'youtube', 'soulseek') for context-aware selection logic."""
self.table_metadata: Dict[str, Any] = {}
"""Optional provider/table metadata (e.g., provider name, view)."""
"""Optional plugin/table metadata (e.g., plugin name, view)."""
self.value_case: str = "preserve"
"""Display-only value casing: 'lower', 'upper', or 'preserve' (default)."""
@@ -754,12 +754,12 @@ class Table:
return self
def set_table_metadata(self, metadata: Optional[Dict[str, Any]]) -> "Table":
"""Attach provider/table metadata for downstream selection logic."""
"""Attach plugin/table metadata for downstream selection logic."""
self.table_metadata = dict(metadata or {})
return self
def get_table_metadata(self) -> Dict[str, Any]:
"""Return attached provider/table metadata (copy to avoid mutation)."""
"""Return attached plugin/table metadata (copy to avoid mutation)."""
try:
return dict(self.table_metadata)
except Exception:
@@ -2223,6 +2223,34 @@ def extract_item_metadata(item: Any) -> Dict[str, Any]:
return {}
out = {}
def _merge_columns(columns_value: Any) -> None:
if not isinstance(columns_value, (list, tuple)):
return
for column in columns_value:
label = None
value = None
if isinstance(column, (list, tuple)) and len(column) >= 2:
label, value = column[0], column[1]
elif isinstance(column, dict):
label = column.get("name") or column.get("label") or column.get("key")
value = column.get("value")
else:
label = getattr(column, "name", None)
value = getattr(column, "value", None)
label_text = str(label or "").strip()
if not label_text or value is None:
continue
value_text = str(value).strip()
if not value_text:
continue
normalized = label_text.lower()
if any(str(existing or "").strip().lower() == normalized for existing in out):
continue
out[label_text] = value_text
# Handle ResultModel specifically for better detail display
if ResultModel is not None and isinstance(item, ResultModel):
@@ -2231,6 +2259,7 @@ def extract_item_metadata(item: Any) -> Dict[str, Any]:
if item.ext: out["Ext"] = item.ext
if item.size_bytes: out["Size"] = format_mb(item.size_bytes)
if item.source: out["Store"] = item.source
_merge_columns(getattr(item, "columns", None))
# Merge internal metadata dict
if item.metadata:
@@ -2256,6 +2285,7 @@ def extract_item_metadata(item: Any) -> Dict[str, Any]:
# Fallback to existing extraction logic for legacy objects/dicts
# Convert once and reuse throughout to avoid repeated _as_dict() calls
data = _as_dict(item) or {}
_merge_columns(data.get("columns"))
# Use existing extractors from match-standard result table columns
title = extract_title_value(item)
@@ -2350,12 +2380,14 @@ class ItemDetailView(Table):
item_metadata: Optional[Dict[str, Any]] = None,
detail_title: Optional[str] = None,
exclude_tags: bool = False,
detail_order: Optional[List[str]] = None,
**kwargs
):
super().__init__(title, **kwargs)
self.item_metadata = item_metadata or {}
self.detail_title = detail_title
self.exclude_tags = exclude_tags
self.detail_order = [str(value) for value in (detail_order or []) if str(value or "").strip()]
def to_rich(self):
"""Render the item details panel above the standard results table."""
@@ -2406,8 +2438,26 @@ class ItemDetailView(Table):
return Group(*renderables)
# Canonical display order for metadata
order = ["Title", "Hash", "Store", "Path", "Ext", "Size", "Duration", "Url", "Relations"]
def _has_renderable_value(value: Any) -> bool:
if value is None:
return False
if isinstance(value, str):
text = value.strip()
return bool(text and text.lower() not in {"<null>", "null", "none"})
if isinstance(value, (list, tuple, set)):
return any(_has_renderable_value(item) for item in value)
return True
# Canonical display order for metadata; plugin-specific detail views can
# prepend a preferred order without needing to reimplement rendering.
order: List[str] = []
seen_order: set[str] = set()
for key in list(self.detail_order or []) + ["Title", "Hash", "Store", "Path", "Ext", "Size", "Duration", "Url", "Relations"]:
normalized = str(key or "").strip().lower()
if not normalized or normalized in seen_order:
continue
seen_order.add(normalized)
order.append(str(key))
has_details = False
# Add ordered items first
@@ -2431,19 +2481,16 @@ class ItemDetailView(Table):
else:
val = "\n".join([f"[dim]→[/dim] {r}" for r in val])
if val is not None and val != "":
if _has_renderable_value(val):
details_table.add_row(f"{key}:", str(val))
has_details = True
elif key in ["Url", "Relations", "Ext"]:
# Show <null> for these important identifier fields if blank
details_table.add_row(f"{key}:", "[dim]<null>[/dim]")
has_details = True
# Add any remaining metadata not in the canonical list
ordered_keys = {x.lower() for x in order}
for k, v in self.item_metadata.items():
k_norm = k.lower()
if k_norm not in [x.lower() for x in order] and v and k_norm not in ["tags", "tag"]:
label = k.capitalize() if len(k) > 1 else k.upper()
if k_norm not in ordered_keys and _has_renderable_value(v) and k_norm not in ["tags", "tag"]:
label = str(k or "")
details_table.add_row(f"{label}:", str(v))
has_details = True