Skip to main content

rusty_pee/
spawner.rs

1//! Per-command shell-wrapping spawn + `Stdio::piped()` stdin (FR-001).
2
3use std::ffi::OsString;
4use std::process::{Child, Command, Stdio};
5
6/// Spawn one command via the platform shell with stdin piped.
7/// On Unix: `/bin/sh -c '<cmd>'`. On Windows: `cmd /C "<cmd>"`.
8///
9/// Stdout and stderr are inherited from the parent (`Stdio::inherit()`) so
10/// children write to the parent's terminal — outputs interleave
11/// nondeterministically (matches moreutils default).
12pub fn spawn_one(cmd: &str) -> std::io::Result<Child> {
13    let mut command = build_command(cmd);
14    command.stdin(Stdio::piped());
15    command.stdout(Stdio::inherit());
16    command.stderr(Stdio::inherit());
17    command.spawn()
18}
19
20/// Spawn one command via the platform shell with piped stdout (for `--capture` mode).
21pub fn spawn_one_piped_stdout(cmd: &str) -> std::io::Result<Child> {
22    let mut command = build_command(cmd);
23    command.stdin(Stdio::piped());
24    command.stdout(Stdio::piped());
25    command.stderr(Stdio::inherit());
26    command.spawn()
27}
28
29#[cfg(unix)]
30fn build_command(cmd: &str) -> Command {
31    let mut c = Command::new("/bin/sh");
32    c.arg("-c");
33    c.arg(OsString::from(cmd));
34    c
35}
36
37#[cfg(windows)]
38fn build_command(cmd: &str) -> Command {
39    let mut c = Command::new("cmd");
40    c.arg("/C");
41    c.arg(OsString::from(cmd));
42    c
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48    use std::io::Write;
49
50    #[test]
51    fn spawn_one_echo_then_close() {
52        // Spawn a child that just exits 0; verify we get a Child back.
53        #[cfg(unix)]
54        let cmd = "true";
55        #[cfg(windows)]
56        let cmd = "exit /b 0";
57        let mut child = spawn_one(cmd).expect("spawn should succeed");
58        // Close stdin so the child can exit.
59        drop(child.stdin.take());
60        let status = child.wait().expect("wait should succeed");
61        assert!(status.success(), "expected success exit");
62    }
63
64    #[test]
65    fn spawn_one_with_piped_stdin_accepts_writes() {
66        // Spawn a passthrough; write to its stdin; verify no panic on close.
67        #[cfg(unix)]
68        let cmd = "cat > /dev/null";
69        #[cfg(windows)]
70        let cmd = "more > NUL";
71        let mut child = spawn_one(cmd).expect("spawn should succeed");
72        if let Some(mut stdin) = child.stdin.take() {
73            let _ = stdin.write_all(b"some bytes\n");
74        }
75        let status = child.wait().expect("wait should succeed");
76        // Don't assert success — some shells return non-zero for trivial redirects.
77        let _ = status;
78    }
79}