terminal/
lib.rs

1//! A helper library for terminal output.
2//!
3//! This library is used by Spin to print out messages in an appropriate format
4//! that is easy for users to read. This is not meant as a general purpose library.
5
6use std::{io::IsTerminal, sync::OnceLock};
7use termcolor::{ColorSpec, StandardStream, StandardStreamLock, WriteColor};
8
9static COLOR_OUT: OnceLock<StandardStream> = OnceLock::new();
10static COLOR_ERR: OnceLock<StandardStream> = OnceLock::new();
11
12/// A wrapper around a standard stream lock that resets the color on drop
13pub struct ColorText(StandardStreamLock<'static>);
14
15impl ColorText {
16    /// Create a `ColorText` tied to stdout
17    pub fn stdout(spec: ColorSpec) -> ColorText {
18        let stream =
19            COLOR_OUT.get_or_init(|| StandardStream::stdout(color_choice(std::io::stdout())));
20        set_color(stream, spec)
21    }
22
23    /// Create a `ColorText` tied to stderr
24    pub fn stderr(spec: ColorSpec) -> ColorText {
25        let stream =
26            COLOR_ERR.get_or_init(|| StandardStream::stderr(color_choice(std::io::stderr())));
27        set_color(stream, spec)
28    }
29}
30
31impl std::io::Write for ColorText {
32    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
33        self.0.write(buf)
34    }
35
36    fn flush(&mut self) -> std::io::Result<()> {
37        self.0.flush()
38    }
39}
40
41impl WriteColor for ColorText {
42    fn supports_color(&self) -> bool {
43        self.0.supports_color()
44    }
45
46    fn set_color(&mut self, spec: &ColorSpec) -> std::io::Result<()> {
47        self.0.set_color(spec)
48    }
49
50    fn reset(&mut self) -> std::io::Result<()> {
51        self.0.reset()
52    }
53}
54
55impl Drop for ColorText {
56    fn drop(&mut self) {
57        let _ = self.reset();
58    }
59}
60
61fn set_color(stream: &'static StandardStream, spec: ColorSpec) -> ColorText {
62    let mut lock = stream.lock();
63    let _ = lock.set_color(&spec);
64    ColorText(lock)
65}
66
67fn color_choice(stream: impl IsTerminal) -> termcolor::ColorChoice {
68    if stream.is_terminal() {
69        termcolor::ColorChoice::Auto
70    } else {
71        termcolor::ColorChoice::Never
72    }
73}
74
75#[macro_export]
76macro_rules! step {
77    ($step:expr, $($arg:tt)*) => {{
78        $crate::cprint!($crate::colors::bold_green(), $step);
79        print!(" ");
80        println!($($arg)*);
81    }};
82}
83
84#[macro_export]
85macro_rules! warn {
86    ($($arg:tt)*) => {{
87        $crate::ceprint!($crate::colors::bold_yellow(), "Warning");
88        eprint!(": ");
89        eprintln!($($arg)*);
90    }};
91}
92
93#[macro_export]
94macro_rules! error {
95    ($($arg:tt)*) => {{
96        $crate::ceprint!($crate::colors::bold_red(), "Error");
97        eprint!(": ");
98        eprintln!($($arg)*);
99    }};
100}
101
102#[macro_export]
103macro_rules! einfo {
104    ($highlight:expr, $($arg:tt)*) => {{
105        $crate::ceprint!($crate::colors::bold_cyan(), $highlight);
106        eprint!(" ");
107        eprintln!($($arg)*);
108    }};
109}
110
111#[macro_export]
112macro_rules! cprint {
113    ($color:expr, $($arg:tt)*) => {
114        use std::io::Write;
115        let mut out = $crate::ColorText::stdout($color);
116        let _ = write!(out, $($arg)*);
117        drop(out); // Reset colors
118    };
119}
120
121#[macro_export]
122macro_rules! ceprint {
123    ($color:expr, $($arg:tt)*) => {
124        use std::io::Write;
125        let mut out = $crate::ColorText::stderr($color);
126        let _ = write!(out, $($arg)*);
127        drop(out); // Reset colors
128    };
129}
130
131#[macro_export]
132macro_rules! ceprintln {
133    ($color:expr, $($arg:tt)*) => {
134        use std::io::Write;
135        let mut out = $crate::ColorText::stderr($color);
136        let _ = writeln!(out, $($arg)*);
137        drop(out); // Reset colors
138    };
139}
140
141pub mod colors {
142    use termcolor::{Color, ColorSpec};
143
144    pub fn bold_red() -> ColorSpec {
145        new(Color::Red, true)
146    }
147
148    pub fn bold_green() -> ColorSpec {
149        new(Color::Green, true)
150    }
151
152    pub fn bold_cyan() -> ColorSpec {
153        new(Color::Cyan, true)
154    }
155
156    pub fn bold_yellow() -> ColorSpec {
157        new(Color::Yellow, true)
158    }
159
160    fn new(color: Color, bold: bool) -> ColorSpec {
161        let mut s = ColorSpec::new();
162        s.set_fg(Some(color)).set_bold(bold);
163        s
164    }
165}