"""Central Rich output helpers. Opinionated: `rich` is a required dependency. This module centralizes Console instances so tables/panels render consistently and so callers can choose stdout vs stderr explicitly (important for pipeline-safe output). """ from __future__ import annotations import contextlib import sys 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. try: from rich.pretty import install as _pretty_install try: _pretty_install(max_string=100_000, max_length=100_000) except TypeError: _pretty_install() except Exception: pass _STDOUT_CONSOLE = Console(file=sys.stdout) _STDERR_CONSOLE = Console(file=sys.stderr) def stdout_console() -> Console: return _STDOUT_CONSOLE def stderr_console() -> Console: return _STDERR_CONSOLE def console_for(file: TextIO | None) -> Console: if file is None or file is sys.stdout: return _STDOUT_CONSOLE if file is sys.stderr: return _STDERR_CONSOLE return Console(file=file) def rprint(renderable: Any = "", *, file: TextIO | None = None) -> None: console_for(file).print(renderable) @contextlib.contextmanager def capture_rich_output(*, stdout: TextIO, stderr: TextIO) -> Iterator[None]: """Temporarily redirect Rich output helpers to provided streams. Note: `stdout_console()` / `stderr_console()` use global Console instances, so `contextlib.redirect_stdout` alone will not capture Rich output. """ global _STDOUT_CONSOLE, _STDERR_CONSOLE previous_stdout = _STDOUT_CONSOLE previous_stderr = _STDERR_CONSOLE try: _STDOUT_CONSOLE = Console(file=stdout) _STDERR_CONSOLE = Console(file=stderr) yield 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)