Skip to main content

cli_ui/
macros.rs

1//! User-facing macros for runtime output.
2//!
3//! All macros write to **stderr** via [`anstream`], which automatically
4//! strips ANSI codes when output is piped or redirected.
5//!
6//! # Quick reference
7//!
8//! | Macro | Output |
9//! |-------|--------|
10//! | [`header!`](crate::header) | Themed badge + version + tagline |
11//! | [`phase!`](crate::phase) | `▶ init  reading file.html` |
12//! | [`step!`](crate::step) | `▸ downloading remote assets` |
13//! | [`substep!`](crate::substep) | `└─ https://… → ./fonts/…` |
14//! | [`ok!`](crate::ok) | `✓  dist/index.html` |
15//! | [`bail!`](crate::bail) | Error + exit(1) |
16//! | [`summary!`](macro@crate::summary) | Aligned summary block |
17
18/// Print the app header with a themed badge.
19///
20/// Reads `badge` from the app theme set in `#[cli(theme = "...")]`.
21/// Use the 4-argument form for the default cyan badge.
22///
23/// # Example
24/// ```rust,no_run
25/// cli_ui::header!("sitefetch", "0.1.0", "offline asset localizer", "all assets offline");
26/// ```
27///
28/// Output:
29/// ```text
30///   sitefetch   v0.1.0
31///  offline asset localizer — all assets offline
32/// ```
33#[macro_export]
34macro_rules! header {
35    // with explicit badge style (generated code passes this form)
36    ($name:expr, $version:expr, $about:expr, $tagline:expr, badge=$badge:expr) => {{
37        use $crate::styles::*;
38        eprintln!();
39        eprintln!(
40            "  {}  {}",
41            paint($badge, &format!(" {} ", $name)),
42            paint(DIM, &format!("v{}", $version)),
43        );
44        eprintln!(" {}", paint(DIM, &format!("{} — {}", $about, $tagline)));
45        eprintln!();
46    }};
47    // default cyan badge
48    ($name:expr, $version:expr, $about:expr, $tagline:expr) => {{
49        use $crate::styles::*;
50        eprintln!();
51        eprintln!(
52            "  {}  {}",
53            paint(BADGE_CYAN, &format!(" {} ", $name)),
54            paint(DIM, &format!("v{}", $version)),
55        );
56        eprintln!(" {}", paint(DIM, &format!("{} — {}", $about, $tagline)));
57        eprintln!();
58    }};
59}
60
61/// Print a phase line: `▶ tag  message`
62///
63/// # Example
64/// ```rust,no_run
65/// cli_ui::phase!("init", "reading {}", "index.html");
66/// // ▶  init  reading index.html
67/// ```
68#[macro_export]
69macro_rules! phase {
70    ($tag:expr, $($arg:tt)*) => {{
71        use $crate::styles::*;
72        let msg = format!($($arg)*);
73        eprintln!(
74            " {}  {:<6}  {}",
75            paint(ACCENT, PHASE),
76            paint(ACCENT, $tag),
77            paint(DIM, &msg),
78        );
79    }};
80}
81
82/// Print a step header: `▸ message`
83///
84/// # Example
85/// ```rust,no_run
86/// cli_ui::step!("downloading remote assets");
87/// // ▸  downloading remote assets
88/// ```
89#[macro_export]
90macro_rules! step {
91    ($($arg:tt)*) => {{
92        use $crate::styles::*;
93        let msg = format!($($arg)*);
94        eprintln!();
95        eprintln!(" {}  {}", paint(WHITE, BULLET), paint(WHITE, &msg));
96    }};
97}
98
99/// Print a CSS/asset sub-step: `└─ url → local`
100///
101/// # Example
102/// ```rust,no_run
103/// cli_ui::substep!("https://fonts.gstatic.com/inter.ttf", "./fonts/inter.ttf");
104/// ```
105#[macro_export]
106macro_rules! substep {
107    ($url:expr, $local:expr) => {
108        $crate::progress::substep($url, $local);
109    };
110}
111
112/// Print a success line outside of a [`Progress`](crate::Progress) block.
113///
114/// # Example
115/// ```rust,no_run
116/// cli_ui::ok!("dist/index.html");
117/// //   ✓  dist/index.html
118/// ```
119#[macro_export]
120macro_rules! ok {
121    ($path:expr) => {{
122        use $crate::styles::*;
123        eprintln!(
124            "   {}  {}",
125            paint(OK, CHECK),
126            paint(DIM, &$path.to_string())
127        );
128    }};
129}
130
131/// Print an error message and exit with code 1.
132///
133/// # Example
134/// ```rust,no_run
135/// # let path = std::path::PathBuf::from("/etc/hosts");
136/// cli_ui::bail!("cannot read file: {}", path.display());
137/// ```
138#[macro_export]
139macro_rules! bail {
140    ($($arg:tt)*) => {{
141        $crate::print_error(&format!($($arg)*));
142        ::std::process::exit(1);
143    }};
144}
145
146/// Build and print a styled summary block at the end of a run.
147///
148/// # Syntax
149/// ```rust,no_run
150/// use cli_ui::{summary, styles::{paint, CYAN, YELLOW, DIM, OK}};
151///
152/// summary! {
153///     done: "All assets localized",       // or warn: "..." for yellow badge
154///     "input"  => paint(CYAN, "in.html"),
155///     "output" => paint(CYAN, "dist/"),
156///     section,                            // starts new alignment group
157///     "assets" => format!("{} remote", paint(OK, "19")),
158///     "size"   => paint(YELLOW, "1.64 MB"),
159///     "time"   => paint(DIM, "13034ms"),
160/// }
161/// ```
162///
163/// The `section,` separator resets column alignment — keys in each section
164/// are right-aligned independently.
165#[macro_export]
166macro_rules! summary {
167    (done: $msg:expr, $($rest:tt)*) => {{
168        let s = $crate::summary::Summary::new().done($msg);
169        let s = $crate::__summary_inner!(s, $($rest)*);
170        s.print();
171    }};
172    (warn: $msg:expr, $($rest:tt)*) => {{
173        let s = $crate::summary::Summary::new().warn($msg);
174        let s = $crate::__summary_inner!(s, $($rest)*);
175        s.print();
176    }};
177    ($($rest:tt)*) => {{
178        let s = $crate::summary::Summary::new();
179        let s = $crate::__summary_inner!(s, $($rest)*);
180        s.print();
181    }};
182}
183
184#[doc(hidden)]
185#[macro_export]
186macro_rules! __summary_inner {
187    ($s:expr,) => { $s };
188    ($s:expr)  => { $s };
189
190    ($s:expr, done: $msg:expr, $($rest:tt)*) => {{
191        let s = $s.done($msg);
192        $crate::__summary_inner!(s, $($rest)*)
193    }};
194    ($s:expr, done: $msg:expr) => { $s.done($msg) };
195
196    ($s:expr, warn: $msg:expr, $($rest:tt)*) => {{
197        let s = $s.warn($msg);
198        $crate::__summary_inner!(s, $($rest)*)
199    }};
200    ($s:expr, warn: $msg:expr) => { $s.warn($msg) };
201
202    ($s:expr, section, $($rest:tt)*) => {{
203        let s = $s.blank().section();
204        $crate::__summary_inner!(s, $($rest)*)
205    }};
206
207    ($s:expr, $key:literal => $val:expr, $($rest:tt)*) => {{
208        let s = $s.stat($key, &format!("{}", $val));
209        $crate::__summary_inner!(s, $($rest)*)
210    }};
211    ($s:expr, $key:literal => $val:expr) => {
212        $s.stat($key, &format!("{}", $val))
213    };
214}