use std::{process, path::PathBuf, fmt::Display, fs}; #[derive(Debug, Clone)] pub struct CommandError { output: process::Output, log_path: PathBuf, } impl CommandError { pub fn new(output: process::Output, log_path: PathBuf) -> Self { Self { output, log_path } } } impl Display for CommandError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let log_path = fs::canonicalize(&self.log_path) .map(|p| p.to_str() .map(String::from) .unwrap_or(format!("[path not valid utf-8: {:?}]", p))) .unwrap_or_else(|e| format!("[invalid path; reason: {}]", e)); write!( f, "Command exited with non-zero exit code ({}). Logs can be viewed at {}\nExcerpt from stderr:\n----\n", self.output.status.code() .map(|n| i32::to_string(&n)) .unwrap_or("??".to_owned()), log_path )?; let buf: Vec<_> = self.output.stderr.rsplit(|b| *b == b'\n').take(10).collect(); for line in buf.into_iter().rev() { write!(f, "{}\n", String::from_utf8(line.to_vec()).unwrap_or(String::from("[LINE NOT VALID UTF-8]")))?; } write!(f, "----\n")?; Ok(()) } }