use crate::command::Command;
use crate::error::Result;
use crate::group::ProcessGroup;
use crate::result::ProcessResult;
use crate::running::{RunningProcess, Spawned};
#[cfg_attr(feature = "mock", mockall::automock)]
#[async_trait::async_trait]
pub trait ProcessRunner: Send + Sync {
async fn output(&self, command: &Command) -> Result<ProcessResult<String>>;
}
#[async_trait::async_trait]
impl<R: ProcessRunner + ?Sized> ProcessRunner for &R {
async fn output(&self, command: &Command) -> Result<ProcessResult<String>> {
(**self).output(command).await
}
}
#[async_trait::async_trait]
pub trait ProcessRunnerExt: ProcessRunner {
async fn run(&self, command: &Command) -> Result<String> {
let result = self.output(command).await?.ensure_success()?;
Ok(result.into_stdout().trim_end().to_owned())
}
async fn exit_code(&self, command: &Command) -> Result<i32> {
self.output(command).await?.require_code()
}
async fn checked(&self, command: &Command) -> Result<ProcessResult<String>> {
self.output(command).await?.ensure_success()
}
}
#[async_trait::async_trait]
impl<T: ProcessRunner + ?Sized> ProcessRunnerExt for T {}
#[derive(Debug, Default, Clone)]
pub struct JobRunner;
impl JobRunner {
pub fn new() -> Self {
Self
}
pub async fn start(&self, command: &Command) -> Result<RunningProcess> {
let group = ProcessGroup::new()?;
let mut process = launch(&group, command).await?;
process.attach_group(group);
Ok(process)
}
}
#[async_trait::async_trait]
impl ProcessRunner for JobRunner {
async fn output(&self, command: &Command) -> Result<ProcessResult<String>> {
self.start(command).await?.output_string().await
}
}
impl ProcessGroup {
pub async fn start(&self, command: &Command) -> Result<RunningProcess> {
launch(self, command).await
}
}
#[async_trait::async_trait]
impl ProcessRunner for ProcessGroup {
async fn output(&self, command: &Command) -> Result<ProcessResult<String>> {
self.start(command).await?.output_string().await
}
}
pub(crate) async fn launch(group: &ProcessGroup, command: &Command) -> Result<RunningProcess> {
let mut tokio_cmd = command.build_tokio();
let mut child = group.spawn(&mut tokio_cmd)?;
let pid = child.id();
let (stdin_pipe, stdin_task) = if command.keeps_stdin_open() {
(child.stdin.take(), None)
} else {
match command.stdin_source() {
Some(source) if !source.is_empty() => {
let task = child.stdin.take().map(|mut sink| {
let source = source.clone();
tokio::spawn(async move {
let result = source.write_to(&mut sink).await;
drop(sink);
result
})
});
(None, task)
}
_ => (None, None),
}
};
let stdout = child.stdout.take();
let stderr = child.stderr.take();
Ok(RunningProcess::from_spawned(Spawned {
program: command.program_name(),
child,
own_group: None,
stdout,
stderr,
stdin: stdin_pipe,
stdin_task,
timeout: command.configured_timeout(),
pid,
stdout_encoding: command.out_encoding(),
stderr_encoding: command.err_encoding(),
stdout_handler: command.stdout_handler(),
stderr_handler: command.stderr_handler(),
buffer: command.output_buffer_policy(),
}))
}