1use std::{borrow::Cow, collections::HashMap, fmt, path::PathBuf, process::Stdio};
4
5#[derive(Default)]
14pub struct Command {
15 program: String,
19
20 args: Option<Vec<String>>,
24
25 pub envs: Option<HashMap<String, String>>,
29
30 pub current_dir: Option<PathBuf>,
34
35 pub stdin: Option<Stdio>,
40
41 pub stdout: Option<Stdio>,
46
47 pub stderr: Option<Stdio>,
52
53 #[cfg(feature = "expand")]
60 pub expand: bool,
61}
62
63impl fmt::Debug for Command {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 let mut debug = f.debug_struct("Command");
66
67 debug.field("program", &self.program);
68
69 if let Some(args) = &self.args {
70 debug.field("args", args);
71 }
72
73 if let Some(envs) = &self.envs {
74 debug.field("envs", &envs.keys());
75 }
76
77 if let Some(dir) = &self.current_dir {
78 debug.field("current_dir", &dir);
79 }
80
81 if let Some(stdin) = &self.stdin {
82 debug.field("stdin", &stdin);
83 }
84
85 if let Some(stdout) = &self.stdout {
86 debug.field("stdout", &stdout);
87 }
88
89 if let Some(stderr) = &self.stderr {
90 debug.field("stderr", &stderr);
91 }
92
93 #[cfg(feature = "expand")]
94 debug.field("expand", &self.expand);
95
96 debug.finish()
97 }
98}
99
100impl Command {
101 pub fn new<S: ToString>(program: S) -> Self {
107 Self {
108 program: program.to_string(),
109 args: None,
110 envs: None,
111 current_dir: None,
112 stdin: None,
113 stdout: None,
114 stderr: None,
115 #[cfg(feature = "expand")]
116 expand: false,
117 }
118 }
119
120 #[cfg(feature = "expand")]
121 pub fn expand<'a>(&self, input: &'a str) -> Cow<'a, str> {
122 let home_dir = || dirs::home_dir().map(|p| p.to_string_lossy().to_string());
123 let get_env = |key: &str| -> Result<Option<Cow<str>>, ()> {
124 if let Some(envs) = &self.envs {
125 if let Some(val) = envs.get(key) {
126 return Ok(Some(val.into()));
127 }
128 }
129
130 match std::env::var(key) {
131 Ok(val) => Ok(Some(val.into())),
132 Err(_) => Ok(None),
133 }
134 };
135
136 if let Ok(input) = shellexpand::full_with_context(input, home_dir, get_env) {
137 return input;
138 }
139
140 input.into()
141 }
142
143 pub fn get_program(&self) -> Cow<'_, str> {
149 #[cfg(feature = "expand")]
150 if self.expand {
151 return self.expand(&self.program);
152 }
153
154 Cow::from(&self.program)
155 }
156
157 pub fn arg<S: ToString>(&mut self, arg: S) -> &mut Self {
161 match &mut self.args {
162 Some(args) => {
163 args.push(arg.to_string());
164 }
165 None => {
166 self.args = Some(vec![arg.to_string()]);
167 }
168 }
169 self
170 }
171
172 pub fn args<I, S>(&mut self, args: I) -> &mut Self
176 where
177 I: IntoIterator<Item = S>,
178 S: ToString,
179 {
180 for arg in args {
181 self.arg(arg);
182 }
183 self
184 }
185
186 pub fn get_args(&self) -> Option<Vec<Cow<'_, str>>> {
192 let self_args = self.args.as_ref()?;
193 let mut args = Vec::with_capacity(self_args.len());
194
195 for self_arg in self_args {
196 #[cfg(feature = "expand")]
197 if self.expand {
198 args.push(self.expand(self_arg));
199 continue;
200 }
201
202 args.push(self_arg.into());
203 }
204
205 Some(args)
206 }
207
208 pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
212 where
213 K: ToString,
214 V: ToString,
215 {
216 let key = key.to_string();
217 let val = val.to_string();
218
219 match &mut self.envs {
220 Some(envs) => {
221 envs.insert(key, val);
222 }
223 None => {
224 self.envs = Some(HashMap::from_iter(Some((key, val))));
225 }
226 }
227 self
228 }
229
230 pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
235 where
236 I: IntoIterator<Item = (K, V)>,
237 K: ToString,
238 V: ToString,
239 {
240 for (key, val) in vars {
241 self.env(key, val);
242 }
243 self
244 }
245
246 pub fn env_remove<K: AsRef<str>>(&mut self, key: K) -> &mut Self {
251 if let Some(envs) = &mut self.envs {
252 envs.remove(key.as_ref());
253 }
254 self
255 }
256
257 pub fn env_clear(&mut self) -> &mut Self {
262 if let Some(envs) = &mut self.envs {
263 envs.clear();
264 }
265 self.envs = None;
266 self
267 }
268
269 pub fn current_dir<P: Into<PathBuf>>(&mut self, dir: P) -> &mut Self {
273 self.current_dir = Some(dir.into());
274 self
275 }
276
277 pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command {
282 self.stdin = Some(cfg.into());
283 self
284 }
285
286 pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command {
291 self.stdout = Some(cfg.into());
292 self
293 }
294
295 pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command {
300 self.stderr = Some(cfg.into());
301 self
302 }
303}
304
305impl Clone for Command {
306 fn clone(&self) -> Self {
307 let mut command = Command::new(&self.program);
308
309 #[cfg(feature = "expand")]
310 {
311 command.expand = self.expand;
312 }
313
314 if let Some(args) = self.args.as_ref() {
315 for arg in args {
316 command.arg(arg);
317 }
318 }
319
320 if let Some(envs) = self.envs.as_ref() {
321 for (key, val) in envs {
322 command.env(key, val);
323 }
324 }
325
326 if let Some(dir) = self.current_dir.as_ref() {
327 command.current_dir(dir);
328 }
329
330 command
331 }
332}
333
334impl Eq for Command {}
335
336impl PartialEq for Command {
337 fn eq(&self, other: &Self) -> bool {
338 if self.program != other.program {
339 return false;
340 }
341
342 if self.args != other.args {
343 return false;
344 }
345
346 if self.current_dir != other.current_dir {
347 return false;
348 }
349
350 #[cfg(feature = "expand")]
351 if self.expand != other.expand {
352 return false;
353 }
354
355 true
356 }
357}