"""µFrame Intermediate Representation — node types for the IR tree. Every .uf source parses into a tree of IRNode subclasses. The layout engine measures, positions, and paints these nodes into a CharGrid. """ from __future__ import annotations from dataclasses import dataclass, field from enum import Enum, auto from typing import Any # --------------------------------------------------------------------------- # Enums # --------------------------------------------------------------------------- class BorderWeight(Enum): LIGHT = auto() HEAVY = auto() DOUBLE = auto() ROUNDED = auto() class HeadingLevel(Enum): H1 = 1 H2 = 2 H3 = 3 class DividerStyle(Enum): LIGHT = auto() HEAVY = auto() DOUBLE = auto() DASH = auto() DOT = auto() class ListStyle(Enum): BULLET = auto() DASH = auto() NUMBER = auto() ARROW = auto() class Align(Enum): LEFT = auto() CENTER = auto() RIGHT = auto() # --------------------------------------------------------------------------- # Style # --------------------------------------------------------------------------- @dataclass class Style: """Visual style attached to any node.""" fg: str | None = None # 3-digit hex bg: str | None = None # 3-digit hex bold: bool = False italic: bool = False underline: bool = False align: Align = Align.LEFT # --------------------------------------------------------------------------- # Inline text spans (parsed from @modifier{} syntax in text content) # --------------------------------------------------------------------------- @dataclass class TextSpan: """A run of text with optional inline styling.""" text: str bold: bool = False italic: bool = False underline: bool = False fg: str | None = None bg: str | None = None # --------------------------------------------------------------------------- # Layout rect — assigned by the layout engine # --------------------------------------------------------------------------- @dataclass class Rect: x: int = 0 y: int = 0 w: int = 0 h: int = 0 # --------------------------------------------------------------------------- # Base node # --------------------------------------------------------------------------- @dataclass class IRNode: """Base class for all IR nodes.""" children: list[IRNode] = field(default_factory=list) style: Style = field(default_factory=Style) rect: Rect = field(default_factory=Rect) source_line: int | None = None # Set by measure pass min_width: int = 0 min_height: int = 0 pref_width: int = 0 pref_height: int = 0 # --------------------------------------------------------------------------- # Layout nodes # --------------------------------------------------------------------------- @dataclass class Page(IRNode): """Root container. One per .uf file.""" title: str = "" width: int = 64 @dataclass class Box(IRNode): """Bordered panel with optional title.""" title: str = "" weight: BorderWeight = BorderWeight.LIGHT @dataclass class Row(IRNode): """Horizontal layout — children split available width.""" gap: int = 1 @dataclass class Col(IRNode): """Explicit column in a row. Width in chars or None (auto).""" col_width: int | None = None @dataclass class Spacer(IRNode): """Vertical whitespace.""" lines: int = 1 @dataclass class Pad(IRNode): """Inner margin for a container.""" top: int = 0 right: int = 0 bottom: int = 0 left: int = 0 # --------------------------------------------------------------------------- # Content nodes # --------------------------------------------------------------------------- @dataclass class Heading(IRNode): """Styled heading (levels 1–3).""" level: HeadingLevel = HeadingLevel.H1 text: str = "" @dataclass class Text(IRNode): """Text content with optional @modifier{} inline styles.""" content: str = "" spans: list[TextSpan] = field(default_factory=list) @dataclass class Label(IRNode): """Aligned key-value pair.""" key: str = "" value: str = "" @dataclass class Divider(IRNode): """Full-width horizontal rule.""" divider_style: DividerStyle = DividerStyle.LIGHT @dataclass class Link(IRNode): """Clickable link — visual in ASCII, interactive in Micron.""" display: str = "" dest: str = "" @dataclass class ListNode(IRNode): """Bulleted or numbered list.""" list_style: ListStyle = ListStyle.BULLET @dataclass class ListItem(IRNode): """Single entry in a ListNode.""" content: str = "" # --------------------------------------------------------------------------- # Placeholder nodes for future phases # --------------------------------------------------------------------------- @dataclass class Gauge(IRNode): """Horizontal bar chart (Phase 4).""" label: str = "" value: float = 0 max_val: float = 100 bar_width: int = 28 warn: float | None = None crit: float | None = None @dataclass class Sparkline(IRNode): """Braille sparkline (Phase 4).""" label: str = "" values: list[float] = field(default_factory=list) spark_width: int = 20 @dataclass class Status(IRNode): """Status indicator (Phase 4).""" label: str = "" state: str = "unknown" @dataclass class Table(IRNode): """Box-drawn table.""" title: str = "" columns: list[tuple[str, int]] = field(default_factory=list) # (name, width) rows: list[list[str]] = field(default_factory=list) # --------------------------------------------------------------------------- # Form nodes # --------------------------------------------------------------------------- @dataclass class Form(IRNode): """Form container grouping interactive fields.""" form_name: str = "" @dataclass class Field(IRNode): """Text input field.""" field_name: str = "" field_width: int = 24 placeholder: str = "" @dataclass class Password(IRNode): """Masked password field.""" field_name: str = "" field_width: int = 24 placeholder: str = "" @dataclass class Radio(IRNode): """Radio button group — options separated by |.""" group: str = "" options: list[str] = field(default_factory=list) @dataclass class Checkbox(IRNode): """Checkbox field.""" field_name: str = "" checkbox_label: str = "" checked: bool = False @dataclass class FormButton(IRNode): """Submit button — clickable link in Micron.""" button_label: str = "" dest: str = "" # --------------------------------------------------------------------------- # Dynamic nodes (Phase 7) # --------------------------------------------------------------------------- class SourceType(Enum): SHELL = auto() FILE = auto() JSON = auto() PYTHON = auto() RNS = auto() PARAM = auto() @dataclass class Let(IRNode): """Variable assignment: let name = "value" or let name = 1,2,3.""" var_name: str = "" var_value: str = "" @dataclass class Source(IRNode): """Data source resolved at render time (dynamic pages only).""" var_name: str = "" source_type: SourceType = SourceType.SHELL command: str = "" timeout: int = 5 @dataclass class IfBlock(IRNode): """Conditional block: if $var > threshold.""" condition: str = "" # children = the "then" branch elif_branches: list[tuple[str, list[IRNode]]] = field(default_factory=list) else_children: list[IRNode] = field(default_factory=list) @dataclass class ForLoop(IRNode): """Iteration: for item in $collection.""" var_name: str = "" iterable: str = "" # children = loop body @dataclass class CacheControl(IRNode): """Cache header: cache 0 (never cache) or cache 300 (5 min).""" seconds: int = 0 @dataclass class OnSubmit(IRNode): """Form submission handler: on_submit "form_name".""" form_name: str = "" # children = handler body @dataclass class StateDecl(IRNode): """State persistence: state "name" "/path.json".""" state_name: str = "" path: str = ""