git_cliff_core/
command.rs1use std::io::{Error as IoError, Write};
2use std::process::{Command, Stdio};
3use std::{env, str, thread};
4
5use crate::error::Result;
6
7pub fn run(command: &str, input: Option<String>, envs: Vec<(&str, &str)>) -> Result<String> {
12 log::trace!("Running command: {:?}", command);
13 let mut child = if cfg!(target_os = "windows") {
14 Command::new("cmd")
15 .envs(envs)
16 .args(["/C", command])
17 .stdin(Stdio::piped())
18 .stdout(Stdio::piped())
19 .current_dir(env::current_dir()?)
20 .spawn()
21 } else {
22 Command::new("sh")
23 .envs(envs)
24 .args(["-c", command])
25 .stdin(Stdio::piped())
26 .stdout(Stdio::piped())
27 .current_dir(env::current_dir()?)
28 .spawn()
29 }?;
30 if let Some(input) = input {
31 let mut stdin = child
32 .stdin
33 .take()
34 .ok_or_else(|| IoError::other("stdin is not captured"))?;
35 thread::spawn(move || {
36 stdin
37 .write_all(input.as_bytes())
38 .expect("Failed to write to stdin");
39 });
40 }
41 let output = child.wait_with_output()?;
42 if output.status.success() {
43 Ok(str::from_utf8(&output.stdout)?.to_string())
44 } else {
45 for output in [output.stdout, output.stderr] {
46 let output = str::from_utf8(&output)?.to_string();
47 if !output.is_empty() {
48 log::error!("{}", output);
49 }
50 }
51 Err(IoError::other(format!("command exited with {:?}", output.status)).into())
52 }
53}
54
55#[cfg(test)]
56mod test {
57 use super::*;
58
59 #[test]
60 #[cfg(target_family = "unix")]
61 fn run_os_command() -> Result<()> {
62 assert_eq!(
63 "eroc-ffilc-tig",
64 run("echo $APP_NAME | rev", None, vec![(
65 "APP_NAME",
66 env!("CARGO_PKG_NAME")
67 )])?
68 .trim()
69 );
70 assert_eq!(
71 "eroc-ffilc-tig",
72 run("rev", Some(env!("CARGO_PKG_NAME").to_string()), vec![])?.trim()
73 );
74 assert_eq!("testing", run("echo 'testing'", None, vec![])?.trim());
75 assert!(run("some_command", None, vec![]).is_err());
76 Ok(())
77 }
78}