Ferramentas/AjustaFundos/actions/ui/ui.py

203 lines
5.6 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ui.py
from contextlib import asynccontextmanager
from contextlib import contextmanager
from typing import Iterable, Mapping
from rich.live import Live
from rich.console import Console
from rich.theme import Theme
from rich.panel import Panel
from rich.table import Table
from rich.text import Text
from rich.progress import (
Progress,
SpinnerColumn,
BarColumn,
TextColumn,
TimeElapsedColumn,
TimeRemainingColumn,
)
from rich.markdown import Markdown
from rich.traceback import install
from rich.logging import RichHandler
import logging
import json
# ─────────────────────────────────────────────────────────────
# Tema global (cores nomeadas para facilitar padronização)
# ─────────────────────────────────────────────────────────────
theme = Theme(
{
"accent": "bright_magenta",
"info": "cyan",
"success": "green",
"warning": "yellow",
"error": "bold red",
"muted": "grey62",
}
)
# Console único para todo o sistema
console = Console(theme=theme)
install(show_locals=False) # Tracebacks bonitos
# ─────────────────────────
# BÁSICOS
# ─────────────────────────
def banner(titulo: str, subtitulo: str | None = None):
text = Text(titulo, style="bold accent")
if subtitulo:
text.append(f"\n{subtitulo}", style="muted")
console.print(Panel.fit(text, border_style="accent", padding=(1, 2)))
def rule(texto: str = ""):
console.rule(Text(texto, style="accent"))
def info(msg: str):
console.print(f" [info]{msg}[/]")
def ok(msg: str):
console.print(f"✅ [success]{msg}[/]")
def warn(msg: str):
console.print(f"⚠️ [warning]{msg}[/]")
def fail(msg: str):
console.print(f"❌ [error]{msg}[/]")
def md(markdown: str):
console.print(Markdown(markdown))
def json_pretty(data):
try:
console.print_json(data=json.loads(data) if isinstance(data, str) else data)
except Exception:
console.print(data)
# ─────────────────────────
# TABELA RÁPIDA
# ─────────────────────────
def table(rows: Iterable[Mapping], title: str | None = None):
rows = list(rows)
if not rows:
warn("Tabela vazia.")
return
t = Table(title=title, title_style="accent")
for col in rows[0].keys():
t.add_column(str(col), style="muted")
for r in rows:
t.add_row(*[str(r[k]) for k in rows[0].keys()])
console.print(t)
# ─────────────────────────
# STATUS / SPINNER
# ─────────────────────────
@contextmanager
def status(msg: str):
with console.status(f"[info]{msg}[/]"):
yield
# ─────────────────────────
# BARRA DE PROGRESSO
# ─────────────────────────
@contextmanager
def progress(task_desc: str, total: int | None = None):
with Progress(
SpinnerColumn(),
TextColumn("[accent]{task.description}"),
BarColumn(),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
TimeElapsedColumn(),
TimeRemainingColumn(),
transient=True,
console=console,
) as p:
task_id = p.add_task(task_desc, total=total)
yield lambda advance=1: p.update(task_id, advance=advance)
# ─────────────────────────
# LOGGING RICH
# ─────────────────────────
def setup_logging(level: int = logging.INFO):
logging.basicConfig(
level=level,
format="%(message)s",
handlers=[RichHandler(rich_tracebacks=True, console=console)],
)
# ─────────────────────────
# FIREBIRD / DB HELPERS
# ─────────────────────────
def db_info(msg: str):
console.print(f"🗄️ [info][DB INFO][/info] {msg}")
def db_ok(msg: str):
console.print(f"🗄️ [success][DB OK][/success] {msg}")
def db_fail(msg: str):
console.print(f"🗄️ [error][DB ERROR][/error] {msg}")
def db_warning(msg: str):
console.print(f"🗄️ [warning][DB WARNING][/warning] {msg}")
def db_dsn(dsn: str):
# Mascara senha com segurança
try:
before_at = dsn.split("@")[0]
user = before_at.split("//")[1].split(":")[0]
masked = dsn.replace(user, "******")
except Exception:
masked = dsn # fallback
console.print(
Panel(
Text(f"[accent]DSN de conexão[/accent]\n{masked}", style="muted"),
border_style="accent",
)
)
@asynccontextmanager
async def progress_manager():
# Criar progress com o console compartilhado
progress = Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TextColumn("{task.completed}/{task.total}"),
TimeElapsedColumn(),
expand=True,
console=console, # fundamental!
)
# Live controla a tela inteira
live = Live(progress, refresh_per_second=10, console=console, transient=False)
live.start()
try:
yield progress
finally:
live.stop()