use std::path::{Path, PathBuf};
use super::path_selection::select_path_candidate;
pub(crate) fn windows_quote_for_cmd(arg: &str) -> String {
let mut escaped = String::with_capacity(arg.len() + 2);
escaped.push('"');
for ch in arg.chars() {
match ch {
'%' => escaped.push_str("%%"),
'"' => escaped.push_str("\"\""),
_ => escaped.push(ch),
}
}
escaped.push('"');
escaped
}
pub(crate) fn resolve_windows_program(program: &str) -> Option<String> {
let program_path = Path::new(program);
let has_separator = program.contains('\\') || program.contains('/');
if has_separator {
return if program_path.is_absolute() { Some(program.to_string()) } else { None };
}
let has_extension = program_path.extension().is_some();
let path_dirs = path_dirs_from_env();
let candidates: Vec<String> = if has_extension {
path_dirs
.iter()
.filter(|dir| !dir.as_os_str().is_empty() && dir.is_absolute())
.filter_map(|dir| {
let candidate = dir.join(program);
if candidate.is_file() { candidate.to_str().map(str::to_string) } else { None }
})
.collect()
} else {
let path_exts = pathext_from_env();
path_dirs
.iter()
.filter(|dir| !dir.as_os_str().is_empty() && dir.is_absolute())
.flat_map(|dir| {
path_exts.iter().filter_map(|ext| {
let mut candidate = dir.join(program);
candidate.set_extension(ext.trim_start_matches('.'));
if candidate.is_file() { candidate.to_str().map(str::to_string) } else { None }
})
})
.collect()
};
let cwd = std::env::current_dir().ok()?;
let candidate_refs: Vec<&str> = candidates.iter().map(String::as_str).collect();
select_path_candidate(&candidate_refs, &cwd)
}
fn path_dirs_from_env() -> Vec<PathBuf> {
std::env::var_os("PATH").map(|val| std::env::split_paths(&val).collect()).unwrap_or_default()
}
fn pathext_from_env() -> Vec<String> {
std::env::var("PATHEXT")
.unwrap_or_else(|_| ".COM;.EXE;.BAT;.CMD".to_string())
.split(';')
.map(str::to_string)
.collect()
}
pub(crate) fn windows_requires_cmd_shell(program: &str) -> bool {
Path::new(program)
.extension()
.and_then(|ext| ext.to_str())
.map(|ext| ext.eq_ignore_ascii_case("bat") || ext.eq_ignore_ascii_case("cmd"))
.unwrap_or(false)
}
pub(crate) fn resolve_cmd_exe() -> Option<String> {
if let Some(system_root) = std::env::var_os("SystemRoot") {
let candidate = Path::new(&system_root).join("System32").join("cmd.exe");
if candidate.is_absolute() && candidate.is_file() {
return candidate.to_str().map(str::to_string);
}
}
if let Some(comspec) = std::env::var_os("ComSpec") {
let candidate = Path::new(&comspec);
if candidate.is_absolute() && candidate.is_file() {
return candidate.to_str().map(str::to_string);
}
}
None
}