1use std::fmt::{self, Display, Formatter};
12use std::io;
13use std::process::{Child, Command, ExitStatus};
14use std::string::FromUtf8Error;
15
16#[derive(Debug)]
18pub enum RunCommandError {
19 Launch {
21 cmd: String,
23 err: io::Error,
25 },
26
27 Wait {
29 cmd: String,
31 err: io::Error,
33 },
34
35 NonZeroExit {
38 cmd: String,
40 status: ExitStatus,
42 },
43
44 NonUtf8 {
48 cmd: String,
50 err: FromUtf8Error,
52 },
53}
54
55impl Display for RunCommandError {
56 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
57 match self {
58 Self::Launch { cmd, .. } => {
59 write!(f, "failed to launch command \"{cmd}\"")
60 }
61 Self::Wait { cmd, .. } => {
62 write!(f, "failed to wait for command \"{cmd}\" to exit")
63 }
64 Self::NonZeroExit { cmd, status } => {
65 write!(f, "command \"{cmd}\" failed with {status}")
66 }
67 Self::NonUtf8 { cmd, .. } => {
68 write!(f, "command \"{cmd}\" output is not utf-8")
69 }
70 }
71 }
72}
73
74impl std::error::Error for RunCommandError {
75 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
76 match self {
77 Self::Launch { err, .. } => Some(err),
78 Self::Wait { err, .. } => Some(err),
79 Self::NonZeroExit { .. } => None,
80 Self::NonUtf8 { err, .. } => Some(err),
81 }
82 }
83}
84
85pub fn format_cmd(cmd: &Command) -> String {
87 format!("{cmd:?}").replace('"', "")
88}
89
90pub fn run_cmd(mut cmd: Command) -> Result<(), RunCommandError> {
95 let cmd_str = format_cmd(&cmd);
96 println!("Running: {}", cmd_str);
97 let status = cmd.status().map_err(|err| RunCommandError::Launch {
98 cmd: cmd_str.clone(),
99 err,
100 })?;
101 if status.success() {
102 Ok(())
103 } else {
104 Err(RunCommandError::NonZeroExit {
105 cmd: cmd_str,
106 status,
107 })
108 }
109}
110
111pub fn get_cmd_stdout(mut cmd: Command) -> Result<Vec<u8>, RunCommandError> {
116 let cmd_str = format_cmd(&cmd);
117 println!("Running: {}", cmd_str);
118 let output = cmd.output().map_err(|err| RunCommandError::Launch {
119 cmd: cmd_str.clone(),
120 err,
121 })?;
122 if output.status.success() {
123 Ok(output.stdout)
124 } else {
125 Err(RunCommandError::NonZeroExit {
126 cmd: cmd_str,
127 status: output.status,
128 })
129 }
130}
131
132pub fn get_cmd_stdout_utf8(cmd: Command) -> Result<String, RunCommandError> {
137 let cmd_str = format_cmd(&cmd);
138 let stdout = get_cmd_stdout(cmd)?;
139 String::from_utf8(stdout)
140 .map_err(|err| RunCommandError::NonUtf8 { cmd: cmd_str, err })
141}
142
143pub fn wait_for_child(
147 mut child: Child,
148 cmd_str: String,
149) -> Result<(), RunCommandError> {
150 let status = child.wait().map_err(|err| RunCommandError::Wait {
151 cmd: cmd_str.clone(),
152 err,
153 })?;
154 if status.success() {
155 Ok(())
156 } else {
157 Err(RunCommandError::NonZeroExit {
158 cmd: cmd_str,
159 status,
160 })
161 }
162}