1use std::ffi::OsString;
2use std::io;
3use std::path::PathBuf;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct Invocation {
16 program: OsString,
17 args: Vec<OsString>,
18 current_dir: Option<PathBuf>,
19 envs: Vec<(OsString, OsString)>,
20}
21
22impl Invocation {
23 pub fn new(program: impl Into<OsString>) -> Self {
25 Self {
26 program: program.into(),
27 args: Vec::new(),
28 current_dir: None,
29 envs: Vec::new(),
30 }
31 }
32
33 #[must_use]
35 pub fn arg(mut self, arg: impl Into<OsString>) -> Self {
36 self.args.push(arg.into());
37 self
38 }
39
40 #[must_use]
42 pub fn args<I, A>(mut self, args: I) -> Self
43 where
44 I: IntoIterator<Item = A>,
45 A: Into<OsString>,
46 {
47 self.args.extend(args.into_iter().map(Into::into));
48 self
49 }
50
51 #[must_use]
53 pub fn env(mut self, key: impl Into<OsString>, value: impl Into<OsString>) -> Self {
54 self.envs.push((key.into(), value.into()));
55 self
56 }
57
58 #[must_use]
60 pub fn current_dir(mut self, dir: impl Into<PathBuf>) -> Self {
61 self.current_dir = Some(dir.into());
62 self
63 }
64
65 pub fn program(&self) -> &OsString {
67 &self.program
68 }
69
70 pub fn arguments(&self) -> &[OsString] {
72 &self.args
73 }
74
75 pub fn envs(&self) -> &[(OsString, OsString)] {
77 &self.envs
78 }
79
80 pub fn working_dir(&self) -> Option<&PathBuf> {
82 self.current_dir.as_ref()
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Default)]
88pub struct Output {
89 pub status: Option<i32>,
91 pub stdout: Vec<u8>,
93 pub stderr: Vec<u8>,
95}
96
97impl Output {
98 pub fn success(&self) -> bool {
108 self.status == Some(0)
109 }
110}
111
112pub trait ProcessRunner: std::fmt::Debug + Send + Sync {
114 fn run(&self, invocation: &Invocation) -> io::Result<Output>;
122}
123
124#[cfg(test)]
125mod tests {
126 use super::Invocation;
127 use std::path::PathBuf;
128
129 #[test]
130 fn invocation_builder_keeps_order_and_dir() {
131 let inv = Invocation::new("uv")
132 .arg("lock")
133 .args(["--upgrade-package", "x"])
134 .env("UV_OFFLINE", "1")
135 .current_dir("/work");
136 assert_eq!(inv.arguments(), ["lock", "--upgrade-package", "x"]);
137 assert_eq!(inv.envs(), [("UV_OFFLINE".into(), "1".into())]);
138 assert_eq!(inv.working_dir(), Some(&PathBuf::from("/work")));
139 assert_eq!(Invocation::new("git").working_dir(), None);
140 }
141}