use std::{ ffi::OsStr, process::Command, process::{Child, ExitStatus, Output}, }; use thiserror::Error; /// Errors that can be created when running the `PipeExecutor` #[derive(Debug, Error)] pub enum ExecutorError { /// IO Error #[error(transparent)] Io(#[from] std::io::Error), /// Failed Command Error #[error("Failed to run command '{} {}', exit code {exit_code}", .command .get_program() .to_string_lossy(), .command .get_args() .map(OsStr::to_string_lossy) .collect::>() .join(" "), )] FailedCommand { command: Command, exit_code: i32 }, /// No stdin Error #[error("Unable to get mutable stdin")] NoStdIn, } /// A lazy piped command executor. pub struct Executor<'a, S> where S: AsRef<[u8]> + ?Sized, { stdin: Option<&'a S>, piped_commands: Box) -> Result + 'a>, } impl<'a, S> Executor<'a, S> where S: AsRef<[u8]> + ?Sized, { /// Construct a `PipeExecutor`. pub fn new( stdin: Option<&'a S>, piped_commands: impl FnMut(Option<&'a S>) -> Result + 'a, ) -> Self { Self { stdin, piped_commands: Box::new(piped_commands), } } /// Retrieves the `ExitStatus` of the last command. /// /// # Errors /// Will error if the exit status of any chained commands fail. pub fn status(&mut self) -> Result { Ok((self.piped_commands)(self.stdin)?.wait()?) } /// Retrieves the `Output` of the last command. /// /// # Errors /// Will error if the exit status of any chained commands fail. pub fn output(&mut self) -> Result { Ok((self.piped_commands)(self.stdin)?.wait_with_output()?) } }