56 lines
1.8 KiB
Python
56 lines
1.8 KiB
Python
#!/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")
|