Skip to main content

rich_ext/
macros.rs

1//! Convenience macros and the print helpers they use.
2//!
3//! - [`rich_table!`](crate::rich_table), [`rich_panel!`](crate::rich_panel),
4//!   [`rich_tree!`](crate::rich_tree) and [`rich_progress!`](crate::rich_progress)
5//!   build the ordinary core types, so the result can still be configured.
6//! - [`rich_dbg!`](crate::rich_dbg) is `dbg!` rendered through `Pretty`.
7//! - With the `macros` feature, [`rich_println!`](crate::rich_println),
8//!   [`rich_eprintln!`](crate::rich_eprintln) and
9//!   [`rich_trace!`](crate::rich_trace) print compile-time checked markup.
10
11use std::io::{IsTerminal, Write};
12
13use rich::{Console, Renderable};
14
15/// A console for standard error: a terminal only when stderr is one.
16pub fn stderr_console() -> Console {
17    Console::builder()
18        .force_terminal(std::io::stderr().is_terminal())
19        .build()
20}
21
22/// Print `renderable` and a newline to standard error.
23pub fn eprint(renderable: &dyn Renderable) {
24    let rendered = stderr_console().render_to_string(renderable);
25    let mut stderr = std::io::stderr().lock();
26    let _ = writeln!(stderr, "{rendered}");
27}
28
29/// Print `renderable` to standard output.
30pub fn print(renderable: &dyn Renderable) {
31    Console::new().print(renderable);
32}
33
34/// A table from a header row and cell rows; each cell is anything `Display`.
35/// Headers and cells are displayed literally, never parsed as markup.
36///
37/// ```
38/// let table = rich_ext::rich_table!(["Name", "Age"], ["Alice", 30], ["Bob", 4]);
39/// # let _ = table;
40/// ```
41#[macro_export]
42macro_rules! rich_table {
43    ([$($header:expr),* $(,)?] $(, [$($cell:expr),* $(,)?])* $(,)?) => {{
44        let mut table = $crate::__private::rich::Table::new();
45        $( table.add_column_text(
46            $crate::__private::rich::Text::new(::std::string::ToString::to_string(&$header)),
47            $crate::__private::rich::Justify::Left,
48        ); )*
49        $( table.add_row_text(::std::vec![$( $crate::__private::rich::Text::new(
50            ::std::string::ToString::to_string(&$cell),
51        ) ),*]); )*
52        table
53    }};
54}
55
56/// A panel around a renderable, or around markup when given a string literal.
57/// Optional `title = …` and `subtitle = …` follow.
58///
59/// ```
60/// let panel = rich_ext::rich_panel!("[bold]ready[/]", title = "status");
61/// # let _ = panel;
62/// ```
63#[macro_export]
64macro_rules! rich_panel {
65    ($markup:literal $(, $key:ident = $value:expr)* $(,)?) => {
66        $crate::rich_panel!(@build ::std::boxed::Box::new(
67            $crate::__private::rich::Text::from_markup($markup)
68                .unwrap_or_else(|_| $crate::__private::rich::Text::new($markup))
69        ) $(, $key = $value)*)
70    };
71    (@build $content:expr $(, $key:ident = $value:expr)*) => {{
72        let panel = $crate::__private::rich::Panel::new($content);
73        $( let panel = panel.$key($value); )*
74        panel
75    }};
76    ($content:expr $(, $key:ident = $value:expr)* $(,)?) => {
77        $crate::rich_panel!(@build ::std::boxed::Box::new($content) $(, $key = $value)*)
78    };
79}
80
81/// A tree from nested labels: `rich_tree!("root" => ["a", "b" => ["c"]])`.
82/// Labels are displayed literally, never parsed as markup.
83///
84/// ```
85/// let tree = rich_ext::rich_tree!("src" => ["main.rs", "lib" => ["mod.rs"]]);
86/// # let _ = tree;
87/// ```
88#[macro_export]
89macro_rules! rich_tree {
90    ($label:expr $(=> [$($children:tt)*])?) => {{
91        #[allow(unused_mut)]
92        let mut tree = $crate::__private::rich::Tree::new($crate::__private::rich::Text::new($label));
93        $( $crate::rich_tree!(@children tree; $($children)*); )?
94        tree
95    }};
96    (@children $parent:ident; ) => {};
97    (@children $parent:ident; $label:expr => [$($children:tt)*] $(, $($rest:tt)*)?) => {{
98        let node = $parent.add($crate::__private::rich::Text::new($label));
99        $crate::rich_tree!(@children node; $($children)*);
100        $( $crate::rich_tree!(@children $parent; $($rest)*); )?
101    }};
102    (@children $parent:ident; $label:expr $(, $($rest:tt)*)?) => {{
103        $parent.add($crate::__private::rich::Text::new($label));
104        $( $crate::rich_tree!(@children $parent; $($rest)*); )?
105    }};
106}
107
108/// Iterate with a progress bar on standard output: core's `track`.
109///
110/// ```no_run
111/// for _ in rich_ext::rich_progress!(0..10, "Working") {}
112/// ```
113#[macro_export]
114macro_rules! rich_progress {
115    ($iter:expr, $description:expr $(,)?) => {
116        $crate::__private::rich::track($iter, $description)
117    };
118    ($iter:expr $(,)?) => {
119        $crate::__private::rich::track($iter, "Working...")
120    };
121}
122
123/// `dbg!` through `Pretty`: prints `[file:line:column] expression = value` to
124/// standard error with the value highlighted, and returns the value.
125///
126/// ```
127/// let doubled = rich_ext::rich_dbg!(2 * 21);
128/// assert_eq!(doubled, 42);
129/// ```
130#[macro_export]
131macro_rules! rich_dbg {
132    () => {
133        $crate::macros::eprint(&$crate::__private::rich::Text::styled(
134            ::std::format!("[{}:{}:{}]", ::std::file!(), ::std::line!(), ::std::column!()),
135            "dim",
136        ))
137    };
138    ($value:expr $(,)?) => {
139        match $value {
140            value => {
141                $crate::macros::eprint(&$crate::macros::dbg_line(
142                    ::std::file!(),
143                    ::std::line!(),
144                    ::std::column!(),
145                    ::std::stringify!($value),
146                    &value,
147                ));
148                value
149            }
150        }
151    };
152    ($($value:expr),+ $(,)?) => {
153        ($($crate::rich_dbg!($value)),+,)
154    };
155}
156
157/// The line `rich_dbg!` prints: a dim location, the expression, and the
158/// value through `Pretty`.
159pub fn dbg_line(
160    file: &str,
161    line: u32,
162    column: u32,
163    expression: &str,
164    value: &dyn std::fmt::Debug,
165) -> rich::Text {
166    use rich::Highlighter;
167    let mut text = rich::Text::styled(format!("[{file}:{line}:{column}] "), "dim");
168    text.append(expression, Some("bold".into()));
169    text.append(" = ", None);
170    let mut rendered = rich::Text::new(format!("{value:#?}"));
171    rich::ReprHighlighter::new().highlight(&mut rendered);
172    text.append_text(&rendered)
173}
174
175/// Print checked markup to standard output: `rich_println!("[bold]{x}[/]")`.
176#[cfg(feature = "macros")]
177#[macro_export]
178macro_rules! rich_println {
179    ($($arg:tt)*) => {
180        $crate::macros::print(&$crate::richf!($($arg)*))
181    };
182}
183
184/// Print checked markup to standard error.
185#[cfg(feature = "macros")]
186#[macro_export]
187macro_rules! rich_eprintln {
188    ($($arg:tt)*) => {
189        $crate::macros::eprint(&$crate::richf!($($arg)*))
190    };
191}
192
193/// Print checked markup to standard error after a dim `file:line` — a quick
194/// trace line. For log pipelines, route `log`/`tracing` through `RichHandler`.
195#[cfg(feature = "macros")]
196#[macro_export]
197macro_rules! rich_trace {
198    ($($arg:tt)*) => {
199        $crate::macros::eprint(&$crate::__private::rich::Text::styled(
200            ::std::format!("{}:{} ", ::std::file!(), ::std::line!()),
201            "dim",
202        ).append_text(&$crate::richf!($($arg)*)))
203    };
204}