use std::process::{Child, Command, ExitStatus, Output};
use anyhow::{Context, Result, anyhow};
pub trait CommandExt {
fn command_success(&mut self) -> bool;
fn command_run(&mut self) -> Result<()>;
fn command_output(&mut self) -> Result<Output>;
fn command_status(&mut self) -> Result<ExitStatus>;
fn command_stdout(&mut self) -> Option<String>;
fn command_stderr(&mut self) -> Option<String>;
fn command_spawn(&mut self) -> Result<Child>;
fn command_line(&self) -> String;
}
impl CommandExt for Command {
fn command_success(&mut self) -> bool {
self.output().is_ok_and(|output| output.status.success())
}
fn command_run(&mut self) -> Result<()> {
let output = self.command_output()?;
if output.status.success() {
return Ok(());
}
Err(failure(self, &output))
}
fn command_output(&mut self) -> Result<Output> {
let program = program(self);
self.output()
.with_context(|| format!("执行 {program} 失败,请确认该命令存在且可用"))
}
fn command_status(&mut self) -> Result<ExitStatus> {
let program = program(self);
self.status()
.with_context(|| format!("执行 {program} 失败,请确认该命令存在且可用"))
}
fn command_stdout(&mut self) -> Option<String> {
Some(text(&self.command_output().ok()?.stdout))
}
fn command_stderr(&mut self) -> Option<String> {
Some(text(&self.command_output().ok()?.stderr))
}
fn command_spawn(&mut self) -> Result<Child> {
let program = program(self);
self.spawn()
.with_context(|| format!("启动 {program} 失败,请确认该命令存在且可用"))
}
fn command_line(&self) -> String {
let mut line = program(self);
for arg in self.get_args() {
line.push(' ');
line.push_str(&arg.to_string_lossy());
}
line
}
}
fn program(command: &Command) -> String {
command.get_program().to_string_lossy().into_owned()
}
fn failure(command: &Command, output: &Output) -> anyhow::Error {
let code = output
.status
.code()
.map_or_else(|| "未知".to_string(), |code| code.to_string());
anyhow!(
"{} 执行失败\n 命令:{}\n 退出码:{code}\n 标准输出:{}\n 标准错误:{}",
program(command),
command.command_line(),
output_text(&output.stdout),
output_text(&output.stderr),
)
}
fn text(bytes: &[u8]) -> String {
String::from_utf8_lossy(bytes).into_owned()
}
fn output_text(bytes: &[u8]) -> String {
let text = text(bytes);
let text = text.trim();
if text.is_empty() {
"(空)".to_string()
} else {
text.to_string()
}
}