Skip to main content

output_readme/
output-readme.rs

1//! The output hero collage for the README, mirroring the C sparcli demo:
2//! a titled hero panel, a three-column dashboard (table | list+tree+rules |
3//! key-value+badges+columns) and a progress bar, in the same colors.
4//!
5//! `cargo run --example output-readme --features markup`
6
7use sparcli::prelude::*;
8use sparcli::{
9    Badge, Cell, Column, Columns, KeyValue, List, Marker, Panel, ProgressBar,
10    Rule, Table, Tree, TreeNode,
11};
12
13fn main() -> Result<()> {
14    println!();
15    hero()?;
16    println!();
17    dashboard()?;
18    println!();
19    progress()?;
20    Ok(())
21}
22
23/// A blank one-line block, used as a vertical spacer in `vstack`.
24fn blank() -> Rendered {
25    Rendered::new(vec![Line::default()])
26}
27
28/// The top hero panel with styled, centered text.
29fn hero() -> Result<()> {
30    let cyan = Style::new().fg(Color::Cyan).bold();
31    let on_magenta = Style::new().fg(Color::Black).bg(Color::Magenta).bold();
32    let yellow = Style::new().fg(Color::Yellow).bold();
33    let body = Text::new(vec![
34        Line::new(vec![
35            Span::raw("A native Rust library for "),
36            Span::styled("styled terminal output", cyan),
37            Span::raw(" and "),
38            Span::styled("input", on_magenta),
39            Span::raw(" - panels, tables, trees, lists,"),
40        ]),
41        Line::new(vec![
42            Span::raw("key-value pairs, badges, rules, "),
43            Span::styled("columns", cyan),
44            Span::raw(" and progress bars, with Rich-compatible "),
45            Span::styled("[markup]", yellow),
46            Span::raw("."),
47        ]),
48    ]);
49    Panel::new(body)
50        .border(BorderType::Rounded)
51        .border_style(Style::new().fg(Color::Cyan))
52        .title(
53            Title::new(" sparcli ")
54                .align(Align::Center)
55                .style(Style::new().fg(Color::Cyan).bold()),
56        )
57        .content_align(Align::Center)
58        .width(96)
59        .print()
60}
61
62/// The three-column dashboard row.
63fn dashboard() -> Result<()> {
64    Columns::new()
65        .add_rendered(overview_table())
66        .add_rendered(middle_column())
67        .add_rendered(right_column())
68        .gap(3)
69        .separator(BorderType::Single)
70        .print()
71}
72
73/// Left column: the striped, row-separated "Overview" table.
74fn overview_table() -> Rendered {
75    let status = |text: &str, color: Color| {
76        Cell::new(Text::styled(text, Style::new().fg(color).bold()))
77    };
78    Table::new()
79        .title("Overview")
80        .title_style(Style::new().fg(Color::Magenta).bold())
81        .border(BorderType::Rounded)
82        .border_style(Style::new().fg(Color::Magenta))
83        .header_style(Style::new().fg(Color::Cyan).bold())
84        .striped(true)
85        .stripe_style(Style::new().bg(Color::Rgb(40, 40, 60)))
86        .row_separators(true)
87        .columns([
88            Column::new("Service"),
89            Column::new("Status").align(Align::Center),
90            Column::new("Uptime").align(Align::Right),
91        ])
92        .row([
93            Cell::new("api-gateway"),
94            status("● OK", Color::Green),
95            Cell::new("99.98%"),
96        ])
97        .row([
98            Cell::new("auth"),
99            status("● OK", Color::Green),
100            Cell::new("99.91%"),
101        ])
102        .row([
103            Cell::new("billing"),
104            status("● WARN", Color::Yellow),
105            Cell::new("97.40%"),
106        ])
107        .row([
108            Cell::new("scheduler"),
109            status("● FAIL", Color::Red),
110            Cell::new("82.10%"),
111        ])
112        .render(40)
113}
114
115/// Middle column: a numbered list above a tree beside titled rules.
116fn middle_column() -> Rendered {
117    let list = List::ordered(Marker::Number)
118        .marker_style(Style::new().fg(Color::Yellow).bold())
119        .item("Compose widgets side by side")
120        .item("Capture, pad, and align them")
121        .item("Style with Rich-style markup")
122        .item("Render to any UTF-8 terminal")
123        .render(36);
124
125    let tree = Tree::new()
126        .dashes(2)
127        .connector_style(Style::new().fg(Color::Cyan))
128        .node(
129            TreeNode::new("project/")
130                .child(
131                    TreeNode::new("api/")
132                        .child(TreeNode::new("routes.c"))
133                        .child(TreeNode::new("auth.c")),
134                )
135                .child(TreeNode::new("worker.c"))
136                .child(TreeNode::new("store.c")),
137        )
138        .render(18);
139
140    let rules = vstack(
141        &[
142            rule("Pipeline", BorderType::Single, Color::Magenta),
143            blank(),
144            rule("Workers", BorderType::Double, Color::Green),
145            blank(),
146            rule("Storage", BorderType::Thick, Color::Yellow),
147            blank(),
148        ],
149        0,
150    );
151
152    let tree_and_rules = Columns::new()
153        .add_rendered(tree)
154        .add_rendered(rules)
155        .gap(2)
156        .render(40);
157
158    vstack(&[list, blank(), tree_and_rules], 0)
159}
160
161/// One titled rule with a colored title and line.
162fn rule(title: &str, border: BorderType, color: Color) -> Rendered {
163    Rule::with_title(Text::styled(title, Style::new().fg(color).bold()))
164        .border(border)
165        .style(Style::new().fg(color))
166        .width(20)
167        .render(20)
168}
169
170/// Right column: key-value pairs, badges and a staggered columns demo.
171fn right_column() -> Rendered {
172    let kv = KeyValue::new()
173        .key_style(Style::new().fg(Color::Cyan).bold())
174        .value_style(Style::new())
175        .add("host", "localhost")
176        .add("port", "8080")
177        .add("scheme", "https")
178        .add("timeout", "30s")
179        .render(24);
180
181    let badge = |text: &str, color: Color| {
182        Badge::new(text)
183            .pad(1)
184            .style(Style::new().fg(Color::Black).bg(color).bold())
185            .span()
186    };
187    let badges = Rendered::new(vec![
188        Line::new(vec![
189            badge("DONE", Color::Green),
190            Span::raw(" "),
191            badge("INFO", Color::LightBlue),
192        ]),
193        Line::default(),
194        Line::new(vec![
195            badge("WARN", Color::Yellow),
196            Span::raw(" "),
197            badge("FAIL", Color::Red),
198        ]),
199    ]);
200
201    let label = |text: &str| {
202        Rendered::new(vec![Line::styled(
203            text,
204            Style::new().fg(Color::Cyan).bold(),
205        )])
206    };
207    let staggered = Columns::new()
208        .add_rendered(label("Col 1"))
209        .add_rendered(vstack(&[blank(), label("Col 2")], 0))
210        .add_rendered(vstack(&[blank(), blank(), label("Col 3")], 0))
211        .gap(2)
212        .separator(BorderType::Single)
213        .render(40);
214
215    vstack(&[kv, blank(), badges, blank(), staggered], 0)
216}
217
218/// The bottom progress bar.
219fn progress() -> Result<()> {
220    ProgressBar::new()
221        .label("Building")
222        .fill_color(Color::Green)
223        .caps("[", "]")
224        .width(29)
225        .show_value(true)
226        .bar(92.0, 100.0)
227        .print()
228}