use std::io::Read;
use std::process::{Child, ExitStatus};
use std::sync::{Arc, Mutex};
use std::thread::JoinHandle;
use anyhow::Result;
fn spawn_drain(buf: Arc<Mutex<Vec<u8>>>, mut pipe: impl Read + Send + 'static) -> JoinHandle<()> {
std::thread::spawn(move || {
let mut chunk = Vec::new();
let _ = pipe.read_to_end(&mut chunk);
buf.lock().unwrap().extend(chunk);
})
}
pub struct OutputCapture {
buf: Arc<Mutex<Vec<u8>>>,
stdout_handle: Option<JoinHandle<()>>,
stderr_handle: Option<JoinHandle<()>>,
}
impl OutputCapture {
pub fn start(child: &mut Child) -> Self {
let buf: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
let stdout_handle = child.stdout.take().map(|p| spawn_drain(buf.clone(), p));
let stderr_handle = child.stderr.take().map(|p| spawn_drain(buf.clone(), p));
Self { buf, stdout_handle, stderr_handle }
}
pub fn finish(self) -> String {
if let Some(h) = self.stdout_handle {
let _ = h.join();
}
if let Some(h) = self.stderr_handle {
let _ = h.join();
}
String::from_utf8_lossy(&self.buf.lock().unwrap()).into_owned()
}
}
pub fn wait_capturing(mut child: Child) -> Result<(ExitStatus, String)> {
let capture = OutputCapture::start(&mut child);
let status = child.wait()?;
Ok((status, capture.finish()))
}
pub fn print_if_nonempty(output: &str) {
let trimmed = output.trim_end();
if !trimmed.is_empty() {
println!("{trimmed}");
}
}