1use log::{debug, error};
17use std::{
18 env, io,
19 ops::{Deref, DerefMut},
20 process::Stdio,
21 result,
22 string::FromUtf8Error,
23};
24use thiserror::Error;
25use tokio::{
26 io::{AsyncReadExt, AsyncWriteExt},
27 process::Command,
28};
29
30#[derive(Debug, Error)]
32pub enum Error {
33 #[error("cannot run command: {1}")]
34 SpawnProcessError(#[source] io::Error, String),
35 #[error("cannot get standard input")]
36 GetStdinError,
37 #[error("cannot wait for exit status code of command: {1}")]
38 WaitForExitStatusCodeError(#[source] io::Error, String),
39 #[error("cannot get exit status code of command: {0}")]
40 GetExitStatusCodeNotAvailableError(String),
41 #[error("command {0} returned non-zero exit status code {1}: {2}")]
42 InvalidExitStatusCodeNonZeroError(String, i32, String),
43 #[error("cannot write data to standard input")]
44 WriteStdinError(#[source] io::Error),
45 #[error("cannot get standard output")]
46 GetStdoutError,
47 #[error("cannot read data from standard output")]
48 ReadStdoutError(#[source] io::Error),
49 #[error("cannot get standard error")]
50 GetStderrError,
51 #[error("cannot read data from standard error")]
52 ReadStderrError(#[source] io::Error),
53 #[error("cannot get command output")]
54 GetOutputError(#[source] io::Error),
55 #[error("cannot parse command output as string")]
56 ParseOutputAsUtf8StringError(#[source] FromUtf8Error),
57}
58
59pub type Result<T> = result::Result<T, Error>;
61
62#[derive(Clone, Debug, Eq, PartialEq)]
66pub enum Cmd {
67 SingleCmd(SingleCmd),
69
70 Pipeline(Pipeline),
72}
73
74impl Cmd {
75 pub fn replace(mut self, from: impl AsRef<str>, to: impl AsRef<str>) -> Self {
80 match &mut self {
81 Self::SingleCmd(SingleCmd(cmd)) => *cmd = cmd.replace(from.as_ref(), to.as_ref()),
82 Self::Pipeline(Pipeline(cmds)) => {
83 for SingleCmd(cmd) in cmds {
84 *cmd = cmd.replace(from.as_ref(), to.as_ref());
85 }
86 }
87 }
88 self
89 }
90
91 pub async fn run_with(&self, input: impl AsRef<[u8]>) -> Result<CmdOutput> {
93 debug!("running command: {}", self.to_string());
94
95 match self {
96 Self::SingleCmd(cmd) => cmd.run(input).await,
97 Self::Pipeline(cmds) => cmds.run(input).await,
98 }
99 }
100
101 pub async fn run(&self) -> Result<CmdOutput> {
103 self.run_with([]).await
104 }
105}
106
107impl Default for Cmd {
108 fn default() -> Self {
109 Self::Pipeline(Pipeline::default())
110 }
111}
112
113impl From<String> for Cmd {
114 fn from(cmd: String) -> Self {
115 Self::SingleCmd(cmd.into())
116 }
117}
118
119impl From<&String> for Cmd {
120 fn from(cmd: &String) -> Self {
121 Self::SingleCmd(cmd.into())
122 }
123}
124
125impl From<&str> for Cmd {
126 fn from(cmd: &str) -> Self {
127 Self::SingleCmd(cmd.into())
128 }
129}
130
131impl From<Vec<String>> for Cmd {
132 fn from(cmd: Vec<String>) -> Self {
133 Self::Pipeline(cmd.into())
134 }
135}
136
137impl From<Vec<&String>> for Cmd {
138 fn from(cmd: Vec<&String>) -> Self {
139 Self::Pipeline(cmd.into())
140 }
141}
142
143impl From<Vec<&str>> for Cmd {
144 fn from(cmd: Vec<&str>) -> Self {
145 Self::Pipeline(cmd.into())
146 }
147}
148
149impl From<&[String]> for Cmd {
150 fn from(cmd: &[String]) -> Self {
151 Self::Pipeline(cmd.into())
152 }
153}
154
155impl From<&[&String]> for Cmd {
156 fn from(cmd: &[&String]) -> Self {
157 Self::Pipeline(cmd.into())
158 }
159}
160
161impl From<&[&str]> for Cmd {
162 fn from(cmd: &[&str]) -> Self {
163 Self::Pipeline(cmd.into())
164 }
165}
166
167impl ToString for Cmd {
168 fn to_string(&self) -> String {
169 match self {
170 Self::SingleCmd(cmd) => cmd.to_string(),
171 Self::Pipeline(pipeline) => pipeline.to_string(),
172 }
173 }
174}
175
176#[derive(Clone, Debug, Eq, PartialEq)]
180pub struct SingleCmd(String);
181
182impl SingleCmd {
183 async fn run(&self, input: impl AsRef<[u8]>) -> Result<CmdOutput> {
190 let windows = cfg!(target_os = "windows")
191 && !(env::var("MSYSTEM")
192 .map(|env| env.starts_with("MINGW"))
193 .unwrap_or_default());
194
195 let (shell, arg) = if windows { ("cmd", "/C") } else { ("sh", "-c") };
196 let mut cmd = Command::new(shell);
197 let cmd = cmd.args(&[arg, &self.0]);
198
199 if input.as_ref().is_empty() {
200 let output = cmd.output().await.map_err(Error::GetOutputError)?;
201 let code = output
202 .status
203 .code()
204 .ok_or_else(|| Error::GetExitStatusCodeNotAvailableError(self.to_string()))?;
205
206 if code != 0 {
207 let cmd = self.to_string();
208 let err = String::from_utf8_lossy(&output.stderr).to_string();
209 return Err(Error::InvalidExitStatusCodeNonZeroError(cmd, code, err));
210 }
211
212 Ok(output.stdout.into())
213 } else {
214 let mut output = Vec::new();
215
216 let mut pipeline = cmd
217 .stdin(Stdio::piped())
218 .stdout(Stdio::piped())
219 .stderr(Stdio::piped())
220 .spawn()
221 .map_err(|err| Error::SpawnProcessError(err, self.to_string()))?;
222
223 pipeline
224 .stdin
225 .as_mut()
226 .ok_or(Error::GetStdinError)?
227 .write_all(input.as_ref())
228 .await
229 .map_err(Error::WriteStdinError)?;
230
231 let code = pipeline
232 .wait()
233 .await
234 .map_err(|err| Error::WaitForExitStatusCodeError(err, self.to_string()))?
235 .code()
236 .ok_or_else(|| Error::GetExitStatusCodeNotAvailableError(self.to_string()))?;
237
238 if code != 0 {
239 let cmd = self.to_string();
240 let mut err = Vec::new();
241 pipeline
242 .stderr
243 .as_mut()
244 .ok_or(Error::GetStderrError)?
245 .read_to_end(&mut err)
246 .await
247 .map_err(Error::ReadStderrError)?;
248 let err = String::from_utf8_lossy(&err).to_string();
249
250 return Err(Error::InvalidExitStatusCodeNonZeroError(cmd, code, err));
251 }
252
253 pipeline
254 .stdout
255 .as_mut()
256 .ok_or(Error::GetStdoutError)?
257 .read_to_end(&mut output)
258 .await
259 .map_err(Error::ReadStdoutError)?;
260
261 Ok(output.into())
262 }
263 }
264}
265
266impl Deref for SingleCmd {
267 type Target = String;
268
269 fn deref(&self) -> &Self::Target {
270 &self.0
271 }
272}
273
274impl DerefMut for SingleCmd {
275 fn deref_mut(&mut self) -> &mut Self::Target {
276 &mut self.0
277 }
278}
279
280impl From<String> for SingleCmd {
281 fn from(cmd: String) -> Self {
282 Self(cmd)
283 }
284}
285
286impl From<&String> for SingleCmd {
287 fn from(cmd: &String) -> Self {
288 Self(cmd.clone())
289 }
290}
291
292impl From<&str> for SingleCmd {
293 fn from(cmd: &str) -> Self {
294 Self(cmd.to_owned())
295 }
296}
297
298impl ToString for SingleCmd {
299 fn to_string(&self) -> String {
300 self.0.clone()
301 }
302}
303
304#[derive(Clone, Debug, Default, Eq, PartialEq)]
311pub struct Pipeline(Vec<SingleCmd>);
312
313impl Pipeline {
314 async fn run(&self, input: impl AsRef<[u8]>) -> Result<CmdOutput> {
316 let mut output = input.as_ref().to_owned();
317
318 for cmd in &self.0 {
319 output = cmd.run(&output).await?.0;
320 }
321
322 Ok(output.into())
323 }
324}
325
326impl Deref for Pipeline {
327 type Target = Vec<SingleCmd>;
328
329 fn deref(&self) -> &Self::Target {
330 &self.0
331 }
332}
333
334impl DerefMut for Pipeline {
335 fn deref_mut(&mut self) -> &mut Self::Target {
336 &mut self.0
337 }
338}
339
340impl From<Vec<String>> for Pipeline {
341 fn from(cmd: Vec<String>) -> Self {
342 Self(cmd.into_iter().map(Into::into).collect())
343 }
344}
345
346impl From<Vec<&String>> for Pipeline {
347 fn from(cmd: Vec<&String>) -> Self {
348 Self(cmd.into_iter().map(Into::into).collect())
349 }
350}
351
352impl From<Vec<&str>> for Pipeline {
353 fn from(cmd: Vec<&str>) -> Self {
354 Self(cmd.into_iter().map(Into::into).collect())
355 }
356}
357
358impl From<&[String]> for Pipeline {
359 fn from(cmd: &[String]) -> Self {
360 Self(cmd.iter().map(Into::into).collect())
361 }
362}
363
364impl From<&[&String]> for Pipeline {
365 fn from(cmd: &[&String]) -> Self {
366 Self(cmd.iter().map(|cmd| (*cmd).into()).collect())
367 }
368}
369
370impl From<&[&str]> for Pipeline {
371 fn from(cmd: &[&str]) -> Self {
372 Self(cmd.iter().map(|cmd| (*cmd).into()).collect())
373 }
374}
375
376impl ToString for Pipeline {
377 fn to_string(&self) -> String {
378 self.0.iter().fold(String::new(), |s, cmd| {
379 if s.is_empty() {
380 cmd.to_string()
381 } else {
382 s + "|" + &cmd.to_string()
383 }
384 })
385 }
386}
387
388#[derive(Clone, Debug, Default, Eq, PartialEq)]
393pub struct CmdOutput(Vec<u8>);
394
395impl CmdOutput {
396 pub fn to_string_lossy(&self) -> String {
398 String::from_utf8_lossy(self).to_string()
399 }
400}
401
402impl Deref for CmdOutput {
403 type Target = Vec<u8>;
404
405 fn deref(&self) -> &Self::Target {
406 &self.0
407 }
408}
409
410impl DerefMut for CmdOutput {
411 fn deref_mut(&mut self) -> &mut Self::Target {
412 &mut self.0
413 }
414}
415
416impl From<Vec<u8>> for CmdOutput {
417 fn from(output: Vec<u8>) -> Self {
418 Self(output)
419 }
420}
421
422impl Into<Vec<u8>> for CmdOutput {
423 fn into(self) -> Vec<u8> {
424 self.0
425 }
426}
427
428impl TryInto<String> for CmdOutput {
429 type Error = Error;
430
431 fn try_into(self) -> result::Result<String, Self::Error> {
432 String::from_utf8(self.0).map_err(Error::ParseOutputAsUtf8StringError)
433 }
434}