Skip to main content

osp_cli/ui/
document.rs

1//! Structured display blocks used as the boundary between formatting and
2//! terminal rendering.
3//!
4//! This module exists so higher-level code can describe *what* should be shown
5//! without deciding *how* it should be painted in a terminal. Formatters build
6//! documents out of semantic blocks, and the renderer later turns those blocks
7//! into themed terminal text.
8//!
9//! In practice, this keeps rendering bugs easier to localize:
10//!
11//! - if the document shape is wrong, the formatter is wrong
12//! - if the document is right but the terminal output is wrong, the renderer is
13//!   wrong
14//!
15//! Contract:
16//!
17//! - document types may carry semantic styling hints and layout intent
18//! - they should not depend on terminal width probing, theme resolution, or
19//!   config precedence
20//! - block variants are intentionally higher-level than raw ANSI/text spans so
21//!   multiple renderers can share the same model
22
23use serde_json::Value;
24
25use crate::ui::TableBorderStyle;
26use crate::ui::chrome::SectionFrameStyle;
27use crate::ui::style::StyleToken;
28
29/// Renderable document composed of high-level display blocks.
30///
31/// The document model is the handoff point between semantic formatting and
32/// terminal rendering. Callers populate it with blocks; renderers decide how
33/// those blocks map onto plain or rich terminal output.
34///
35/// # Examples
36///
37/// ```
38/// use osp_cli::ui::{Block, Document, LineBlock, LinePart};
39///
40/// let document = Document {
41///     blocks: vec![Block::Line(LineBlock {
42///         parts: vec![LinePart {
43///             text: "hello".to_string(),
44///             token: None,
45///         }],
46///     })],
47/// };
48///
49/// assert_eq!(document.blocks.len(), 1);
50/// ```
51#[derive(Debug, Clone, Default)]
52pub struct Document {
53    /// Ordered blocks to render.
54    pub blocks: Vec<Block>,
55}
56
57/// Top-level document block variants understood by the renderer.
58#[derive(Debug, Clone)]
59pub enum Block {
60    /// A single styled line.
61    Line(LineBlock),
62    /// A framed section containing another document.
63    Panel(PanelBlock),
64    /// A fenced code block.
65    Code(CodeBlock),
66    /// A JSON payload block.
67    Json(JsonBlock),
68    /// A tabular data block.
69    Table(TableBlock),
70    /// A plain list-of-values block.
71    Value(ValueBlock),
72    /// An MREG-style key/value block.
73    Mreg(MregBlock),
74}
75
76/// Single rendered line composed of independently styled parts.
77#[derive(Debug, Clone)]
78pub struct LineBlock {
79    /// Ordered text parts for the line.
80    pub parts: Vec<LinePart>,
81}
82
83/// Fragment of a rendered line with optional semantic styling.
84#[derive(Debug, Clone)]
85pub struct LinePart {
86    /// Literal text to render.
87    pub text: String,
88    /// Optional style token for the fragment.
89    pub token: Option<StyleToken>,
90}
91
92/// Framed panel containing a nested document.
93///
94/// Panels carry grouping intent without hard-coding terminal chrome. The
95/// renderer is free to honor that intent with ASCII, Unicode, or theme-aware
96/// borders.
97#[derive(Debug, Clone)]
98pub struct PanelBlock {
99    /// Optional title displayed in the panel chrome.
100    pub title: Option<String>,
101    /// Nested document rendered inside the panel body.
102    pub body: Document,
103    /// Which horizontal rules to render around the panel.
104    pub rules: PanelRules,
105    /// Explicit frame-style override for the panel.
106    pub frame_style: Option<SectionFrameStyle>,
107    /// Optional semantic kind identifier for the panel.
108    pub kind: Option<String>,
109    /// Optional style token used for panel borders.
110    pub border_token: Option<StyleToken>,
111    /// Optional style token used for the panel title.
112    pub title_token: Option<StyleToken>,
113}
114
115/// Rule placement policy for panel chrome.
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum PanelRules {
118    /// Render no horizontal rules.
119    None,
120    /// Render only a top rule.
121    Top,
122    /// Render only a bottom rule.
123    Bottom,
124    /// Render both top and bottom rules.
125    Both,
126}
127
128/// Fenced code block with optional language metadata.
129#[derive(Debug, Clone)]
130pub struct CodeBlock {
131    /// Code payload to render verbatim.
132    pub code: String,
133    /// Optional language tag used for display or copy helpers.
134    pub language: Option<String>,
135}
136
137/// JSON payload block.
138#[derive(Debug, Clone)]
139pub struct JsonBlock {
140    /// JSON value to render.
141    pub payload: Value,
142}
143
144/// Tabular document block.
145///
146/// Table blocks preserve row/column structure until the final render pass so
147/// width-aware layout decisions stay inside the renderer.
148#[derive(Debug, Clone)]
149pub struct TableBlock {
150    /// Stable identifier used for interactive table state.
151    pub block_id: u64,
152    /// Table rendering style.
153    pub style: TableStyle,
154    /// Optional border style override for this table.
155    pub border_override: Option<TableBorderStyle>,
156    /// Column headers in display order.
157    pub headers: Vec<String>,
158    /// Table rows in display order.
159    pub rows: Vec<Vec<Value>>,
160    /// Optional header metadata rendered above grouped tables.
161    pub header_pairs: Vec<(String, Value)>,
162    /// Optional per-column alignment hints.
163    pub align: Option<Vec<TableAlign>>,
164    /// Whether the renderer may shrink the table to fit width constraints.
165    pub shrink_to_fit: bool,
166    /// Logical nesting depth used for grouped table presentation.
167    pub depth: usize,
168}
169
170/// Table presentation style.
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub enum TableStyle {
173    /// Standard grid/table presentation.
174    Grid,
175    /// Semantic guide-table presentation.
176    Guide,
177    /// Markdown-compatible table presentation.
178    Markdown,
179}
180
181/// Column alignment hint for table rendering.
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum TableAlign {
184    /// Use renderer defaults for the column.
185    Default,
186    /// Left-align cell contents.
187    Left,
188    /// Center-align cell contents.
189    Center,
190    /// Right-align cell contents.
191    Right,
192}
193
194/// Block representing a simple ordered list of scalar values.
195#[derive(Debug, Clone)]
196pub struct ValueBlock {
197    /// Values to render line by line.
198    pub values: Vec<String>,
199    /// Additional indent applied before each rendered line.
200    pub indent: usize,
201    /// Whether inline markup should be parsed before rendering.
202    pub inline_markup: bool,
203    /// Layout policy for the values.
204    pub layout: ValueLayout,
205}
206
207/// Layout policy for scalar value blocks.
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
209pub enum ValueLayout {
210    /// Render one item per line.
211    #[default]
212    Vertical,
213    /// Render as a grid when the list is long enough.
214    AutoGrid,
215}
216
217/// MREG-style hierarchical key/value block.
218#[derive(Debug, Clone)]
219pub struct MregBlock {
220    /// Stable identifier used for interactive MREG state.
221    pub block_id: u64,
222    /// Rows that make up the block.
223    pub rows: Vec<MregRow>,
224}
225
226/// One row inside an MREG-style block.
227#[derive(Debug, Clone)]
228pub struct MregRow {
229    /// Ordered entries rendered for the row.
230    pub entries: Vec<MregEntry>,
231}
232
233/// Key/value entry inside an MREG row.
234#[derive(Debug, Clone)]
235pub struct MregEntry {
236    /// Display key for the entry.
237    pub key: String,
238    /// Logical nesting depth of the entry.
239    pub depth: usize,
240    /// Rendered value payload.
241    pub value: MregValue,
242}
243
244/// Rendered value kinds supported by MREG output.
245#[derive(Debug, Clone)]
246pub enum MregValue {
247    /// Group heading marker.
248    Group,
249    /// Visual separator marker.
250    Separator,
251    /// Scalar JSON value.
252    Scalar(Value),
253    /// Vertical list of values.
254    VerticalList(Vec<Value>),
255    /// Compact grid of values.
256    Grid(Vec<Value>),
257}