use std::fmt::Debug;
use std::fmt::Display;
use std::process::Child;
use std::process::ExitStatus;
use std::process::{Command, Output};
use utf8_command::Utf8Output;
use crate::ChildContext;
use crate::Error;
use crate::ExecError;
use crate::OutputContext;
use crate::OutputConversionError;
use crate::OutputLike;
use crate::Utf8ProgramAndArgs;
pub trait CommandExt: Sized {
type Error: From<Error> + Send + Sync;
type Child;
#[track_caller]
fn output_checked_as<O, R, E>(
&mut self,
succeeded: impl Fn(OutputContext<O>) -> Result<R, E>,
) -> Result<R, E>
where
O: Debug + OutputLike + TryFrom<Output> + Send + Sync + 'static,
<O as TryFrom<Output>>::Error: Display + Send + Sync,
E: From<Self::Error> + Send + Sync;
#[track_caller]
fn output_checked_with<O, E>(
&mut self,
succeeded: impl Fn(&O) -> Result<(), Option<E>>,
) -> Result<O, Self::Error>
where
O: Debug + OutputLike + TryFrom<Output> + Send + Sync + 'static,
<O as TryFrom<Output>>::Error: Display + Send + Sync,
E: Debug + Display + Send + Sync + 'static,
{
self.output_checked_as(|context| match succeeded(context.output()) {
Ok(()) => Ok(context.into_output()),
Err(user_error) => Err(context.maybe_error_msg(user_error).into()),
})
}
#[track_caller]
fn output_checked(&mut self) -> Result<Output, Self::Error> {
self.output_checked_with(|output: &Output| {
if output.status.success() {
Ok(())
} else {
Err(None::<String>)
}
})
}
#[track_caller]
fn output_checked_utf8(&mut self) -> Result<Utf8Output, Self::Error> {
self.output_checked_with_utf8(|output| {
if output.status.success() {
Ok(())
} else {
Err(None::<String>)
}
})
}
#[track_caller]
fn output_checked_with_utf8<E>(
&mut self,
succeeded: impl Fn(&Utf8Output) -> Result<(), Option<E>>,
) -> Result<Utf8Output, Self::Error>
where
E: Display + Debug + Send + Sync + 'static,
{
self.output_checked_with(succeeded)
}
#[track_caller]
fn status_checked_as<R, E>(
&mut self,
succeeded: impl Fn(OutputContext<ExitStatus>) -> Result<R, E>,
) -> Result<R, E>
where
E: From<Self::Error>;
#[track_caller]
fn status_checked_with<E>(
&mut self,
succeeded: impl Fn(ExitStatus) -> Result<(), Option<E>>,
) -> Result<ExitStatus, Self::Error>
where
E: Debug + Display + Send + Sync + 'static,
{
self.status_checked_as(|status| match succeeded(status.status()) {
Ok(()) => Ok(status.status()),
Err(user_error) => Err(status.maybe_error_msg(user_error).into()),
})
}
#[track_caller]
fn status_checked(&mut self) -> Result<ExitStatus, Self::Error> {
self.status_checked_with(|status| {
if status.success() {
Ok(())
} else {
Err(None::<String>)
}
})
}
#[track_caller]
fn spawn_checked(&mut self) -> Result<Self::Child, Self::Error>;
fn log(&self) -> Result<(), Self::Error>;
}
impl CommandExt for Command {
type Error = Error;
type Child = ChildContext<Child>;
fn log(&self) -> Result<(), Self::Error> {
#[cfg(feature = "tracing")]
{
let command: Utf8ProgramAndArgs = self.into();
tracing::debug!(%command, "Executing command");
}
Ok(())
}
fn output_checked_as<O, R, E>(
&mut self,
succeeded: impl Fn(OutputContext<O>) -> Result<R, E>,
) -> Result<R, E>
where
O: Debug + OutputLike + TryFrom<Output> + Send + Sync + 'static,
<O as TryFrom<Output>>::Error: Display + Send + Sync,
E: From<Self::Error> + Send + Sync,
{
self.log()?;
let displayed: Utf8ProgramAndArgs = (&*self).into();
match self.output() {
Ok(output) => match output.try_into() {
Ok(output) => succeeded(OutputContext::new(output, Box::new(displayed))),
Err(error) => Err(Error::from(OutputConversionError::new(
Box::new(displayed),
Box::new(error),
))
.into()),
},
Err(inner) => Err(Error::from(ExecError::new(Box::new(displayed), inner)).into()),
}
}
fn status_checked_as<R, E>(
&mut self,
succeeded: impl Fn(OutputContext<ExitStatus>) -> Result<R, E>,
) -> Result<R, E>
where
E: From<Self::Error>,
{
self.log()?;
let displayed: Utf8ProgramAndArgs = (&*self).into();
let displayed = Box::new(displayed);
match self.status() {
Ok(status) => succeeded(OutputContext::new(status, displayed)),
Err(inner) => Err(Error::from(ExecError::new(displayed, inner)).into()),
}
}
fn spawn_checked(&mut self) -> Result<Self::Child, Self::Error> {
let displayed: Utf8ProgramAndArgs = (&*self).into();
match self.spawn() {
Ok(child) => Ok(ChildContext::new(child, Box::new(displayed))),
Err(inner) => Err(Error::from(ExecError::new(Box::new(displayed), inner))),
}
}
}