1#[cfg(test)]
2mod tests {
3 use super::*;
4 #[test]
6 fn pipe() {
7 let command = Single::new("echo")
8 .a("foo\nbar\nbaz")
9 .pipe(Single::new("grep").a("bar"));
10 assert_eq!(command.run().unwrap().stdout(), "bar\n");
11 }
12}
13
14use std::collections::{HashMap, HashSet};
15use std::process::Stdio;
16use std::{fmt, io, io::Write, process};
17
18#[derive(Clone)]
20pub struct Output {
21 stderr: String,
22 stdout: String,
23 exit_code: i32,
24}
25
26impl Output {
27 pub fn success(&self) -> bool {
29 self.code() == 0
30 }
31 pub fn code(&self) -> i32 {
33 self.exit_code
34 }
35 pub fn stdout(&self) -> &str {
37 self.stdout.as_ref()
38 }
39 pub fn stderr(&self) -> &str {
41 self.stderr.as_ref()
42 }
43}
44
45pub trait Command: Sized + std::fmt::Debug + Clone {
46 fn and<C: Command>(self, other: C) -> And<Self, C> {
48 And {
49 first: self,
50 second: other,
51 }
52 }
53
54 fn or<C: Command>(self, other: C) -> Or<Self, C> {
56 Or {
57 first: self,
58 second: other,
59 }
60 }
61
62 fn then<C: Command>(self, other: C) -> Then<Self, C> {
64 Then {
65 first: self,
66 second: other,
67 }
68 }
69
70 fn pipe<C: Command>(self, other: C) -> Pipe<Self, C> {
72 Pipe {
73 first: self,
74 second: other,
75 }
76 }
77
78 fn env(self, key: &str, value: &str) -> Env<Self> {
80 Env {
81 key: key.to_owned(),
82 value: value.to_owned(),
83 on: self,
84 }
85 }
86
87 fn clear_envs(self) -> ClearEnv<Self> {
89 ClearEnv { on: self }
90 }
91
92 fn without_env(self, key: &str) -> ExceptEnv<Self> {
94 ExceptEnv {
95 key: key.to_owned(),
96 on: self,
97 }
98 }
99
100 fn without_envs<I: IntoIterator<Item = String>>(self, envs: I) -> ExceptEnvs<Self, I> {
102 ExceptEnvs {
103 on: self,
104 keys: envs,
105 }
106 }
107
108 fn with_dir<P: AsRef<std::path::Path>>(self, dir: P) -> Dir<Self> {
109 Dir {
110 on: self,
111 path: dir.as_ref().to_owned(),
112 }
113 }
114
115 fn run(&self) -> io::Result<Output> {
117 self.run_internal(None, false, HashMap::new(), HashSet::new(), None)
118 }
119
120 fn with_input(self, input: &str) -> Input<Self> {
122 Input {
123 input: input.to_owned(),
124 on: self,
125 }
126 }
127
128 fn run_internal(
133 &self,
134 input: Option<&str>,
135 clear_env: bool,
136 env: HashMap<String, String>,
137 del_env: HashSet<String>,
138 path: Option<std::path::PathBuf>,
139 ) -> io::Result<Output>;
140}
141
142#[derive(Clone)]
144pub struct Dir<C>
145where
146 C: Command,
147{
148 on: C,
149 path: std::path::PathBuf,
150}
151
152impl<C: Command> fmt::Debug for Dir<C> {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
154 write!(f, "cd {:?}; {:?}", self.path, self.on)
155 }
156}
157
158impl<C: Command> Command for Dir<C> {
159 fn run_internal(
160 &self,
161 input: std::option::Option<&str>,
162 clear_env: bool,
163 envs: std::collections::HashMap<std::string::String, std::string::String>,
164 del_envs: std::collections::HashSet<std::string::String>,
165 _: Option<std::path::PathBuf>,
166 ) -> std::result::Result<Output, std::io::Error> {
167 self.on
168 .run_internal(input, clear_env, envs, del_envs, Some(self.path.clone()))
169 }
170}
171
172#[derive(Clone)]
174pub struct ExceptEnvs<C, I>
175where
176 C: Command,
177 I: IntoIterator<Item = String>,
178{
179 on: C,
180 keys: I,
181}
182
183#[derive(Clone)]
185pub struct Input<F>
186where
187 F: Command,
188{
189 input: String,
190 on: F,
191}
192
193impl<F: Command> std::fmt::Debug for Input<F> {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
195 write!(f, "{:?} < \"{}\"", self.on, self.input)
196 }
197}
198
199impl<F: Command> Command for Input<F> {
200 fn run_internal(
201 &self,
202 input: std::option::Option<&str>,
203 clear_env: bool,
204 env: std::collections::HashMap<std::string::String, std::string::String>,
205 del_env: HashSet<String>,
206 path: Option<std::path::PathBuf>,
207 ) -> std::result::Result<Output, std::io::Error> {
208 let input_string = match input.as_ref() {
209 Some(prev) => prev.to_string() + &self.input,
210 None => self.input.to_owned(),
211 };
212 self.on
213 .run_internal(Some(&input_string), clear_env, env, del_env, path)
214 }
215}
216impl<F: Command> fmt::Debug for ClearEnv<F> {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
218 write!(f, "CLEAR_ENV \"{:?}\"", self.on)
219 }
220}
221impl<F: Command> Command for ClearEnv<F> {
222 fn run_internal(
223 &self,
224 input: std::option::Option<&str>,
225 _: bool,
226 env: std::collections::HashMap<std::string::String, std::string::String>,
227 del_env: HashSet<String>,
228 path: Option<std::path::PathBuf>,
229 ) -> std::result::Result<Output, std::io::Error> {
230 self.on.run_internal(input, true, env, del_env, path)
231 }
232}
233
234#[derive(Clone)]
236pub struct ClearEnv<F>
237where
238 F: Command,
239{
240 on: F,
241}
242
243#[derive(Clone)]
245pub struct ExceptEnv<F>
246where
247 F: Command,
248{
249 key: String,
250 on: F,
251}
252impl<F: Command> fmt::Debug for ExceptEnv<F> {
253 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
254 write!(f, "unset {} {:?}", self.key, self.on)
255 }
256}
257impl<F: Command> Command for ExceptEnv<F> {
258 fn run_internal(
259 &self,
260 input: Option<&str>,
261 clear_env: bool,
262 env: std::collections::HashMap<std::string::String, std::string::String>,
263 mut del_env: HashSet<String>,
264 path: Option<std::path::PathBuf>,
265 ) -> std::result::Result<Output, std::io::Error> {
266 del_env.insert(self.key.clone());
267 self.on.run_internal(input, clear_env, env, del_env, path)
268 }
269}
270
271#[derive(Clone)]
273pub struct Env<F>
274where
275 F: Command,
276{
277 key: String,
278 value: String,
279 on: F,
280}
281impl<F: Command> fmt::Debug for Env<F> {
282 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
283 write!(f, "{}={} {:?}", self.key, self.value, self.on)
284 }
285}
286impl<F: Command> Command for Env<F> {
287 fn run_internal(
288 &self,
289 input: Option<&str>,
290 clear_env: bool,
291 mut env: std::collections::HashMap<std::string::String, std::string::String>,
292 del_env: HashSet<String>,
293 path: Option<std::path::PathBuf>,
294 ) -> std::result::Result<Output, std::io::Error> {
295 env.insert(self.key.clone(), self.value.clone());
296 self.on.run_internal(input, clear_env, env, del_env, path)
297 }
298}
299
300#[derive(Clone)]
302pub struct Pipe<F, S>
303where
304 F: Command,
305 S: Command,
306{
307 first: F,
308 second: S,
309}
310
311impl<F: Command, S: Command> fmt::Debug for Pipe<F, S> {
312 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
313 write!(f, "{:?} | {:?}", self.first, self.second)
314 }
315}
316impl<F: Command, S: Command> Command for Pipe<F, S> {
317 fn run_internal(
318 &self,
319 input: std::option::Option<&str>,
320 clear_env: bool,
321 env: std::collections::HashMap<std::string::String, std::string::String>,
322 del_env: HashSet<String>,
323 path: Option<std::path::PathBuf>,
324 ) -> std::result::Result<Output, std::io::Error> {
325 let first = self.first.run_internal(
326 input,
327 clear_env,
328 env.clone(),
329 del_env.clone(),
330 path.clone(),
331 )?;
332 self.second
333 .run_internal(Some(&first.stdout), clear_env, env, del_env, path)
334 }
335}
336
337impl<F: Command, S: Command> fmt::Debug for Then<F, S> {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
339 write!(f, "{:?}; {:?}", self.first, self.second)
340 }
341}
342impl<F: Command, S: Command> Command for Then<F, S> {
343 fn run_internal(
344 &self,
345 input: std::option::Option<&str>,
346 clear_env: bool,
347 env: std::collections::HashMap<std::string::String, std::string::String>,
348 del_env: HashSet<String>,
349 path: Option<std::path::PathBuf>,
350 ) -> std::result::Result<Output, std::io::Error> {
351 self.first
352 .run_internal(input, clear_env, env.clone(), del_env.clone(), path.clone())?;
353 self.second
354 .run_internal(None, clear_env, env, del_env, path)
355 }
356}
357
358#[derive(Clone)]
360pub struct Then<F, S>
361where
362 F: Command,
363 S: Command,
364{
365 first: F,
366 second: S,
367}
368
369impl<F: Command, S: Command> fmt::Debug for And<F, S> {
370 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
371 write!(f, "{:?} && {:?}", self.first, self.second)
372 }
373}
374impl<F: Command, S: Command> Command for And<F, S> {
375 fn run_internal(
376 &self,
377 input: std::option::Option<&str>,
378 clear_env: bool,
379 env: std::collections::HashMap<std::string::String, std::string::String>,
380 del_env: HashSet<String>,
381 path: Option<std::path::PathBuf>,
382 ) -> std::result::Result<Output, std::io::Error> {
383 let first = self.first.run_internal(
384 input,
385 clear_env,
386 env.clone(),
387 del_env.clone(),
388 path.clone(),
389 )?;
390 if first.success() {
391 self.second
392 .run_internal(None, clear_env, env, del_env, path)
393 } else {
394 Ok(first)
395 }
396 }
397}
398
399#[derive(Clone)]
401pub struct And<F, S>
402where
403 F: Command,
404 S: Command,
405{
406 first: F,
407 second: S,
408}
409
410impl<F: Command, S: Command> fmt::Debug for Or<F, S> {
411 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
412 write!(f, "{:?} || {:?}", self.first, self.second)
413 }
414}
415impl<F: Command, S: Command> Command for Or<F, S> {
416 fn run_internal(
417 &self,
418 input: Option<&str>,
419 clear_env: bool,
420 env: HashMap<String, String>,
421 del_env: HashSet<String>,
422 path: Option<std::path::PathBuf>,
423 ) -> io::Result<Output> {
424 let first = self.first.run_internal(
425 input,
426 clear_env,
427 env.clone(),
428 del_env.clone(),
429 path.clone(),
430 )?;
431 if !first.success() {
432 self.second
433 .run_internal(None, clear_env, env, del_env, path)
434 } else {
435 Ok(first)
436 }
437 }
438}
439
440#[derive(Clone)]
442pub struct Or<F, S>
443where
444 F: Command,
445 S: Command,
446{
447 first: F,
448 second: S,
449}
450
451#[derive(Clone, PartialEq, Eq)]
453pub struct Single(Vec<String>);
454impl fmt::Debug for Single {
455 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
456 write!(
457 f,
458 "{}{}",
459 self.0.first().unwrap(),
460 self.0
461 .iter()
462 .skip(1)
463 .fold(String::new(), |old: String, next: &String| old + " " + next)
464 )
465 }
466}
467impl Command for Single {
468 fn run_internal(
469 &self,
470 input: Option<&str>,
471 do_clear_env: bool,
472 env: HashMap<String, String>,
473 del_env: HashSet<String>,
474 path: Option<std::path::PathBuf>,
475 ) -> Result<Output, std::io::Error> {
476 let f = self.0.first().unwrap();
477 let mut out = envs_remove(
478 clear_env(
479 with_path(
480 process::Command::new(f)
481 .args(self.0.iter().skip(1))
482 .stderr(Stdio::piped())
483 .stdin(Stdio::piped())
484 .stdout(Stdio::piped()),
485 path,
486 ),
487 do_clear_env,
488 )
489 .envs(env.iter()),
490 del_env.iter(),
491 )
492 .spawn()?;
493 if let Some(input) = input {
494 write!(
495 match out.stdin.as_mut() {
496 Some(i) => i,
497 None => return Err(io::Error::from(io::ErrorKind::BrokenPipe)),
498 },
499 "{}",
500 input
501 )?;
502 }
503 let output = out.wait_with_output()?;
504 Ok(Output {
505 stderr: String::from_utf8_lossy(&output.stderr).to_owned().into(),
506 stdout: String::from_utf8_lossy(&output.stdout).into(),
507 exit_code: output.status.code().unwrap_or(1),
508 })
509 }
510}
511
512impl Single {
513 pub fn new(command: &str) -> Self {
515 Self(vec![command.to_owned()])
516 }
517
518 pub fn arg<S>(mut self, argument: S) -> Self
520 where
521 S: AsRef<str>,
522 {
523 self.0.push(argument.as_ref().to_owned());
524 self
525 }
526
527 pub fn args<I, S>(self, arguments: I) -> Self
529 where
530 I: IntoIterator<Item = S>,
531 S: AsRef<str>,
532 {
533 arguments.into_iter().fold(self, |this, arg| this.arg(arg))
534 }
535}
536
537fn envs_remove<I, K>(command: &mut process::Command, keys: I) -> &mut process::Command
538where
539 I: IntoIterator<Item = K>,
540 K: AsRef<std::ffi::OsStr>,
541{
542 let mut iter = keys.into_iter();
543 match iter.next() {
544 Some(k) => envs_remove(command.env_remove(k), iter),
545 None => command,
546 }
547}
548
549fn clear_env(command: &mut process::Command, clear: bool) -> &mut process::Command {
550 match clear {
551 true => command.env_clear(),
552 false => command,
553 }
554}
555
556fn with_path(
557 command: &mut process::Command,
558 path: Option<std::path::PathBuf>,
559) -> &mut process::Command {
560 match path {
561 Some(p) => command.current_dir(p),
562 None => command,
563 }
564}