Source code for imednet.cli.utils.output

from __future__ import annotations

from datetime import datetime
from typing import Any, Dict, List, Sequence

from rich import print
from rich.console import Console
from rich.markup import escape
from rich.table import Table

# Map status strings to Rich colors for O(1) lookup
_STATUS_COLOR_MAP = {
    # Green (Success/Active)
    "active": "green",
    "success": "green",
    "ok": "green",
    "completed": "green",
    "open": "green",
    "approved": "green",
    "verified": "green",
    # Yellow (Pending/Processing)
    "pending": "yellow",
    "processing": "yellow",
    "suspended": "yellow",
    "hold": "yellow",
    "incomplete": "yellow",
    "initiated": "yellow",
    # Red (Failure/Inactive)
    "inactive": "red",
    "closed": "red",
    "error": "red",
    "fail": "red",
    "failed": "red",
    "rejected": "red",
    "terminated": "red",
    "withdrawn": "red",
}

console = Console()


def _truncate(text: str, length: int = 60) -> str:
    """Truncate text to a maximum length with ellipsis."""
    return f"{text[:length]}..." if len(text) > length else text


def _format_cell_value(value: Any, key: str | None = None) -> str:
    """Format a single cell value for display."""
    if value is None:
        return "[dim]-[/dim]"
    if isinstance(value, bool):
        return "[green]Yes[/green]" if value else "[dim]No[/dim]"
    if isinstance(value, datetime):
        return value.strftime("%Y-%m-%d %H:%M")

    str_val = str(value)

    # Colorize status columns
    if key and any(k in key.lower() for k in ("status", "state")):
        lower_val = str_val.lower()
        if color := _STATUS_COLOR_MAP.get(lower_val):
            return f"[{color}]{escape(str_val)}[/{color}]"

    if isinstance(value, list) and all(isinstance(x, (str, int, float, bool)) for x in value):
        if not value:
            return "[dim]-[/dim]"
        s = ", ".join(str(x) for x in value)
        return escape(_truncate(s))
    if isinstance(value, (list, dict)):
        # Truncate very long list/dict representations
        return escape(_truncate(str(value)))
    return escape(str_val)


def _create_table(items: Sequence[Any], fields: List[str]) -> Table:
    """Create a Rich table from a list of items and specified fields."""
    table = Table(show_header=True, header_style="bold magenta")
    for header in fields:
        table.add_column(str(header).replace("_", " ").title())

    for item in items:
        row = []
        for k in fields:
            if isinstance(item, dict):
                val = item.get(k)
            else:
                # Use getattr for Pydantic models or objects
                val = getattr(item, k, None)
            row.append(_format_cell_value(val, key=str(k)))
        table.add_row(*row)
    return table


[docs]def display_list( items: Sequence[Any], label: str, empty_msg: str | None = None, fields: List[str] | None = None, ) -> None: """Print list output with a standardized format.""" if not items: print(empty_msg or f"No {label} found.") return print(f"Found {len(items)} {label}:") # Bolt Optimization: If specific fields are requested, extract them directly # from the objects instead of converting everything to a dictionary. # This avoids O(N*M) overhead for large lists where M is total field count. if fields: table = _create_table(items, fields) print(table) return # Try to determine if we can display this as a table first = items[0] data_list: List[Dict[str, Any]] = [] if hasattr(first, "model_dump"): data_list = [item.model_dump() for item in items] elif hasattr(first, "dict"): data_list = [item.dict() for item in items] elif isinstance(first, dict): data_list = list(items) # type: ignore if not data_list: print(items) return all_keys = list(data_list[0].keys()) table = _create_table(data_list, all_keys) print(table)