1use std::ffi::OsStr;
10use std::io::IsTerminal;
11use std::process::{Command, ExitStatus, Stdio};
12
13pub fn run_shell(
19 cmd: &str,
20 path: &OsStr,
21 relpath: &OsStr,
22 bg: bool,
23) -> std::io::Result<Option<ExitStatus>> {
24 let mut command = Command::new("sh");
25 command
26 .arg("-c")
27 .arg(cmd)
28 .env("path", path)
29 .env("relpath", relpath);
30 if bg {
31 command
32 .stdin(Stdio::null())
33 .stdout(Stdio::null())
34 .stderr(Stdio::null())
35 .spawn()?;
36 Ok(None)
37 } else {
38 if !std::io::stdin().is_terminal()
41 && let Ok(tty) = std::fs::File::open("/dev/tty")
42 {
43 command.stdin(Stdio::from(tty));
44 }
45 command.status().map(Some)
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52
53 #[test]
54 fn foreground_command_sees_path_env_vars() {
55 let dir = tempfile::tempdir().unwrap();
56 let out = dir.path().join("out");
57 let status = run_shell(
58 &format!(
59 "printf '%s\\n%s' \"$path\" \"$relpath\" > {}",
60 out.display()
61 ),
62 OsStr::new("/abs/some/file.txt"),
63 OsStr::new("some/file.txt"),
64 false,
65 )
66 .unwrap()
67 .expect("foreground returns a status");
68 assert!(status.success());
69 assert_eq!(
70 std::fs::read_to_string(out).unwrap(),
71 "/abs/some/file.txt\nsome/file.txt"
72 );
73 }
74
75 #[test]
76 fn foreground_reports_failure_status() {
77 let status = run_shell("exit 3", OsStr::new("/x"), OsStr::new("x"), false)
78 .unwrap()
79 .unwrap();
80 assert_eq!(status.code(), Some(3));
81 }
82
83 #[test]
84 fn background_command_detaches() {
85 let dir = tempfile::tempdir().unwrap();
86 let out = dir.path().join("out");
87 let result = run_shell(
88 &format!("echo \"$relpath\" > {}", out.display()),
89 OsStr::new("/abs/f"),
90 OsStr::new("f"),
91 true,
92 )
93 .unwrap();
94 assert!(result.is_none());
95 for _ in 0..50 {
97 if out.exists() {
98 break;
99 }
100 std::thread::sleep(std::time::Duration::from_millis(10));
101 }
102 assert_eq!(std::fs::read_to_string(out).unwrap().trim(), "f");
103 }
104}