use std::ffi::OsString;
use std::io;
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Invocation {
program: OsString,
args: Vec<OsString>,
current_dir: Option<PathBuf>,
envs: Vec<(OsString, OsString)>,
}
impl Invocation {
pub fn new(program: impl Into<OsString>) -> Self {
Self {
program: program.into(),
args: Vec::new(),
current_dir: None,
envs: Vec::new(),
}
}
#[must_use]
pub fn arg(mut self, arg: impl Into<OsString>) -> Self {
self.args.push(arg.into());
self
}
#[must_use]
pub fn args<I, A>(mut self, args: I) -> Self
where
I: IntoIterator<Item = A>,
A: Into<OsString>,
{
self.args.extend(args.into_iter().map(Into::into));
self
}
#[must_use]
pub fn env(mut self, key: impl Into<OsString>, value: impl Into<OsString>) -> Self {
self.envs.push((key.into(), value.into()));
self
}
#[must_use]
pub fn current_dir(mut self, dir: impl Into<PathBuf>) -> Self {
self.current_dir = Some(dir.into());
self
}
pub fn program(&self) -> &OsString {
&self.program
}
pub fn arguments(&self) -> &[OsString] {
&self.args
}
pub fn envs(&self) -> &[(OsString, OsString)] {
&self.envs
}
pub fn working_dir(&self) -> Option<&PathBuf> {
self.current_dir.as_ref()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Output {
pub status: Option<i32>,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
}
impl Output {
pub fn success(&self) -> bool {
self.status == Some(0)
}
}
pub trait ProcessRunner: std::fmt::Debug + Send + Sync {
fn run(&self, invocation: &Invocation) -> io::Result<Output>;
}
#[cfg(test)]
mod tests {
use super::Invocation;
use std::path::PathBuf;
#[test]
fn invocation_builder_keeps_order_and_dir() {
let inv = Invocation::new("uv")
.arg("lock")
.args(["--upgrade-package", "x"])
.env("UV_OFFLINE", "1")
.current_dir("/work");
assert_eq!(inv.arguments(), ["lock", "--upgrade-package", "x"]);
assert_eq!(inv.envs(), [("UV_OFFLINE".into(), "1".into())]);
assert_eq!(inv.working_dir(), Some(&PathBuf::from("/work")));
assert_eq!(Invocation::new("git").working_dir(), None);
}
}