comlexr 1.5.0

Dynamically build Command objects with conditional expressions
Documentation
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::<Vec<_>>()
            .join(" "),
    )]
    FailedCommand {
        command: Box<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<dyn FnMut(Option<&'a S>) -> Result<Child, ExecutorError> + '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<Child, ExecutorError> + '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<ExitStatus, ExecutorError> {
        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<Output, ExecutorError> {
        Ok((self.piped_commands)(self.stdin)?.wait_with_output()?)
    }
}