Skip to main content

foundry_common/io/
macros.rs

1/// Prints a message to [`stderr`][std::io::stderr] and reads a line from stdin into a String.
2///
3/// Returns `Result<T>`, so sometimes `T` must be explicitly specified, like in `str::parse`.
4///
5/// # Examples
6///
7/// ```no_run
8/// use foundry_common::prompt;
9///
10/// let response: String = prompt!("Would you like to continue? [y/N] ")?;
11/// if !matches!(response.as_str(), "y" | "Y") {
12///     return Ok(());
13/// }
14/// # Ok::<(), Box<dyn std::error::Error>>(())
15/// ```
16#[macro_export]
17macro_rules! prompt {
18    () => {
19        $crate::stdin::parse_line()
20    };
21
22    ($($tt:tt)+) => {{
23        let _ = $crate::sh_eprint!($($tt)+);
24        match ::std::io::Write::flush(&mut ::std::io::stderr()) {
25            ::core::result::Result::Ok(()) => $crate::prompt!(),
26            ::core::result::Result::Err(e) => ::core::result::Result::Err(::eyre::eyre!("Could not flush stderr: {e}"))
27        }
28    }};
29}
30
31/// Prints a formatted error to stderr.
32///
33/// **Note**: will log regardless of the verbosity level.
34#[macro_export]
35macro_rules! sh_err {
36    ($($args:tt)*) => {
37        $crate::__sh_dispatch!(error $($args)*)
38    };
39}
40
41/// Prints a formatted warning to stderr.
42///
43/// **Note**: if `verbosity` is set to `Quiet`, this is a no-op.
44#[macro_export]
45macro_rules! sh_warn {
46    ($($args:tt)*) => {
47        $crate::__sh_dispatch!(warn $($args)*)
48    };
49}
50
51/// Prints a raw formatted message to stdout.
52///
53/// **Note**: if `verbosity` is set to `Quiet`, this is a no-op.
54#[macro_export]
55macro_rules! sh_print {
56    ($($args:tt)*) => {
57        $crate::__sh_dispatch!(print_out $($args)*)
58    };
59
60    ($shell:expr, $($args:tt)*) => {
61        $crate::__sh_dispatch!(print_out $shell, $($args)*)
62    };
63}
64
65/// Prints a raw formatted message to stderr.
66///
67/// **Note**: if `verbosity` is set to `Quiet`, this is a no-op.
68#[macro_export]
69macro_rules! sh_eprint {
70    ($($args:tt)*) => {
71        $crate::__sh_dispatch!(print_err $($args)*)
72    };
73
74    ($shell:expr, $($args:tt)*) => {
75        $crate::__sh_dispatch!(print_err $shell, $($args)*)
76    };
77}
78
79/// Prints a raw formatted message to stdout, with a trailing newline.
80///
81/// **Note**: if `verbosity` is set to `Quiet`, this is a no-op.
82#[macro_export]
83macro_rules! sh_println {
84    () => {
85        $crate::sh_print!("\n")
86    };
87
88    ($fmt:literal $($args:tt)*) => {
89        $crate::sh_print!("{}\n", ::core::format_args!($fmt $($args)*))
90    };
91
92    ($shell:expr $(,)?) => {
93        $crate::sh_print!($shell, "\n").expect("failed to write newline")
94    };
95
96    ($shell:expr, $($args:tt)*) => {
97        $crate::sh_print!($shell, "{}\n", ::core::format_args!($($args)*))
98    };
99
100    ($($args:tt)*) => {
101        $crate::sh_print!("{}\n", ::core::format_args!($($args)*))
102    };
103}
104
105/// Prints a status message to stderr with a trailing newline.
106///
107/// Use for human-facing diagnostic prose ("Compiling…", "Deploying contract…")
108/// that is not the command's primary machine-readable result.
109#[macro_export]
110macro_rules! sh_status {
111    ($($args:tt)*) => {
112        $crate::sh_eprintln!($($args)*)
113    };
114}
115
116/// Prints a progress message to stderr with a trailing newline.
117///
118/// Use for transient progress updates outside the spinner.
119///
120/// Suppressed when:
121/// - `--quiet` is set, or
122/// - stderr is not a tty (e.g. CI logs, piped consumers).
123///
124/// Always returns `Ok(())`; progress is best-effort and never fails the caller.
125#[macro_export]
126macro_rules! sh_progress {
127    ($($args:tt)*) => {{
128        if $crate::shell::is_err_tty() && !$crate::shell::is_quiet() {
129            let _ = $crate::sh_eprintln!($($args)*);
130        }
131        ::core::result::Result::<(), ::eyre::Report>::Ok(())
132    }};
133}
134
135/// Prints a raw formatted message to stderr, with a trailing newline.
136///
137/// **Note**: if `verbosity` is set to `Quiet`, this is a no-op.
138#[macro_export]
139macro_rules! sh_eprintln {
140    () => {
141        $crate::sh_eprint!("\n")
142    };
143
144    ($fmt:literal $($args:tt)*) => {
145        $crate::sh_eprint!("{}\n", ::core::format_args!($fmt $($args)*))
146    };
147
148    ($shell:expr $(,)?) => {
149        $crate::sh_eprint!($shell, "\n")
150    };
151
152    ($shell:expr, $($args:tt)*) => {
153        $crate::sh_eprint!($shell, "{}\n", ::core::format_args!($($args)*))
154    };
155
156    ($($args:tt)*) => {
157        $crate::sh_eprint!("{}\n", ::core::format_args!($($args)*))
158    };
159}
160
161#[doc(hidden)]
162#[macro_export]
163macro_rules! __sh_dispatch {
164    ($f:ident $fmt:literal $($args:tt)*) => {
165        $crate::__sh_dispatch!(@impl $f &mut *$crate::Shell::get(), $fmt $($args)*)
166    };
167
168    ($f:ident $shell:expr, $($args:tt)*) => {
169        $crate::__sh_dispatch!(@impl $f $shell, $($args)*)
170    };
171
172    ($f:ident $($args:tt)*) => {
173        $crate::__sh_dispatch!(@impl $f &mut *$crate::Shell::get(), $($args)*)
174    };
175
176    // Ensure that the global shell lock is held for as little time as possible.
177    // Also avoids deadlocks in case of nested calls.
178    (@impl $f:ident $shell:expr, $($args:tt)*) => {
179        match format!($($args)*) {
180            fmt => $crate::Shell::$f($shell, fmt),
181        }
182    };
183}
184
185#[cfg(test)]
186mod tests {
187    #[test]
188    fn macros() -> eyre::Result<()> {
189        sh_err!("err")?;
190        sh_err!("err {}", "arg")?;
191
192        sh_warn!("warn")?;
193        sh_warn!("warn {}", "arg")?;
194
195        sh_print!("print -")?;
196        sh_print!("print {} -", "arg")?;
197
198        sh_println!()?;
199        sh_println!("println")?;
200        sh_println!("println {}", "arg")?;
201
202        sh_eprint!("eprint -")?;
203        sh_eprint!("eprint {} -", "arg")?;
204
205        sh_eprintln!()?;
206        sh_eprintln!("eprintln")?;
207        sh_eprintln!("eprintln {}", "arg")?;
208
209        sh_status!("status")?;
210        sh_status!("status {}", "arg")?;
211
212        sh_progress!("progress")?;
213        sh_progress!("progress {}", "arg")?;
214
215        sh_println!("{:?}", {
216            sh_println!("hi")?;
217            solar::data_structures::fmt::from_fn(|f| {
218                let _ = sh_println!("even more nested");
219                write!(f, "hi 2")
220            })
221        })?;
222
223        Ok(())
224    }
225
226    #[test]
227    fn macros_with_shell() -> eyre::Result<()> {
228        let shell = &mut crate::Shell::new();
229        sh_eprintln!(shell)?;
230        sh_eprintln!(shell,)?;
231        sh_eprintln!(shell, "shelled eprintln")?;
232        sh_eprintln!(shell, "shelled eprintln {}", "arg")?;
233        sh_eprintln!(&mut crate::Shell::new(), "shelled eprintln {}", "arg")?;
234
235        Ok(())
236    }
237
238    /// Asserts that every macro routes to the channel documented in
239    /// `docs/dev/output-channels.md`.
240    #[test]
241    fn routing_contract() -> eyre::Result<()> {
242        let mut shell = crate::Shell::captured();
243
244        // stdout: machine-readable result
245        sh_print!(&mut shell, "out-print")?;
246        sh_println!(&mut shell, "out-println")?;
247
248        // stderr: diagnostics + raw stderr
249        sh_eprint!(&mut shell, "err-print")?;
250        sh_eprintln!(&mut shell, "err-println")?;
251        crate::Shell::warn(&mut shell, "warn-msg")?;
252        crate::Shell::error(&mut shell, "err-msg")?;
253
254        let stdout = std::str::from_utf8(shell.captured_stdout().unwrap()).unwrap();
255        let stderr = std::str::from_utf8(shell.captured_stderr().unwrap()).unwrap();
256
257        // stdout only contains what `sh_print!`/`sh_println!` produced.
258        assert_eq!(stdout, "out-printout-println\n");
259
260        // stderr received the eprint/warn/error output and no stdout content.
261        assert!(stderr.contains("err-print"), "stderr missing eprint: {stderr:?}");
262        assert!(stderr.contains("err-println"), "stderr missing eprintln: {stderr:?}");
263        assert!(stderr.contains("warn-msg"), "stderr missing warn: {stderr:?}");
264        assert!(stderr.contains("err-msg"), "stderr missing error: {stderr:?}");
265        assert!(!stderr.contains("out-print"), "stdout content leaked to stderr: {stderr:?}");
266        assert!(!stderr.contains("out-println"), "stdout content leaked to stderr: {stderr:?}");
267
268        Ok(())
269    }
270
271    /// `--quiet` currently suppresses both stdout and stderr diagnostics, but `sh_err!` must
272    /// always be visible. The stdout half of this is intentional for now; it will be flipped
273    /// to "stdout is never suppressed" once the prose `sh_println!` call sites in forge/script
274    /// are migrated to `sh_status!` (see `docs/dev/output-channels.md`).
275    #[test]
276    fn quiet_contract() -> eyre::Result<()> {
277        let mut shell = crate::Shell::captured();
278        shell.set_output_mode(crate::shell::OutputMode::Quiet);
279
280        sh_println!(&mut shell, "result")?;
281        sh_eprintln!(&mut shell, "diag")?;
282        crate::Shell::warn(&mut shell, "warned")?;
283        crate::Shell::error(&mut shell, "boom")?;
284
285        let stdout = std::str::from_utf8(shell.captured_stdout().unwrap()).unwrap();
286        let stderr = std::str::from_utf8(shell.captured_stderr().unwrap()).unwrap();
287
288        // Today's behavior: stdout is suppressed by --quiet. Pinned here so the future
289        // migration that flips this bypass has to deliberately update the test.
290        assert!(stdout.is_empty(), "stdout leaked through --quiet: {stdout:?}");
291        assert!(!stderr.contains("diag"), "eprintln leaked through --quiet: {stderr:?}");
292        assert!(!stderr.contains("warned"), "warn leaked through --quiet: {stderr:?}");
293        assert!(stderr.contains("boom"), "sh_err was suppressed by --quiet: {stderr:?}");
294
295        Ok(())
296    }
297}