1use std::{
2 path::PathBuf,
3 process::{self, Stdio},
4};
5
6pub struct Command {
7 bin: String,
8 args: Vec<String>,
9 error_message: String,
10 dry_run: bool,
11 current_dir: Option<PathBuf>,
12}
13
14impl Command {
15 pub fn program<S: AsRef<str>>(bin: S) -> Self {
16 Self {
17 bin: bin.as_ref().to_owned(),
18 args: vec![],
19 error_message: String::new(),
20 dry_run: false,
21 current_dir: None,
22 }
23 }
24
25 pub fn args<S: AsRef<str>, V: AsRef<[S]>>(mut self, args: V) -> Self {
26 self.args
27 .extend(args.as_ref().iter().map(|s| s.as_ref().to_string()));
28 self
29 }
30
31 pub fn error_message<S: AsRef<str>>(mut self, message: S) -> Self {
32 message.as_ref().clone_into(&mut self.error_message);
33 self
34 }
35
36 pub fn dry_run(mut self, dry_run: bool) -> Self {
37 self.dry_run = dry_run;
38 self
39 }
40
41 pub fn current_dir(mut self, current_dir: PathBuf) -> Self {
42 self.current_dir = Some(current_dir);
43 self
44 }
45
46 pub fn execute(self) {
47 let mut cmd = process::Command::new(self.bin);
48 cmd.args(&self.args)
49 .stderr(Stdio::inherit())
50 .stdout(Stdio::inherit());
51 if let Some(current_dir) = self.current_dir {
52 cmd.current_dir(¤t_dir);
53 }
54 if self.dry_run {
55 println!("{cmd:?}");
56 return;
57 }
58 let status = cmd.status();
59 assert!(
60 {
61 if self.error_message.is_empty() {
62 status.unwrap()
63 } else {
64 status.expect(&self.error_message)
65 }
66 }
67 .success()
68 );
69 }
70
71 pub fn output_string(self) -> String {
72 let mut cmd = process::Command::new(self.bin);
73 cmd.args(&self.args).stderr(Stdio::inherit());
74 if let Some(current_dir) = self.current_dir {
75 cmd.current_dir(¤t_dir);
76 }
77 let output = cmd.output();
78 String::from_utf8({
79 if self.error_message.is_empty() {
80 output.unwrap().stdout
81 } else {
82 output.expect(&self.error_message).stdout
83 }
84 })
85 .expect("Stdout contains non UTF-8 characters")
86 }
87}