use std::{collections::HashMap, io::Result, path::PathBuf, process::Output};
use async_trait::async_trait;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Command {
pub args: Vec<String>,
pub cwd: Option<PathBuf>,
pub env: Option<HashMap<String, String>>,
#[cfg(unix)]
pub gid: Option<u32>,
pub program: String,
#[cfg(unix)]
pub uid: Option<u32>,
}
impl Command {
pub fn new<S: Into<String>>(program: S) -> Self {
Self {
args: vec![],
cwd: None,
env: None,
#[cfg(unix)]
gid: None,
program: program.into(),
#[cfg(unix)]
uid: None,
}
}
pub fn with_arg<S: Into<String>>(mut self, arg: S) -> Self {
self.args.push(arg.into());
self
}
pub fn with_args(mut self, args: Vec<String>) -> Self {
self.args = args;
self
}
pub fn with_cwd<P: Into<PathBuf>>(mut self, cwd: P) -> Self {
self.cwd = Some(cwd.into());
self
}
pub fn with_env<S: Into<String>>(mut self, key: S, val: S) -> Self {
match self.env {
Some(ref mut env) => {
env.insert(key.into(), val.into());
}
None => {
self.env = Some(HashMap::from_iter([(key.into(), val.into())]));
}
}
self
}
pub fn with_envs(mut self, env: HashMap<String, String>) -> Self {
self.env = Some(env);
self
}
#[cfg(unix)]
pub fn with_gid(mut self, gid: u32) -> Self {
self.gid = Some(gid);
self
}
#[cfg(unix)]
pub fn with_uid(mut self, uid: u32) -> Self {
self.uid = Some(uid);
self
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct CommandOutput {
pub code: Option<i32>,
pub stderr: Vec<u8>,
pub stdout: Vec<u8>,
}
impl From<Output> for CommandOutput {
fn from(output: Output) -> Self {
Self {
code: output.status.code(),
stderr: output.stderr,
stdout: output.stdout,
}
}
}
#[async_trait]
pub trait CommandRunner: Send + Sync {
async fn run(&self, cmd: &Command) -> Result<CommandOutput>;
}
pub struct DefaultCommandRunner;
#[async_trait]
impl CommandRunner for DefaultCommandRunner {
async fn run(&self, cmd: &Command) -> Result<CommandOutput> {
let mut builder = tokio::process::Command::new(&cmd.program);
builder.args(&cmd.args);
if let Some(cwd) = &cmd.cwd {
builder.current_dir(cwd);
}
if let Some(env) = &cmd.env {
builder.envs(env);
}
if cfg!(unix) {
if let Some(gid) = cmd.gid {
builder.gid(gid);
}
if let Some(uid) = cmd.uid {
builder.uid(uid);
}
}
let output = builder.output().await?;
Ok(output.into())
}
}
#[cfg(feature = "mock")]
mockall::mock! {
pub CommandRunner {}
#[async_trait]
impl CommandRunner for CommandRunner {
async fn run(&self, cmd: &Command) -> Result<CommandOutput>;
}
}