use tokio::process::Command; pub struct ShellInfo { pub program: &'static str, pub flag: &'static str, } pub fn shell_info() -> ShellInfo { if cfg!(windows) { ShellInfo { program: "cmd", flag: "/C", } } else { ShellInfo { program: "sh", flag: "-c", } } } pub fn shell_command_builder(command_str: &str) -> Command { let info = shell_info(); let mut cmd = Command::new(info.program); cmd.arg(info.flag).arg(command_str); // CREATE_NO_WINDOW: don't flash a console window when the host is a GUI app. #[cfg(windows)] cmd.creation_flags(0x0800_0000); cmd } #[cfg(test)] mod tests { use super::*; #[test] fn shell_info_returns_platform_appropriate_values() { let info = shell_info(); if cfg!(windows) { assert_eq!(info.program, "cmd"); assert_eq!(info.flag, "/C"); } else { assert_eq!(info.program, "sh"); assert_eq!(info.flag, "-c"); } } #[tokio::test] async fn shell_command_builder_allows_env_and_cwd() { let tmp = std::env::temp_dir(); let cmd_str = if cfg!(windows) { "echo %MY_VAR%" } else { "echo $MY_VAR" }; let output = shell_command_builder(cmd_str) .env("MY_VAR", "test_value") .current_dir(&tmp) .output() .await .expect("builder failed"); let stdout = String::from_utf8_lossy(&output.stdout); assert!(stdout.contains("test_value")); } }