Source code for imednet.cli.utils.output

"""CLI output formatting and display utilities."""

from __future__ import annotations

import os
import sys
from collections.abc import Sequence
from datetime import datetime
from typing import Any

_ANSI_COLORS = {
    "green": "\033[32m",
    "yellow": "\033[33m",
    "red": "\033[31m",
    "blue": "\033[34m",
    "magenta": "\033[35m",
    "dim": "\033[2m",
    "bold_magenta": "\033[1;35m",
    "reset": "\033[0m",
}

_STATUS_COLOR_MAP = {
    "active": "green",
    "success": "green",
    "ok": "green",
    "completed": "green",
    "open": "green",
    "approved": "green",
    "verified": "green",
    "pending": "yellow",
    "processing": "yellow",
    "suspended": "yellow",
    "hold": "yellow",
    "incomplete": "yellow",
    "initiated": "yellow",
    "inactive": "red",
    "closed": "red",
    "error": "red",
    "fail": "red",
    "failed": "red",
    "rejected": "red",
    "terminated": "red",
    "withdrawn": "red",
}


class _DummyConsole:
    def status(self, msg: str, spinner: str | None = None) -> Any:
        class DummyStatus:
            def __enter__(self) -> None:
                print(msg)

            def __exit__(self, *args: Any) -> None:
                pass

        return DummyStatus()


console = _DummyConsole()


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


def _format_cell_value(value: Any, key: str | None = None) -> str:
    if value is None:
        return "-"
    if isinstance(value, bool):
        return "Yes" if value else "No"
    if isinstance(value, datetime):
        return value.strftime("%Y-%m-%d %H:%M")

    str_val = str(value)
    if isinstance(value, list) and all(isinstance(x, (str, int, float, bool)) for x in value):
        if not value:
            return "-"
        s = ", ".join(str(x) for x in value)
        return _truncate(s)
    if isinstance(value, (list, dict)):
        return _truncate(str(value))
    return str_val


def _colorize(text: str, color: str) -> str:
    if not sys.stdout.isatty():
        return text
    return f"{_ANSI_COLORS.get(color, '')}{text}{_ANSI_COLORS['reset']}"


def _print_table(items: Sequence[Any], fields: list[str]) -> None:
    if not items:
        return

    headers = [str(header).replace("_", " ").title() for header in fields]

    rows = []
    for item in items:
        row = []
        for k in fields:
            if isinstance(item, dict):  # noqa: SIM108
                val = item.get(k)
            else:
                val = getattr(item, k, None)
            row.append(_format_cell_value(val, key=str(k)))
        rows.append(row)

    col_widths = [len(h) for h in headers]
    for row in rows:
        for i, cell in enumerate(row):
            col_widths[i] = max(col_widths[i], len(cell))

    header_str = " | ".join(h.ljust(w) for h, w in zip(headers, col_widths))  # noqa: B905
    print(_colorize(header_str, "bold_magenta"))
    print("-" * len(header_str))

    is_hc = os.environ.get("IMEDNET_HIGH_CONTRAST") == "1"

    for row in rows:
        colored_row = []
        for i, cell in enumerate(row):
            k = fields[i]
            c = cell
            if any(key in k.lower() for key in ("status", "state")):
                lower_val = cell.lower()
                if is_hc:
                    hc_map = {
                        "active": "blue",
                        "success": "blue",
                        "ok": "blue",
                        "completed": "blue",
                        "open": "blue",
                        "approved": "blue",
                        "verified": "blue",
                        "pending": "yellow",
                        "processing": "yellow",
                        "suspended": "yellow",
                        "hold": "yellow",
                        "incomplete": "yellow",
                        "initiated": "yellow",
                        "inactive": "magenta",
                        "closed": "magenta",
                        "error": "magenta",
                        "fail": "magenta",
                        "failed": "magenta",
                        "rejected": "magenta",
                        "terminated": "magenta",
                        "withdrawn": "magenta",
                    }
                    color = hc_map.get(lower_val)
                else:
                    color = _STATUS_COLOR_MAP.get(lower_val)
                if color:
                    c = _colorize(cell, color)
            colored_row.append(c + " " * (col_widths[i] - len(cell)))
        print(" | ".join(colored_row))


[docs]def display_list( items: Sequence[Any], label: str, empty_msg: str | None = None, fields: list[str] | None = None ) -> None: """Display a list of items.""" if not items: print(empty_msg or f"No {label} found.") return print(f"Found {len(items)} {label}:") if fields: _print_table(items, fields) return 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) if not data_list: print(items) return all_keys = list(data_list[0].keys()) _print_table(data_list, all_keys)