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