use std::io::{self, Write};
use std::sync::Mutex;
static CAPTURE: Mutex<Option<Vec<u8>>> = Mutex::new(None);
struct Stderr;
impl Write for Stderr {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
let mut capture = CAPTURE.lock().unwrap_or_else(|error| error.into_inner());
if let Some(buffer) = capture.as_mut() {
buffer.extend_from_slice(bytes);
Ok(bytes.len())
} else {
io::stderr().write(bytes)
}
}
fn flush(&mut self) -> io::Result<()> {
io::stderr().flush()
}
}
pub fn init() {
env_logger::Builder::from_env(env_logger::Env::default().filter_or("MBX_LOG", "info"))
.format_target(false)
.format_timestamp(None)
.target(env_logger::Target::Pipe(Box::new(Stderr)))
.init();
}
pub(crate) fn note(message: &str) {
let _ = writeln!(Stderr, "{message}");
}
pub(crate) struct Capture;
impl Capture {
pub(crate) fn start() -> Self {
*CAPTURE.lock().unwrap_or_else(|error| error.into_inner()) = Some(Vec::new());
Self
}
pub(crate) fn drain(&self) -> Vec<u8> {
CAPTURE
.lock()
.unwrap_or_else(|error| error.into_inner())
.as_mut()
.map(std::mem::take)
.unwrap_or_default()
}
}
impl Drop for Capture {
fn drop(&mut self) {
let mut capture = CAPTURE.lock().unwrap_or_else(|error| error.into_inner());
if let Some(bytes) = capture.take() {
let _ = io::stderr().write_all(&bytes);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn background_notes_and_logger_writes_share_the_screen_queue() {
let capture = Capture::start();
std::thread::spawn(|| {
note("background warning");
Stderr.write_all(b"logger warning\n").unwrap();
})
.join()
.unwrap();
let output = String::from_utf8(capture.drain()).unwrap();
assert!(output.contains("background warning"));
assert!(output.contains("logger warning"));
}
}