"""Terminal rendering helpers (CJK-aware column widths).""" import unicodedata def display_width(text): width = 0 for char in str(text): if unicodedata.east_asian_width(char) in ("W", "F"): width += 2 else: width += 1 return width def clip(text, limit=60): text = "" if text is None else str(text) text = text.replace("\n", " ").replace("\r", " ").replace("\t", " ") if len(text) <= limit: return text return text[: max(0, limit - 1)] + "…" def table(headers, rows, align=None): """Render a plain-text table. *align* is a list of ``l``/``r`` per column.""" rows = [["" if cell is None else str(cell) for cell in row] for row in rows] if align is None: align = ["l"] * len(headers) widths = [display_width(header) for header in headers] for row in rows: for index, cell in enumerate(row[: len(widths)]): widths[index] = max(widths[index], display_width(cell)) def render(cells): parts = [] for index, cell in enumerate(cells): padding = widths[index] - display_width(cell) if align[index] == "r": parts.append(" " * padding + cell) else: parts.append(cell + " " * padding) return " ".join(parts).rstrip() lines = [render(headers), " ".join("-" * width for width in widths)] lines.extend(render(row) for row in rows) return "\n".join(lines) def human_bytes(size): if not isinstance(size, (int, float)): return str(size) value = float(size) for unit in ("B", "KiB", "MiB", "GiB", "TiB"): if value < 1024 or unit == "TiB": if unit == "B": return "%d B" % value return "%.1f %s" % (value, unit) value /= 1024.0 return str(size) def percent(part, total): if not total: return "-" return "%.1f%%" % (100.0 * part / total) def kv_block(pairs): """Render aligned ``key: value`` lines.""" if not pairs: return "" width = max(display_width(key) for key, _ in pairs) lines = [] for key, value in pairs: padding = " " * (width - display_width(key)) lines.append("%s%s : %s" % (key, padding, "" if value is None else value)) return "\n".join(lines)