use std::env::consts::EXE_EXTENSION;
use std::ffi::OsStr;
use std::path::Path;
use std::process::Command;
#[derive(Debug)]
pub enum WindowsRunnable {
Executable,
PowerShell,
Command,
Batch,
}
impl WindowsRunnable {
fn all() -> &'static [Self] {
&[
Self::Executable,
Self::PowerShell,
Self::Command,
Self::Batch,
]
}
fn to_extension(&self) -> &'static str {
match self {
Self::Executable => EXE_EXTENSION,
Self::PowerShell => "ps1",
Self::Command => "cmd",
Self::Batch => "bat",
}
}
fn from_extension(ext: &str) -> Option<Self> {
match ext {
EXE_EXTENSION => Some(Self::Executable),
"ps1" => Some(Self::PowerShell),
"cmd" => Some(Self::Command),
"bat" => Some(Self::Batch),
_ => None,
}
}
fn as_command(&self, runnable_path: &Path) -> Command {
match self {
Self::Executable => Command::new(runnable_path),
Self::PowerShell => {
let mut cmd = Command::new("powershell");
cmd.arg("-NoLogo").arg("-File").arg(runnable_path);
cmd
}
Self::Command | Self::Batch => {
let mut cmd = Command::new("cmd");
cmd.arg("/q").arg("/c").arg(runnable_path);
cmd
}
}
}
pub fn from_script_path(script_path: &Path, runnable_name: &OsStr) -> Command {
let script_path = script_path.join(runnable_name);
if let Some(script_type) = script_path
.extension()
.and_then(OsStr::to_str)
.and_then(Self::from_extension)
.filter(|_| script_path.is_file())
{
return script_type.as_command(&script_path);
}
Self::all()
.iter()
.map(|script_type| {
(
script_type,
script_path.with_added_extension(script_type.to_extension()),
)
})
.find(|(_, script_path)| script_path.is_file())
.map(|(script_type, script_path)| script_type.as_command(&script_path))
.unwrap_or_else(|| Command::new(runnable_name))
}
}
#[cfg(test)]
mod tests {
#[cfg(target_os = "windows")]
use super::WindowsRunnable;
#[cfg(target_os = "windows")]
use fs_err as fs;
#[cfg(target_os = "windows")]
use std::ffi::OsStr;
#[cfg(target_os = "windows")]
use std::io;
#[cfg(target_os = "windows")]
fn create_test_environment() -> io::Result<tempfile::TempDir> {
let temp_dir = tempfile::tempdir()?;
let scripts_dir = temp_dir.path().join("Scripts");
fs::create_dir_all(&scripts_dir)?;
fs::write(scripts_dir.join("python.exe"), "")?;
fs::write(scripts_dir.join("awslabs.cdk-mcp-server.exe"), "")?;
fs::write(scripts_dir.join("org.example.tool.exe"), "")?;
fs::write(scripts_dir.join("multi.dot.package.name.exe"), "")?;
fs::write(scripts_dir.join("script.ps1"), "")?;
fs::write(scripts_dir.join("batch.bat"), "")?;
fs::write(scripts_dir.join("command.cmd"), "")?;
fs::write(scripts_dir.join("explicit.ps1"), "")?;
Ok(temp_dir)
}
#[cfg(target_os = "windows")]
#[test]
fn test_from_script_path_single_dot_package() {
let temp_dir = create_test_environment().expect("Failed to create test environment");
let scripts_dir = temp_dir.path().join("Scripts");
let command =
WindowsRunnable::from_script_path(&scripts_dir, OsStr::new("awslabs.cdk-mcp-server"));
let expected_path = scripts_dir.join("awslabs.cdk-mcp-server.exe");
assert_eq!(command.get_program(), expected_path.as_os_str());
}
#[cfg(target_os = "windows")]
#[test]
fn test_from_script_path_multiple_dots_package() {
let temp_dir = create_test_environment().expect("Failed to create test environment");
let scripts_dir = temp_dir.path().join("Scripts");
let command =
WindowsRunnable::from_script_path(&scripts_dir, OsStr::new("org.example.tool"));
let expected_path = scripts_dir.join("org.example.tool.exe");
assert_eq!(command.get_program(), expected_path.as_os_str());
let command =
WindowsRunnable::from_script_path(&scripts_dir, OsStr::new("multi.dot.package.name"));
let expected_path = scripts_dir.join("multi.dot.package.name.exe");
assert_eq!(command.get_program(), expected_path.as_os_str());
}
#[cfg(target_os = "windows")]
#[test]
fn test_from_script_path_simple_package_name() {
let temp_dir = create_test_environment().expect("Failed to create test environment");
let scripts_dir = temp_dir.path().join("Scripts");
let command = WindowsRunnable::from_script_path(&scripts_dir, OsStr::new("python"));
let expected_path = scripts_dir.join("python.exe");
assert_eq!(command.get_program(), expected_path.as_os_str());
}
#[cfg(target_os = "windows")]
#[test]
fn test_from_script_path_explicit_extensions() {
let temp_dir = create_test_environment().expect("Failed to create test environment");
let scripts_dir = temp_dir.path().join("Scripts");
let command = WindowsRunnable::from_script_path(&scripts_dir, OsStr::new("explicit.ps1"));
let expected_path = scripts_dir.join("explicit.ps1");
assert_eq!(command.get_program(), "powershell");
let args: Vec<&OsStr> = command.get_args().collect();
assert!(args.contains(&OsStr::new("-File")));
assert!(args.contains(&expected_path.as_os_str()));
let command = WindowsRunnable::from_script_path(&scripts_dir, OsStr::new("batch.bat"));
assert_eq!(command.get_program(), "cmd");
let command = WindowsRunnable::from_script_path(&scripts_dir, OsStr::new("command.cmd"));
assert_eq!(command.get_program(), "cmd");
}
}