abscissa_core/terminal/
status.rs

1//! Terminal status handling.
2//!
3//! Presently provides a Cargo-like visual style. Hopefully in future versions
4//! this can be made configurable.
5//!
6//! # `status_ok!`: Successful status messages
7//!
8//! ```ignore
9//! // Print a Cargo-like justified status to STDOUT
10//! status_ok!("Loaded", "app loaded successfully");
11//! ```
12//!
13//! # `status_err!`: Error messages
14//!
15//! ```ignore
16//! // Print an error message
17//! status_err!("something bad happened");
18//! ```
19//!
20//! # `status_attr_ok!`: Successful attributes
21//!
22//! ```ignore
23//! // Print an indented attribute to STDOUT
24//! status_attr_ok!("good", "yep");
25//! ```
26//!
27//! # `status_attr_error!`: Error attributes
28//!
29//! ```ignore
30//! // Print an error attribute to STDERR
31//! status_attr_err!("error", "yep");
32//! ```
33
34use super::{stderr, stdout};
35use crate::FrameworkError;
36use std::io::Write;
37use termcolor::{Color, ColorSpec, StandardStream, WriteColor};
38
39/// Print a success status message (in green if colors are enabled)
40///
41/// ```ignore
42/// // Print a Cargo-like justified status to STDOUT
43/// status_ok!("Loaded", "app loaded successfully");
44/// ```
45#[macro_export]
46macro_rules! status_ok {
47    ($status:expr, $msg:expr) => {
48        $crate::terminal::status::Status::new()
49            .justified()
50            .bold()
51            .color($crate::terminal::Color::Green)
52            .status($status)
53            .print_stderr($msg)
54            .unwrap();
55    };
56    ($status:expr, $fmt:expr, $($arg:tt)+) => {
57        $crate::status_ok!($status, format!($fmt, $($arg)+));
58    };
59}
60
61/// Print an informational status message (in cyan if colors are enabled)
62///
63/// ```ignore
64/// // Print a Cargo-like justified status to STDOUT
65/// status_info!("Info", "you may care to know about");
66/// ```
67#[macro_export]
68macro_rules! status_info {
69    ($status:expr, $msg:expr) => {
70        $crate::terminal::status::Status::new()
71            .justified()
72            .bold()
73            .color($crate::terminal::Color::Cyan)
74            .status($status)
75            .print_stderr($msg)
76            .unwrap();
77    };
78    ($status:expr, $fmt:expr, $($arg:tt)+) => {
79        $crate::status_info!($status, format!($fmt, $($arg)+));
80    };
81}
82
83/// Print a warning status message (in yellow if colors are enabled)
84///
85/// ```ignore
86/// // Print a Cargo-like justified status to STDOUT
87/// status_warn!("heads up, there's something you should know");
88/// ```
89#[macro_export]
90macro_rules! status_warn {
91    ($msg:expr) => {
92        $crate::terminal::status::Status::new()
93            .bold()
94            .color($crate::terminal::Color::Yellow)
95            .status("warning:")
96            .print_stderr($msg)
97            .unwrap();
98    };
99    ($fmt:expr, $($arg:tt)+) => {
100        $crate::status_warn!(format!($fmt, $($arg)+));
101    };
102}
103
104/// Print an error message (in red if colors are enabled)
105///
106/// ```ignore
107/// // Print an error message
108/// status_err!("something bad happened");
109/// ```
110#[macro_export]
111macro_rules! status_err {
112    ($msg:expr) => {
113        $crate::terminal::status::Status::new()
114            .bold()
115            .color($crate::terminal::Color::Red)
116            .status("error:")
117            .print_stderr($msg)
118            .unwrap();
119    };
120    ($fmt:expr, $($arg:tt)+) => {
121        $crate::status_err!(format!($fmt, $($arg)+));
122    };
123}
124
125/// Print a tab-delimited status attribute (in green if colors are enabled)
126///
127/// ```ignore
128/// // Print an indented attribute to STDOUT
129/// status_attr_ok!("good", "yep");
130/// ```
131#[macro_export]
132macro_rules! status_attr_ok {
133    ($attr:expr, $msg:expr) => {
134        // TODO(tarcieri): hax... use a better format string?
135        let attr_delimited = if $attr.len() >= 7 {
136            format!("{}:", $attr)
137        } else {
138            format!("{}:\t", $attr)
139        };
140
141
142        $crate::terminal::status::Status::new()
143            .bold()
144            .color($crate::terminal::Color::Green)
145            .status(attr_delimited)
146            .print_stdout($msg)
147            .unwrap();
148    };
149    ($attr: expr, $fmt:expr, $($arg:tt)+) => {
150        $crate::status_attr_ok!($attr, format!($fmt, $($arg)+));
151    }
152}
153
154/// Print a tab-delimited status attribute (in red if colors are enabled)
155///
156/// ```ignore
157/// // Print an error attribute to STDERR
158/// status_attr_err!("error", "yep");
159/// ```
160#[macro_export]
161macro_rules! status_attr_err {
162    ($attr:expr, $msg:expr) => {
163        // TODO(tarcieri): hax... use a better format string?
164        let attr_delimited = if $attr.len() >= 7 {
165            format!("{}:", $attr)
166        } else {
167            format!("{}:\t", $attr)
168        };
169
170
171        $crate::terminal::status::Status::new()
172            .bold()
173            .color($crate::terminal::Color::Red)
174            .status(attr_delimited)
175            .print_stdout($msg)
176            .unwrap();
177    };
178    ($attr: expr, $fmt:expr, $($arg:tt)+) => {
179        $crate::status_attr_err!($attr, format!($fmt, $($arg)+));
180    }
181}
182
183/// Status message builder
184#[derive(Clone, Debug, Default)]
185pub struct Status {
186    /// Should the status be justified?
187    justified: bool,
188
189    /// Should colors be bold?
190    bold: bool,
191
192    /// Color in which status should be displayed
193    color: Option<Color>,
194
195    /// Prefix of the status message (e.g. `Success`)
196    status: Option<String>,
197}
198
199impl Status {
200    /// Create a new status message with default settings
201    pub fn new() -> Self {
202        Self::default()
203    }
204
205    /// Justify status on display
206    pub fn justified(mut self) -> Self {
207        self.justified = true;
208        self
209    }
210
211    /// Make colors bold
212    pub fn bold(mut self) -> Self {
213        self.bold = true;
214        self
215    }
216
217    /// Set the colors used to display this message
218    pub fn color(mut self, c: Color) -> Self {
219        self.color = Some(c);
220        self
221    }
222
223    /// Set a status message to display
224    pub fn status<S>(mut self, msg: S) -> Self
225    where
226        S: ToString,
227    {
228        self.status = Some(msg.to_string());
229        self
230    }
231
232    /// Print the given message to stdout
233    pub fn print_stdout<S>(self, msg: S) -> Result<(), FrameworkError>
234    where
235        S: AsRef<str>,
236    {
237        self.print(stdout(), msg)
238    }
239
240    /// Print the given message to stderr
241    pub fn print_stderr<S>(self, msg: S) -> Result<(), FrameworkError>
242    where
243        S: AsRef<str>,
244    {
245        self.print(stderr(), msg)
246    }
247
248    /// Print the given message
249    fn print<S>(self, stream: &StandardStream, msg: S) -> Result<(), FrameworkError>
250    where
251        S: AsRef<str>,
252    {
253        let mut s = stream.lock();
254        s.reset()?;
255        s.set_color(ColorSpec::new().set_fg(self.color).set_bold(self.bold))?;
256
257        if let Some(status) = self.status {
258            if self.justified {
259                write!(s, "{:>12}", status)?;
260            } else {
261                write!(s, "{}", status)?;
262            }
263        }
264
265        s.reset()?;
266        let msg = msg.as_ref();
267        if !msg.is_empty() {
268            writeln!(s, " {}", msg)?;
269        }
270        s.flush()?;
271
272        Ok(())
273    }
274}