Source code for prescription.renderers.plain_text

"""PlainTextRenderer — pure-Python plain text output.

Kramer Harrison, 2026
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from optiland.prescription.renderers.base import BaseRenderer

if TYPE_CHECKING:
    import os

    from optiland.prescription.document import (
        Block,
        Document,
        KeyValueBlock,
        Section,
        TableBlock,
    )

_PAGE_WIDTH = 70
_SECTION_BAR = "─" * (_PAGE_WIDTH - 2)


def _col_widths(headers: list[str], rows: list[list[str]]) -> list[int]:
    widths = [len(h) for h in headers]
    for row in rows:
        for i, cell in enumerate(row):
            if i < len(widths):
                widths[i] = max(widths[i], len(cell))
    return widths


def _render_table(headers: list[str], rows: list[list[str]]) -> list[str]:
    widths = _col_widths(headers, rows)
    gap = 3

    def _row_str(cells: list[str]) -> str:
        parts = [cell.ljust(w) for cell, w in zip(cells, widths, strict=False)]
        return "  " + (" " * gap).join(parts)

    def _sep_str() -> str:
        parts = ["─" * w for w in widths]
        return "  " + (" " * gap).join(parts)

    lines = [_row_str(headers), _sep_str()]
    for row in rows:
        lines.append(_row_str(row))
    return lines


[docs] class PlainTextRenderer(BaseRenderer): """Renders a Document to plain text with Unicode box-drawing characters."""
[docs] def render(self, document: Document) -> str: horiz = "═" * _PAGE_WIDTH lines: list[str] = [] lines.append(horiz) lines.append(f" {document.title.upper()}") lines.append(f" Generated: {document.generated_at}") lines.append(horiz) for section in document.sections: lines.append("") lines.extend(self._render_section(section)) lines.append("") return "\n".join(lines)
[docs] def write(self, document: Document, path: str | os.PathLike) -> None: content = self.render(document) with open(path, "w", encoding="utf-8") as fh: fh.write(content)
def _render_section(self, section: Section) -> list[str]: lines: list[str] = [] fill = max(0, _PAGE_WIDTH - len(section.title) - 5) title_bar = f"┌─ {section.title} " + "─" * fill + "┐" lines.append(title_bar) for block in section.blocks: lines.extend(self._render_block(block)) lines.append("└" + "─" * (_PAGE_WIDTH - 2) + "┘") return lines def _render_block(self, block: Block) -> list[str]: from optiland.prescription.document import ( KeyValueBlock, SeparatorBlock, TableBlock, TextBlock, ) if isinstance(block, KeyValueBlock): return self._render_kv(block) if isinstance(block, TableBlock): return self._render_table_block(block) if isinstance(block, TextBlock): return [f" {block.text}", ""] if isinstance(block, SeparatorBlock): return [" " + _SECTION_BAR] return [] @staticmethod def _render_kv(block: KeyValueBlock) -> list[str]: if not block.rows: return [] key_w = max(len(k) for k, _ in block.rows) lines = [] if block.title: lines.append(f" {block.title}") for key, val in block.rows: lines.append(f" {key.ljust(key_w)} {val}") lines.append("") return lines @staticmethod def _render_table_block(block: TableBlock) -> list[str]: lines = [] if block.title: lines.append(f" {block.title}") for line in _render_table(block.headers, block.rows): lines.append(line) lines.append("") return lines