hypershell-tokio-components 0.1.0

Modular DSL for shellscripting in Rust
Documentation
use core::fmt::Debug;
use core::marker::PhantomData;
use std::process::Output;

use cgp::extra::handler::{CanHandle, Handler, HandlerComponent};
use cgp::prelude::*;
use hypershell_components::dsl::SimpleExec;
use tokio::io::AsyncWriteExt;
use tokio::process::Child;

use crate::dsl::CoreExec;

pub struct ExecOutputError {
    pub output: Output,
}

#[cgp_new_provider]
impl<Context, CommandPath, Args, Input> Handler<Context, SimpleExec<CommandPath, Args>, Input>
    for HandleSimpleExec
where
    Context: CanHandle<CoreExec<CommandPath, Args>, (), Output = Child>
        + for<'a> CanRaiseAsyncError<ExecOutputError>
        + CanWrapAsyncError<StdinPipeError>
        + CanWrapAsyncError<WaitWithOutputError>
        + CanRaiseAsyncError<std::io::Error>,
    CommandPath: Send,
    Args: Send,
    Input: Send + AsRef<[u8]>,
{
    type Output = Vec<u8>;

    async fn handle(
        context: &Context,
        _tag: PhantomData<SimpleExec<CommandPath, Args>>,
        input: Input,
    ) -> Result<Vec<u8>, Context::Error> {
        let mut child = context.handle(PhantomData, ()).await?;

        let input_bytes = input.as_ref();

        if !input_bytes.is_empty() {
            if let Some(mut stdin) = child.stdin.take() {
                stdin
                    .write_all(&input_bytes)
                    .await
                    .map_err(Context::raise_error)
                    .map_err(|e| Context::wrap_error(e, StdinPipeError))?;
            }
        }

        let output = child
            .wait_with_output()
            .await
            .map_err(Context::raise_error)
            .map_err(|e| Context::wrap_error(e, WaitWithOutputError))?;

        if output.status.success() {
            Ok(output.stdout)
        } else {
            Err(Context::raise_error(ExecOutputError { output }))
        }
    }
}

pub struct StdinPipeError;

impl Debug for StdinPipeError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "error piping input to stdin of child process")
    }
}

pub struct WaitWithOutputError;

impl Debug for WaitWithOutputError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "error waiting for output from child process")
    }
}

impl Debug for ExecOutputError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(
            f,
            "child process exited with non-success code {:?}, stderr: {}",
            self.output.status.code(),
            String::from_utf8_lossy(&self.output.stderr),
        )
    }
}