feat: added a twist
This commit is contained in:
243
backend/uframe/ir.py
Normal file
243
backend/uframe/ir.py
Normal file
@@ -0,0 +1,243 @@
|
||||
"""µ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 (Phase 4)."""
|
||||
title: str = ""
|
||||
columns: list[tuple[str, int]] = field(default_factory=list) # (name, width)
|
||||
rows: list[list[str]] = field(default_factory=list)
|
||||
Reference in New Issue
Block a user