1use std::fmt;
13use std::io::{self, Write};
14use std::sync::{Arc, Mutex, RwLock};
15
16pub type Sink = Arc<Mutex<dyn Write + Send>>;
18
19static SINK: RwLock<Option<Sink>> = RwLock::new(None);
21
22pub fn notice(args: fmt::Arguments<'_>) {
27 let _ = Stderr.write_fmt(format_args!("{}\n", args));
28}
29
30pub fn redirect(sink: Sink) -> RedirectGuard {
35 let mut slot = SINK.write().unwrap_or_else(|p| p.into_inner());
36 let previous = slot.replace(sink);
37 RedirectGuard { previous }
38}
39
40#[must_use = "diagnostics return to stderr as soon as the guard is dropped"]
42pub struct RedirectGuard {
43 previous: Option<Sink>,
44}
45
46impl Drop for RedirectGuard {
47 fn drop(&mut self) {
48 let mut slot = SINK.write().unwrap_or_else(|p| p.into_inner());
49 *slot = self.previous.take();
50 }
51}
52
53#[derive(Debug, Clone, Copy, Default)]
58pub struct Stderr;
59
60impl Write for Stderr {
61 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
62 let slot = SINK.read().unwrap_or_else(|p| p.into_inner());
63 match slot.as_ref() {
64 Some(sink) => sink.lock().unwrap_or_else(|p| p.into_inner()).write(buf),
65 None => io::stderr().write(buf),
66 }
67 }
68
69 fn flush(&mut self) -> io::Result<()> {
70 let slot = SINK.read().unwrap_or_else(|p| p.into_inner());
71 match slot.as_ref() {
72 Some(sink) => sink.lock().unwrap_or_else(|p| p.into_inner()).flush(),
73 None => io::stderr().flush(),
74 }
75 }
76}
77
78#[macro_export]
80macro_rules! notice {
81 ($($arg:tt)*) => {
82 $crate::diag::notice(format_args!($($arg)*))
83 };
84}
85
86#[cfg(test)]
91mod tests {
92 use super::*;
93
94 static REDIRECT_LOCK: Mutex<()> = Mutex::new(());
98
99 #[test]
100 fn test_stderr_writer_flushes_with_and_without_redirect() {
101 let _lock = REDIRECT_LOCK.lock().unwrap_or_else(|p| p.into_inner());
102 let mut w = Stderr;
104 assert!(w.flush().is_ok());
105 let buf: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
106 let _g = redirect(buf.clone());
107 assert!(w.flush().is_ok());
108 }
109
110 #[test]
111 fn test_redirect_captures_and_restores() {
112 let _lock = REDIRECT_LOCK.lock().unwrap_or_else(|p| p.into_inner());
113 let outer: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
116 let inner: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
117 {
118 let _g1 = redirect(outer.clone());
119 notice(format_args!("one"));
120 {
121 let _g2 = redirect(inner.clone());
122 crate::notice!("two {}", 2);
123 }
124 notice(format_args!("three"));
125 }
126 let outer = String::from_utf8_lossy(&outer.lock().unwrap()).into_owned();
129 let inner = String::from_utf8_lossy(&inner.lock().unwrap()).into_owned();
130 let (one, three) = (outer.find("one\n").unwrap(), outer.find("three\n").unwrap());
131 assert!(one < three);
132 assert!(!outer.contains("two 2"));
133 assert!(inner.contains("two 2\n"));
134 assert!(!inner.contains("one") && !inner.contains("three"));
135 }
136}