#!/usr/bin/env python3 """Text rendering helpers for the intrusion-detection system.""" from __future__ import annotations from datetime import datetime from typing import Any, Callable, Sequence #: (header, getter) pair used for CSV + console tables Column = tuple[str, Callable[[Any], Any]] def columns_header(columns: Sequence[Column]) -> list[str]: return [c[0] for c in columns] def columns_row(columns: Sequence[Column], obj: Any) -> list[Any]: return [fn(obj) for _, fn in columns] def render_table(headers: Sequence[str], rows: Sequence[Sequence[Any]], max_width: int = 48) -> str: """Aligned fixed-width text table. Numeric columns right-aligned.""" def cell(v: Any) -> str: s = "" if v is None else str(v) return s if len(s) <= max_width else s[:max_width - 1] + "~" body = [[cell(v) for v in row] for row in rows] aligns = [] for i in range(len(headers)): numeric = bool(rows) and all( isinstance(row[i], (int, float)) and not isinstance(row[i], bool) for row in rows) aligns.append(">" if numeric else "<") widths = [len(h) for h in headers] for row in body: for i, v in enumerate(row): widths[i] = max(widths[i], len(v)) def line(values: Sequence[str]) -> str: return " ".join( f"{v:{a}{w}}" for v, a, w in zip(values, aligns, widths)).rstrip() out = [line(list(headers)), "-" * (sum(widths) + 2 * (len(widths) - 1))] out += [line(row) for row in body] return "\n".join(out) def render_kv(rows: Sequence[tuple[str, Any]]) -> str: width = max((len(k) for k, _ in rows), default=0) + 1 return "\n".join(f"{k + ':':<{width + 1}} {v}" for k, v in rows) def timestamp_name() -> str: return datetime.now().strftime("%Y-%m-%d-%H-%M-%S")