1use std::{
11 ffi::OsStr,
12 fmt::{self, Debug, Display, Formatter},
13 io,
14 iter::once,
15 process::{Command, ExitStatus, Output},
16};
17
18pub struct EasyCommand {
20 inner: Command,
21}
22
23impl EasyCommand {
24 pub fn new<C>(cmd: C) -> Self
26 where
27 C: AsRef<OsStr>,
28 {
29 Self::new_with(cmd, |cmd| cmd)
30 }
31
32 pub fn new_with<C>(cmd: C, f: impl FnOnce(&mut Command) -> &mut Command) -> Self
34 where
35 C: AsRef<OsStr>,
36 {
37 let mut cmd = Command::new(cmd);
38 f(&mut cmd);
39 Self { inner: cmd }
40 }
41
42 pub fn simple<C, A, I>(cmd: C, args: I) -> Self
45 where
46 C: AsRef<OsStr>,
47 A: AsRef<OsStr>,
48 I: IntoIterator<Item = A>,
49 {
50 Self::new_with(cmd, |cmd| cmd.args(args))
51 }
52
53 fn spawn_and_wait_impl(&mut self) -> Result<ExitStatus, SpawnAndWaitErrorKind> {
54 log::debug!("spawning child process with {self}…");
55
56 self.inner
57 .spawn()
58 .map_err(|source| SpawnAndWaitErrorKind::Spawn { source })
59 .and_then(|mut child| {
60 log::trace!("waiting for exit from {self}…");
61 let status = child
62 .wait()
63 .map_err(|source| SpawnAndWaitErrorKind::WaitForExitCode { source })?;
64 log::debug!("received exit code {:?} from {self}", status.code());
65 Ok(status)
66 })
67 }
68
69 pub fn spawn_and_wait(&mut self) -> Result<ExitStatus, ExecuteError<SpawnAndWaitErrorKind>> {
74 self.spawn_and_wait_impl()
75 .map_err(|source| ExecuteError::new(self, source))
76 }
77
78 fn run_impl(&mut self) -> Result<(), RunErrorKind> {
79 let status = self.spawn_and_wait_impl()?;
80
81 if status.success() {
82 Ok(())
83 } else {
84 Err(RunErrorKind::UnsuccessfulExitCode {
85 code: status.code(),
86 })
87 }
88 }
89
90 pub fn run(&mut self) -> Result<(), ExecuteError<RunErrorKind>> {
95 self.run_impl()
96 .map_err(|source| ExecuteError::new(self, source))
97 }
98
99 fn output_impl(&mut self) -> Result<Output, io::Error> {
100 log::debug!("getting output from {self}…");
101 let output = self.inner.output()?;
102 log::debug!("received exit code {:?} from {self}", output.status.code());
103 Ok(output)
104 }
105
106 pub fn output(&mut self) -> Result<Output, ExecuteError<io::Error>> {
108 self.output_impl()
109 .map_err(|source| ExecuteError::new(self, source))
110 }
111}
112
113impl Debug for EasyCommand {
114 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
115 Debug::fmt(&self.inner, f)
116 }
117}
118
119impl Display for EasyCommand {
120 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
121 let Self { inner } = self;
122 let prog = inner.get_program().to_string_lossy();
123 let args = inner.get_args().map(|a| a.to_string_lossy());
124 let shell_words = ::shell_words::join(once(prog).chain(args));
125 write!(f, "`{shell_words}`")
126 }
127}
128
129#[derive(Debug)]
130struct EasyCommandInvocation {
131 shell_words: String,
132}
133
134impl EasyCommandInvocation {
135 fn new(cmd: &EasyCommand) -> Self {
136 let EasyCommand { inner } = cmd;
137 let prog = inner.get_program().to_string_lossy();
138 let args = inner.get_args().map(|a| a.to_string_lossy());
139 let shell_words = ::shell_words::join(once(prog).chain(args));
140 Self { shell_words }
141 }
142}
143
144impl Display for EasyCommandInvocation {
145 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
146 let Self { shell_words } = self;
147 write!(f, "{shell_words}")
148 }
149}
150
151#[derive(Debug, thiserror::Error)]
153#[error("failed to execute {cmd}")]
154pub struct ExecuteError<E> {
155 cmd: EasyCommandInvocation,
156 pub source: E,
157}
158
159impl<E> ExecuteError<E> {
160 fn new(cmd: &EasyCommand, source: E) -> Self {
161 Self {
162 cmd: EasyCommandInvocation::new(cmd),
163 source,
164 }
165 }
166}
167
168#[derive(Debug, thiserror::Error)]
170pub enum SpawnAndWaitErrorKind {
171 #[error("failed to spawn")]
172 Spawn { source: io::Error },
173 #[error("failed to wait for exit code")]
174 WaitForExitCode { source: io::Error },
175}
176
177#[derive(Debug, thiserror::Error)]
179pub enum RunErrorKind {
180 #[error(transparent)]
181 SpawnAndWait(#[from] SpawnAndWaitErrorKind),
182 #[error("returned exit code {code:?}")]
183 UnsuccessfulExitCode { code: Option<i32> },
184}