anvil_zksync_common/
shell.rs

1//! Utility functions for writing to stdout and stderr.
2//!
3//! Simplified adaptation from
4//! https://github.com/foundry-rs/foundry/blob/master/crates/common/src/io/macros.rs.
5
6use anstream::{AutoStream, ColorChoice as AnstreamColorChoice};
7use anstyle::{AnsiColor, Effects, Reset, Style};
8use std::fmt::Arguments;
9use std::io::{self, IsTerminal, Write};
10use std::sync::{Mutex, OnceLock};
11
12/// Default styles for WARNING and ERROR messages.
13pub const ERROR: Style = AnsiColor::Red.on_default().effects(Effects::BOLD);
14pub const WARN: Style = AnsiColor::Yellow.on_default().effects(Effects::BOLD);
15
16/// Choices for whether to use colored output.
17#[derive(Debug, Clone, Copy, PartialEq)]
18pub enum ColorChoice {
19    Auto,
20    Always,
21    Never,
22}
23
24impl ColorChoice {
25    #[inline]
26    fn to_anstream(self) -> AnstreamColorChoice {
27        match self {
28            ColorChoice::Always => AnstreamColorChoice::Always,
29            ColorChoice::Never => AnstreamColorChoice::Never,
30            ColorChoice::Auto => AnstreamColorChoice::Auto,
31        }
32    }
33}
34
35/// The output mode: either normal output or completely quiet.
36#[derive(Debug, Clone, Copy, PartialEq)]
37pub enum OutputMode {
38    Normal,
39    Quiet,
40}
41
42/// A shell abstraction that tracks verbosity level, an output mode,
43/// and a color choice.
44/// It uses anstream’s AutoStream for stdout and stderr.
45#[derive(Debug)]
46pub struct Shell {
47    /// Verbosity level.
48    pub verbosity: u8,
49    /// Whether to output anything at all.
50    pub output_mode: OutputMode,
51    /// Whether to use colors.
52    pub color_choice: ColorChoice,
53    /// Adaptive stdout.
54    stdout: AutoStream<std::io::Stdout>,
55    /// Adaptive stderr.
56    stderr: AutoStream<std::io::Stderr>,
57}
58
59impl Shell {
60    /// Creates a new shell with default settings:
61    /// - verbosity 0,
62    /// - Normal output mode,
63    /// - Auto color detection.
64    pub fn new() -> Self {
65        let color = ColorChoice::Auto;
66        Self {
67            verbosity: 0,
68            output_mode: OutputMode::Normal,
69            color_choice: color,
70            stdout: AutoStream::new(std::io::stdout(), color.to_anstream()),
71            stderr: AutoStream::new(std::io::stderr(), color.to_anstream()),
72        }
73    }
74
75    /// Print a string to stdout.
76    pub fn print_out(&mut self, args: Arguments) -> io::Result<()> {
77        if self.output_mode == OutputMode::Quiet {
78            return Ok(());
79        }
80
81        self.stdout.write_fmt(args)?;
82        self.stdout.flush()
83    }
84
85    /// Print a line (with a newline) to stdout.
86    pub fn println_out(&mut self, args: Arguments) -> io::Result<()> {
87        if self.output_mode == OutputMode::Quiet {
88            return Ok(());
89        }
90
91        self.stdout.write_fmt(args)?;
92        writeln!(self.stdout)?;
93        self.stdout.flush()
94    }
95
96    /// Print a string to stderr.
97    pub fn print_err(&mut self, args: Arguments) -> io::Result<()> {
98        if self.output_mode == OutputMode::Quiet {
99            return Ok(());
100        }
101        self.stderr.write_fmt(args)?;
102        self.stderr.flush()
103    }
104
105    /// Print a line (with a newline) to stderr.
106    pub fn println_err(&mut self, args: Arguments) -> io::Result<()> {
107        if self.output_mode == OutputMode::Quiet {
108            return Ok(());
109        }
110        self.stderr.write_fmt(args)?;
111        writeln!(self.stderr)?;
112        self.stderr.flush()
113    }
114
115    /// Print a warning message.
116    ///
117    /// If colors are enabled, the “Warning:” prefix is printed in yellow.
118    pub fn warn(&mut self, args: Arguments) -> io::Result<()> {
119        if self.output_mode == OutputMode::Quiet {
120            return Ok(());
121        }
122        if self.should_color() {
123            write!(self.stderr, "{}Warning:{} ", WARN, Reset)?;
124        } else {
125            write!(self.stderr, "Warning: ")?;
126        }
127        self.stderr.write_fmt(args)?;
128        writeln!(self.stderr)?;
129        self.stderr.flush()
130    }
131
132    /// Print an error message.
133    ///
134    /// If colors are enabled, the “Error:” prefix is printed in red.
135    pub fn error(&mut self, args: Arguments) -> io::Result<()> {
136        if self.should_color() {
137            write!(self.stderr, "{}Error:{} ", ERROR, Reset)?;
138        } else {
139            write!(self.stderr, "Error: ")?;
140        }
141        self.stderr.write_fmt(args)?;
142        writeln!(self.stderr)?;
143        self.stderr.flush()
144    }
145
146    fn should_color(&self) -> bool {
147        match self.color_choice {
148            ColorChoice::Always => true,
149            ColorChoice::Never => false,
150            ColorChoice::Auto => std::io::stdout().is_terminal(),
151        }
152    }
153}
154
155impl Default for Shell {
156    fn default() -> Self {
157        Self::new()
158    }
159}
160
161/// The global shell instance.
162///
163/// This uses a [`OnceLock`](https://doc.rust-lang.org/std/sync/struct.OnceLock.html)
164/// to initialize the shell once and then a [`Mutex`](std::sync::Mutex) to allow
165/// mutable access to it from anywhere.
166static GLOBAL_SHELL: OnceLock<Mutex<Shell>> = OnceLock::new();
167
168/// Get a lock to the global shell.
169///
170/// This will initialize the shell with default values if it has not been set yet.
171pub fn get_shell() -> std::sync::MutexGuard<'static, Shell> {
172    GLOBAL_SHELL
173        .get_or_init(|| Mutex::new(Shell::new()))
174        .lock()
175        .expect("global shell mutex is poisoned")
176}
177
178/// (Optional) Set the global shell with a custom configuration.
179///
180/// Note that this will fail if the shell has already been set.
181pub fn set_shell(shell: Shell) {
182    let _ = GLOBAL_SHELL.set(Mutex::new(shell));
183}
184
185/// Macro to print a formatted message to stdout.
186///
187/// Usage:
188/// ```
189/// use anvil_zksync_common::sh_print;
190///
191/// sh_print!("Hello, {}!", "world");
192/// ```
193#[macro_export]
194macro_rules! sh_print {
195    ($($arg:tt)*) => {{
196        $crate::shell::get_shell().print_out(format_args!($($arg)*))
197            .unwrap_or_else(|e| eprintln!("Error writing output: {}", e));
198    }};
199}
200
201/// Macro to print a formatted message (with a newline) to stdout.
202#[macro_export]
203macro_rules! sh_println {
204    ($($arg:tt)*) => {{
205        $crate::shell::get_shell().println_out(format_args!($($arg)*))
206            .unwrap_or_else(|e| eprintln!("Error writing output: {}", e));
207    }};
208}
209
210/// Macro to print a formatted message to stderr.
211#[macro_export]
212macro_rules! sh_eprint {
213    ($($arg:tt)*) => {{
214        $crate::shell::get_shell().print_err(format_args!($($arg)*))
215            .unwrap_or_else(|e| eprintln!("Error writing stderr: {}", e));
216    }};
217}
218
219/// Macro to print a formatted message (with newline) to stderr.
220#[macro_export]
221macro_rules! sh_eprintln {
222    ($($arg:tt)*) => {{
223        $crate::shell::get_shell().println_err(format_args!($($arg)*))
224            .unwrap_or_else(|e| eprintln!("Error writing stderr: {}", e));
225    }};
226}
227
228/// Macro to print a warning message.
229///
230/// Usage:
231/// ```
232/// use anvil_zksync_common::sh_warn;
233///
234/// sh_warn!("This is a warning: {}", "be careful!");
235/// ```
236#[macro_export]
237macro_rules! sh_warn {
238    ($($arg:tt)*) => {{
239        $crate::shell::get_shell().warn(format_args!($($arg)*))
240            .unwrap_or_else(|e| eprintln!("Error writing warning: {}", e));
241    }};
242}
243
244/// Macro to print an error message.
245///
246/// Usage:
247/// ```
248/// use anvil_zksync_common::sh_err;
249///
250/// sh_err!("Something went wrong: {}", "details");
251/// ```
252#[macro_export]
253macro_rules! sh_err {
254    ($($arg:tt)*) => {{
255        $crate::shell::get_shell().error(format_args!($($arg)*))
256            .unwrap_or_else(|e| eprintln!("Error writing error: {}", e));
257    }};
258}
259
260#[cfg(test)]
261mod tests {
262    #[test]
263    fn test_shell_macros() {
264        sh_print!("Hello, ");
265        sh_println!("world!");
266        sh_eprint!("Error: ");
267        sh_eprintln!("Something went wrong!");
268        sh_warn!("This is a warning");
269        sh_err!("This is an error");
270    }
271}