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}