use tokio::process::Command;
pub fn program_command(program: &str) -> Command {
#[cfg(windows)]
{
if let Some(shim) = windows_batch_shim(program) {
let mut c = Command::new("cmd");
c.arg("/C").arg(shim);
if let Some(path) = crate::win_env::cmd_path_override() {
c.env("PATH", path);
}
return c;
}
}
Command::new(program)
}
#[cfg(windows)]
fn has_batch_ext(p: &std::path::Path) -> bool {
p.extension()
.and_then(|e| e.to_str())
.map(|e| {
let e = e.to_ascii_lowercase();
e == "cmd" || e == "bat"
})
.unwrap_or(false)
}
#[cfg(windows)]
fn windows_batch_shim(program: &str) -> Option<std::path::PathBuf> {
use std::path::Path;
let p = Path::new(program);
if p.extension().is_some() || p.components().count() > 1 {
return has_batch_ext(p).then(|| p.to_path_buf());
}
let path = std::env::var_os("PATH")?;
let pathext = std::env::var_os("PATHEXT").unwrap_or_else(|| ".COM;.EXE;.BAT;.CMD".into());
let exts: Vec<String> = pathext
.to_string_lossy()
.split(';')
.filter(|e| !e.is_empty())
.map(|e| e.to_string())
.collect();
for dir in std::env::split_paths(&path) {
for ext in &exts {
let cand = dir.join(format!("{program}{ext}"));
if cand.is_file() {
return has_batch_ext(&cand).then_some(cand);
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn direct_program_is_passed_through() {
let c = program_command("some-plain-binary");
let prog = c.as_std().get_program().to_string_lossy().to_string();
#[cfg(windows)]
assert_eq!(prog, "some-plain-binary"); #[cfg(not(windows))]
assert_eq!(prog, "some-plain-binary");
}
#[cfg(windows)]
#[test]
fn batch_extensions_route_through_cmd() {
assert!(windows_batch_shim("foo.cmd").is_some());
assert!(windows_batch_shim("foo.bat").is_some());
assert!(windows_batch_shim(r"C:\tools\foo.CMD").is_some());
assert!(windows_batch_shim("foo.exe").is_none());
assert!(windows_batch_shim(r"C:\tools\foo.exe").is_none());
}
#[cfg(windows)]
#[test]
fn cmd_shim_wraps_program() {
let c = program_command("foo.cmd");
assert_eq!(c.as_std().get_program().to_string_lossy(), "cmd");
let args: Vec<_> = c
.as_std()
.get_args()
.map(|a| a.to_string_lossy().to_string())
.collect();
assert_eq!(args, vec!["/C".to_string(), "foo.cmd".to_string()]);
}
}