#!/usr/bin/env python3 """BiDi-safe box-drawing table renderer for mixed Hebrew/English terminal output. Uses Unicode directional marks to prevent the BiDi algorithm from breaking table alignment when Hebrew text is present. Usage as module: from scripts.bidi_table import bidi_table print(bidi_table(['Col1', 'Col2'], [['val1', 'ערך2']])) Usage from CLI: python3 scripts/bidi_table.py """ from __future__ import annotations import re LRM = "\u200E" # Left-to-Right Mark RLM = "\u200F" # Right-to-Left Mark LRE = "\u202A" # Left-to-Right Embedding PDF = "\u202C" # Pop Directional Formatting _HEB_RE = re.compile(r'[\u0590-\u05FF]') def _has_hebrew(text: str) -> bool: return bool(_HEB_RE.search(text)) def bidi_table(headers: list[str], rows: list[list[str]]) -> str: """Render a box-drawing table safe for mixed RTL/LTR terminal display.""" ncols = len(headers) # Calculate column widths (visual length, not counting bidi marks) col_widths = [len(h) for h in headers] for row in rows: for i, cell in enumerate(row[:ncols]): col_widths[i] = max(col_widths[i], len(cell)) def hline(left: str, mid: str, right: str) -> str: return left + mid.join("─" * (w + 2) for w in col_widths) + right def dataline(cells: list[str]) -> str: parts = [] for i in range(ncols): cell = cells[i] if i < len(cells) else "" padded = cell + " " * max(0, col_widths[i] - len(cell)) # Wrap each cell: LRE forces left-to-right context for the cell, # so box-drawing chars stay in place. PDF closes the embedding. parts.append(LRE + " " + padded + " " + PDF) return LRM + "│" + ("│").join(parts) + "│" lines = [hline("┌", "┬", "┐")] lines.append(dataline(headers)) lines.append(hline("├", "┼", "┤")) for row in rows: lines.append(dataline(row)) lines.append(hline("└", "┴", "┘")) return "\n".join(lines) if __name__ == "__main__": table = bidi_table( ["File", "Description", "Model", "Step"], [ ["claims_extractor.py", "חילוץ טענות מכתבי טענות", "Sonnet", "שלב 3 — הבא בתור"], ["brainstorm.py", "סיעור מוחות — כיווני נימוק", "Sonnet", "שלב 4"], ["block_writer.py", "כתיבת בלוקים של החלטה", "Sonnet/Opus", "שלב 5"], ["qa_validator.py", "בדיקת איכות QA", "Sonnet", "שלב 6"], ["style_analyzer.py", "ניתוח סגנון דפנה", "Opus", "חד-פעמי"], ["learning_loop.py", "למידה מהחלטה סופית", "Sonnet", "סוף תהליך"], ], ) print(table)