This commit is contained in:
2026-01-01 20:37:27 -08:00
parent f3c79609d8
commit deb05c0d44
35 changed files with 5030 additions and 4879 deletions

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
from importlib import import_module
from types import ModuleType
from typing import Any, Dict, List, Optional
try:
@@ -21,22 +22,36 @@ def _should_hide_db_args(config: Optional[Dict[str, Any]]) -> bool:
return False
try:
from cmdlet import REGISTRY
except Exception:
REGISTRY = {} # type: ignore
_cmdlet_pkg: ModuleType | None = None
try:
from cmdnat import register_native_commands as _register_native_commands
except Exception:
_register_native_commands = None
def _get_cmdlet_package() -> Optional[ModuleType]:
global _cmdlet_pkg
if _cmdlet_pkg is not None:
return _cmdlet_pkg
try:
_cmdlet_pkg = import_module("cmdlet")
except Exception:
_cmdlet_pkg = None
return _cmdlet_pkg
def _get_registry() -> Dict[str, Any]:
pkg = _get_cmdlet_package()
if pkg is None:
return {}
return getattr(pkg, "REGISTRY", {}) or {}
def ensure_registry_loaded() -> None:
"""Ensure native commands are registered into REGISTRY (idempotent)."""
if _register_native_commands and REGISTRY is not None:
pkg = _get_cmdlet_package()
if pkg is None:
return
ensure_fn = getattr(pkg, "ensure_cmdlet_modules_loaded", None)
if callable(ensure_fn):
try:
_register_native_commands(REGISTRY)
ensure_fn()
except Exception:
pass
@@ -105,7 +120,8 @@ def get_cmdlet_metadata(
if data is None:
try:
reg_fn = (REGISTRY or {}).get(cmd_name.replace("_", "-").lower())
registry = _get_registry()
reg_fn = registry.get(cmd_name.replace("_", "-").lower())
if reg_fn:
owner_mod = getattr(reg_fn, "__module__", "")
if owner_mod:
@@ -150,7 +166,8 @@ def list_cmdlet_metadata(config: Optional[Dict[str, Any]] = None) -> Dict[str, D
"""Collect metadata for all registered cmdlet keyed by canonical name."""
ensure_registry_loaded()
entries: Dict[str, Dict[str, Any]] = {}
for reg_name in (REGISTRY or {}).keys():
registry = _get_registry()
for reg_name in registry.keys():
meta = get_cmdlet_metadata(reg_name, config=config)
canonical = str(reg_name).replace("_", "-").lower()

View File

@@ -103,7 +103,7 @@ class PipeObject:
return
# Prefer a stable, human-friendly title:
# "1 - download-media", "2 - download-media", ...
# "1 - download-file", "2 - download-file", ...
# The index is preserved when possible via `pipe_index` in the PipeObject's extra.
idx = None
try:
@@ -875,7 +875,7 @@ class PipelineLiveProgress:
# IMPORTANT: use the shared stderr Console instance so that any
# `stderr_console().print(...)` calls from inside cmdlets (e.g. preflight
# tables/prompts in download-media) cooperate with Rich Live rendering.
# tables/prompts in download-file) cooperate with Rich Live rendering.
# If we create a separate Console(file=sys.stderr), output will fight for
# terminal cursor control and appear "blocked"/truncated.
from SYS.rich_display import stderr_console

View File

@@ -361,6 +361,8 @@ class ResultRow:
"""Arguments to use for this row when selected via @N syntax (e.g., ['-item', '3'])"""
source_index: Optional[int] = None
"""Original insertion order index (used to map sorted views back to source items)."""
payload: Optional[Any] = None
"""Original object that contributed to this row."""
def add_column(self, name: str, value: Any) -> None:
"""Add a column to this row."""
@@ -498,6 +500,9 @@ class ResultTable:
self.table: Optional[str] = None
"""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)."""
self.value_case: str = "lower"
"""Display-only value casing: 'lower' (default), 'upper', or 'preserve'."""
@@ -525,6 +530,18 @@ class ResultTable:
self.table = table
return self
def set_table_metadata(self, metadata: Optional[Dict[str, Any]]) -> "ResultTable":
"""Attach provider/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)."""
try:
return dict(self.table_metadata)
except Exception:
return {}
def set_no_choice(self, no_choice: bool = True) -> "ResultTable":
"""Mark the table as non-interactive (no row numbers, no selection parsing)."""
self.no_choice = bool(no_choice)
@@ -612,6 +629,9 @@ class ResultTable:
new_table.input_options = dict(self.input_options) if self.input_options else {}
new_table.no_choice = self.no_choice
new_table.table = self.table
new_table.table_metadata = (
dict(self.table_metadata) if getattr(self, "table_metadata", None) else {}
)
new_table.header_lines = list(self.header_lines) if self.header_lines else []
return new_table
@@ -712,6 +732,7 @@ class ResultTable:
Self for chaining
"""
row = self.add_row()
row.payload = result
# Handle TagItem from get_tag.py (tag display with index)
if hasattr(result, "__class__") and result.__class__.__name__ == "TagItem":
@@ -738,6 +759,21 @@ class ResultTable:
return self
def get_row_payload(self, row_index: int) -> Optional[Any]:
"""Return the original payload for the row at ``row_index`` if available."""
if 0 <= row_index < len(self.rows):
return getattr(self.rows[row_index], "payload", None)
return None
def get_payloads(self) -> List[Any]:
"""Return the payloads for every row, preserving table order."""
payloads: List[Any] = []
for row in self.rows:
payload = getattr(row, "payload", None)
if payload is not None:
payloads.append(payload)
return payloads
def _add_search_result(self, row: ResultRow, result: Any) -> None:
"""Extract and add SearchResult fields to row."""
# If provider supplied explicit columns, render those and skip legacy defaults

View File

@@ -11,9 +11,11 @@ from __future__ import annotations
import contextlib
import sys
from typing import Any, Iterator, TextIO
from typing import Any, Iterator, Sequence, TextIO
from rich.console import Console
from rich.panel import Panel
from rich.text import Text
# Configure Rich pretty-printing to avoid truncating long strings (hashes/paths).
# This is version-safe: older Rich versions may not support the max_* arguments.
@@ -70,3 +72,33 @@ def capture_rich_output(*, stdout: TextIO, stderr: TextIO) -> Iterator[None]:
finally:
_STDOUT_CONSOLE = previous_stdout
_STDERR_CONSOLE = previous_stderr
def show_provider_config_panel(
provider_name: str,
keys: Sequence[str] | None = None,
*,
config_hint: str = "config.conf"
) -> None:
"""Show a Rich panel explaining how to configure a provider."""
normalized = str(provider_name or "").strip() or "provider"
pre = Text("Add this to your config", style="bold")
footer = Text(
f"Place this block in {config_hint} or config.d/*.conf",
style="dim"
)
body = Text()
body.append(f"[provider={normalized}]\n", style="bold cyan")
for key in keys or []:
body.append(f'{key}=""\n', style="yellow")
stderr_console().print(pre)
stderr_console().print(
Panel(
body,
title=f"{normalized} configuration",
expand=False
)
)
stderr_console().print(footer)