136 lines
3.6 KiB
Python
136 lines
3.6 KiB
Python
"""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)
|
|
|
|
|
|
def show_store_config_panel(
|
|
store_type: str,
|
|
keys: Sequence[str] | None = None,
|
|
*,
|
|
config_hint: str = "config.conf"
|
|
) -> None:
|
|
"""Show a Rich panel explaining how to configure a storage backend."""
|
|
|
|
normalized = str(store_type or "").strip().lower() or "store"
|
|
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"[store={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} storage configuration",
|
|
expand=False
|
|
)
|
|
)
|
|
stderr_console().print(footer)
|
|
|