From 14aa6ab6ccf39e0777c0e6e46ca72c949b90c83a Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:43:22 +0800 Subject: [PATCH 1/5] Add `Cmd::inherit_stdin` Should fix #63. --- src/lib.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index aaf8ac0..758c828 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -752,7 +752,7 @@ struct CmdData { ignore_status: bool, quiet: bool, secret: bool, - stdin_contents: Option>, + stdin_mode: StdinMode, ignore_stdout: bool, ignore_stderr: bool, } @@ -768,6 +768,33 @@ enum EnvChange { Clear, } +#[derive(Debug, Clone, Default)] +enum StdinMode { + /// Default mode. + #[default] + Null, + /// `Cmd::inherit_stdin`. + Inherited, + /// `Cmd::stdin`. + Piped(Vec), +} +impl StdinMode { + fn set_inherited(&mut self) { + match self { + Self::Null => *self = Self::Inherited, + Self::Inherited => {} // ignore + Self::Piped(_) => panic!("Cmd::inherit_stdin is mutually-exclusive with Cmd::stdin"), + } + } + fn set_piped(&mut self, data: Vec) { + match self { + Self::Null => *self = Self::Piped(data), + Self::Inherited => panic!("Cmd::stdin is mutually-exclusive with Cmd::inherit_stdin"), + Self::Piped(_) => *self = Self::Piped(data), // replace + } + } +} + impl fmt::Display for Cmd<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.data, f) @@ -917,13 +944,23 @@ impl<'a> Cmd<'a> { self.data.secret = yes; } + /// Inherit standard input of the parent shell. + /// + /// Mutually-exclusive with [`Cmd::stdin`]. + pub fn inherit_stdin(mut self) -> Cmd<'a> { + self.data.stdin_mode.set_inherited(); + self + } + /// Pass the given slice to the standard input of the spawned process. + /// + /// Mutually-exclusive with [`Cmd::inherit_stdin`]. pub fn stdin(mut self, stdin: impl AsRef<[u8]>) -> Cmd<'a> { self._stdin(stdin.as_ref()); self } fn _stdin(&mut self, stdin: &[u8]) { - self.data.stdin_contents = Some(stdin.to_vec()); + self.data.stdin_mode.set_piped(stdin.to_vec()); } /// Ignores the standard output stream of the process. @@ -1011,9 +1048,10 @@ impl<'a> Cmd<'a> { command.stderr(if read_stderr { Stdio::piped() } else { Stdio::inherit() }); } - command.stdin(match &self.data.stdin_contents { - Some(_) => Stdio::piped(), - None => Stdio::null(), + command.stdin(match &self.data.stdin_mode { + StdinMode::Null => Stdio::null(), + StdinMode::Inherited => Stdio::inherit(), + StdinMode::Piped(_) => Stdio::piped(), }); command.spawn().map_err(|err| { @@ -1031,7 +1069,7 @@ impl<'a> Cmd<'a> { }; let mut io_thread = None; - if let Some(stdin_contents) = self.data.stdin_contents.clone() { + if let StdinMode::Piped(stdin_contents) = self.data.stdin_mode.clone() { let mut stdin = child.stdin.take().unwrap(); io_thread = Some(std::thread::spawn(move || { stdin.write_all(&stdin_contents)?; From ff661d01a42e062d1f8568961306a55a26dc6dd5 Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:25:13 +0800 Subject: [PATCH 2/5] Add tests for `Cmd::inherit_stdin` --- tests/data/stdin_is_terminal.rs | 14 ++++++++++++++ tests/it/main.rs | 31 ++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 tests/data/stdin_is_terminal.rs diff --git a/tests/data/stdin_is_terminal.rs b/tests/data/stdin_is_terminal.rs new file mode 100644 index 0000000..5513c4e --- /dev/null +++ b/tests/data/stdin_is_terminal.rs @@ -0,0 +1,14 @@ +use std::{ + io::{stdin, IsTerminal}, + process::exit, +}; + +fn main() { + if stdin().is_terminal() { + println!("Stdin is terminal"); + exit(0); + } else { + println!("Stdin is not terminal"); + exit(1); + } +} diff --git a/tests/it/main.rs b/tests/it/main.rs index 3214840..b2d0172 100644 --- a/tests/it/main.rs +++ b/tests/it/main.rs @@ -10,14 +10,19 @@ fn setup() -> Shell { static ONCE: std::sync::Once = std::sync::Once::new(); let sh = Shell::new().unwrap(); - let xecho_src = sh.current_dir().join("./tests/data/xecho.rs"); + let source_files = [ + sh.current_dir().join("./tests/data/xecho.rs"), + sh.current_dir().join("./tests/data/stdin_is_terminal.rs"), + ]; let target_dir = sh.current_dir().join("./target/"); ONCE.call_once(|| { - cmd!(sh, "rustc {xecho_src} --out-dir {target_dir}") - .quiet() - .run() - .unwrap_or_else(|err| panic!("failed to install binaries from mock_bin: {}", err)) + for src in source_files { + cmd!(sh, "rustc {src} --out-dir {target_dir}") + .quiet() + .run() + .unwrap_or_else(|err| panic!("failed to install binaries from mock_bin: {}", err)) + } }); sh.set_var("PATH", target_dir); @@ -210,6 +215,22 @@ fn escape() { assert_eq!(output, r#"\hello\ \world\"#) } +#[test] +fn inherit_stdin() { + let sh = setup(); + + let res = cmd!(sh, "stdin_is_terminal").inherit_stdin().run(); + assert!(res.is_ok()); +} + +#[test] +fn no_inherit_stdin() { + let sh = setup(); + + let res = cmd!(sh, "stdin_is_terminal").run(); + assert!(res.is_err()); +} + #[test] fn stdin_redirection() { let sh = setup(); From 70b975eef1ea0b9bb816774763ab6d8a9e26c93b Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:52:57 +0800 Subject: [PATCH 3/5] Write changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b257dc..4f2cf1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Add `Cmd::inherit_stdin` to support pre-0.2.3 behaviour #98 + ## 0.2.7 - MSRV is raised to 1.63.0 From 8c3fdb2551fd5a74d37f9e12c9bbd0590c101994 Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:57:37 +0800 Subject: [PATCH 4/5] Switch to `is-terminal` crate for test due to MSRV --- tests/data/stdin_is_terminal/Cargo.toml | 9 ++++++++ .../src/main.rs} | 8 +++---- tests/it/main.rs | 23 ++++++++++++++----- 3 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 tests/data/stdin_is_terminal/Cargo.toml rename tests/data/{stdin_is_terminal.rs => stdin_is_terminal/src/main.rs} (58%) diff --git a/tests/data/stdin_is_terminal/Cargo.toml b/tests/data/stdin_is_terminal/Cargo.toml new file mode 100644 index 0000000..24d6f3a --- /dev/null +++ b/tests/data/stdin_is_terminal/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "stdin_is_terminal" +version = "0.0.0" +edition = "2021" + +[workspace] + +[dependencies] +is-terminal = "0.4.13" diff --git a/tests/data/stdin_is_terminal.rs b/tests/data/stdin_is_terminal/src/main.rs similarity index 58% rename from tests/data/stdin_is_terminal.rs rename to tests/data/stdin_is_terminal/src/main.rs index 5513c4e..9b7a818 100644 --- a/tests/data/stdin_is_terminal.rs +++ b/tests/data/stdin_is_terminal/src/main.rs @@ -1,7 +1,7 @@ -use std::{ - io::{stdin, IsTerminal}, - process::exit, -}; +use std::{io::stdin, process::exit}; + +// TODO: switch to `std::io::IsTerminal` when MSRV >= 1.70.0 +use is_terminal::IsTerminal; fn main() { if stdin().is_terminal() { diff --git a/tests/it/main.rs b/tests/it/main.rs index b2d0172..2cc8895 100644 --- a/tests/it/main.rs +++ b/tests/it/main.rs @@ -10,22 +10,33 @@ fn setup() -> Shell { static ONCE: std::sync::Once = std::sync::Once::new(); let sh = Shell::new().unwrap(); - let source_files = [ - sh.current_dir().join("./tests/data/xecho.rs"), - sh.current_dir().join("./tests/data/stdin_is_terminal.rs"), - ]; + let single_file_sources = [sh.current_dir().join("./tests/data/xecho.rs")]; + let crate_sources = [sh.current_dir().join("./tests/data/stdin_is_terminal/")]; let target_dir = sh.current_dir().join("./target/"); ONCE.call_once(|| { - for src in source_files { + for src in single_file_sources { cmd!(sh, "rustc {src} --out-dir {target_dir}") .quiet() .run() .unwrap_or_else(|err| panic!("failed to install binaries from mock_bin: {}", err)) } + for src_dir in crate_sources { + sh.change_dir(src_dir); + cmd!(sh, "cargo build -q --target-dir {target_dir}") + .quiet() + .run() + .unwrap_or_else(|err| panic!("failed to build mock crate: {err}")); + } }); - sh.set_var("PATH", target_dir); + let path_env = std::env::join_paths([ + &target_dir, + &target_dir.join("debug/"), + &target_dir.join("release/"), + ]) + .unwrap(); + sh.set_var("PATH", path_env); sh } From add6efb2059d62132be2a0e40293306ec990acac Mon Sep 17 00:00:00 2001 From: cyqsimon <28627918+cyqsimon@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:14:17 +0800 Subject: [PATCH 5/5] skip `inherit_stdin` tests for CI --- examples/ci.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/ci.rs b/examples/ci.rs index fcda0e5..bb4c957 100644 --- a/examples/ci.rs +++ b/examples/ci.rs @@ -38,7 +38,8 @@ fn test(sh: &Shell) -> Result<()> { { let _s = Section::new("TEST"); - cmd!(sh, "cargo test --workspace").run()?; + // inherit_stdin tests are skipped because they require an interactive terminal + cmd!(sh, "cargo test --workspace -- --skip inherit_stdin").run()?; } Ok(()) }