use anyhow::{bail, Error};
use std::ffi::{OsStr, OsString};
use std::fmt;
use std::io;
use std::path::{Path, PathBuf};
use std::process;
use thiserror::Error;
pub struct Output {
pub status: process::ExitStatus,
pub stdout: String,
pub stderr: String,
}
impl Output {
pub fn into_error(self) -> OutputError {
OutputError {
status: self.status,
stdout: self.stdout,
stderr: self.stderr,
}
}
}
#[derive(Debug, Error)]
pub struct OutputError {
pub status: process::ExitStatus,
pub stdout: String,
pub stderr: String,
}
impl fmt::Display for OutputError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
writeln!(fmt, "process exited with status: {}", self.status)?;
if !self.stdout.is_empty() {
writeln!(fmt, "stdout:")?;
self.stdout.fmt(fmt)?;
}
if !self.stderr.is_empty() {
writeln!(fmt, "stderr:")?;
self.stderr.fmt(fmt)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct Command {
pub(crate) name: PathBuf,
pub(crate) working_directory: Option<PathBuf>,
pub(crate) args: Vec<OsString>,
}
impl Command {
pub fn new(name: impl Into<PathBuf>) -> Command {
Command {
name: name.into(),
working_directory: None,
args: Vec::new(),
}
}
pub fn arg<A>(&mut self, arg: A)
where
A: AsRef<OsStr>,
{
self.args.push(arg.as_ref().to_owned());
}
pub fn args<I>(&mut self, args: I)
where
I: IntoIterator,
I::Item: AsRef<OsStr>,
{
self.args
.extend(args.into_iter().map(|arg| arg.as_ref().to_owned()));
}
fn command(&self) -> process::Command {
let mut cmd = process::Command::new(self.name.as_os_str());
cmd.args(&self.args);
if let Some(working_directory) = self.working_directory.as_ref() {
cmd.current_dir(working_directory);
}
cmd
}
pub fn working_directory(&mut self, path: impl AsRef<Path>) {
self.working_directory = Some(path.as_ref().to_owned());
}
pub fn run_lines(self) -> Result<Vec<String>, Error> {
let lines = self
.run_stdout()?
.split('\n')
.map(|s| s.to_string())
.collect();
Ok(lines)
}
pub fn run_stdout(self) -> Result<String, Error> {
let output = self.run()?;
if !output.status.success() {
return Err(Error::from(output.into_error()));
}
Ok(output.stdout)
}
pub fn run_checked(self) -> Result<(), Error> {
let output = self.run()?;
if !output.status.success() {
return Err(Error::from(output.into_error()));
}
Ok(())
}
pub fn run_inherited(&self) -> Result<(), Error> {
let mut cmd = self.command();
let status = cmd.status()?;
if !status.success() {
bail!(
"Command exited with non-zero status: {:?}: {:?}",
cmd,
status
);
}
Ok(())
}
pub fn run(self) -> io::Result<Output> {
let output = self.command().output()?;
let output = Output {
status: output.status,
stdout: String::from_utf8(output.stdout).map_err(|_| {
io::Error::new(io::ErrorKind::Other, "Cannot decode stdout as utf-8")
})?,
stderr: String::from_utf8(output.stderr).map_err(|_| {
io::Error::new(io::ErrorKind::Other, "Cannot decode stderr as utf-8")
})?,
};
Ok(output)
}
pub fn status(self) -> io::Result<process::ExitStatus> {
self.command().status()
}
#[cfg(windows)]
pub fn runas(self) -> io::Result<i32> {
crate::ffi::win::shellapi::runas(self)
}
}