aboutsummaryrefslogtreecommitdiff
path: root/src/plugin_command.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugin_command.rs')
-rw-r--r--src/plugin_command.rs93
1 files changed, 72 insertions, 21 deletions
diff --git a/src/plugin_command.rs b/src/plugin_command.rs
index a0f71894..6b639f0d 100644
--- a/src/plugin_command.rs
+++ b/src/plugin_command.rs
@@ -1,25 +1,37 @@
-#[cfg(any(windows, test))]
+use std::ffi::{OsStr, OsString};
use std::path::Path;
#[cfg(windows)]
use std::path::PathBuf;
use std::process::Command;
-pub(crate) fn command_for_argv(program: &str, args: &[String]) -> Command {
- let mut command = command_for_program(program);
- command.args(args);
+pub(crate) fn command_for_argv_in_dir(program: &str, args: &[String], cwd: &Path) -> Command {
+ let program = program_for_cwd(program, cwd);
+ let mut command = command_for_program(&program);
+ command.args(args).current_dir(cwd);
command
}
+fn program_for_cwd(program: &str, cwd: &Path) -> OsString {
+ let path = Path::new(program);
+ let has_separator = program.contains('/') || (cfg!(windows) && program.contains('\\'));
+ if path.is_relative() && has_separator {
+ let relative = path.strip_prefix(Path::new(".")).unwrap_or(path);
+ cwd.join(relative).into_os_string()
+ } else {
+ path.as_os_str().to_os_string()
+ }
+}
+
#[cfg(not(windows))]
-fn command_for_program(program: &str) -> Command {
+fn command_for_program(program: &OsStr) -> Command {
crate::noninteractive_process::command(program)
}
#[cfg(windows)]
-fn command_for_program(program: &str) -> Command {
+fn command_for_program(program: &OsStr) -> Command {
let resolved = resolve_windows_program(program);
let command_program = resolved.as_ref().map_or_else(
- || std::ffi::OsString::from(program),
+ || program.to_os_string(),
|path| path.as_os_str().to_os_string(),
);
if is_windows_batch_file_name(program)
@@ -38,7 +50,7 @@ fn command_for_program(program: &str) -> Command {
}
#[cfg(windows)]
-fn resolve_windows_program(program: &str) -> Option<PathBuf> {
+fn resolve_windows_program(program: &OsStr) -> Option<PathBuf> {
if has_path_separator(program) {
return None;
}
@@ -55,7 +67,11 @@ fn resolve_windows_program(program: &str) -> Option<PathBuf> {
std::env::split_paths(&path_var).find_map(|dir| {
extensions
.iter()
- .map(|extension| dir.join(format!("{program}{extension}")))
+ .map(|extension| {
+ let mut file_name = program.to_os_string();
+ file_name.push(extension);
+ dir.join(file_name)
+ })
.find(|candidate| candidate.is_file())
})
})
@@ -91,22 +107,22 @@ fn windows_path_extensions() -> Vec<String> {
}
#[cfg(windows)]
-fn has_path_separator(program: &str) -> bool {
- program.contains(['/', '\\'])
+fn has_path_separator(program: &OsStr) -> bool {
+ program.to_string_lossy().contains(['/', '\\'])
}
#[cfg(windows)]
fn is_windows_batch_path(path: &Path) -> bool {
path.extension()
- .and_then(std::ffi::OsStr::to_str)
+ .and_then(OsStr::to_str)
.is_some_and(is_windows_batch_extension)
}
#[cfg(any(windows, test))]
-fn is_windows_batch_file_name(program: &str) -> bool {
+fn is_windows_batch_file_name(program: &OsStr) -> bool {
Path::new(program)
.extension()
- .and_then(std::ffi::OsStr::to_str)
+ .and_then(OsStr::to_str)
.is_some_and(is_windows_batch_extension)
}
@@ -120,11 +136,22 @@ mod tests {
use super::*;
#[test]
+ fn resolves_explicit_relative_program_against_working_directory() {
+ let cwd = Path::new("plugin-root");
+
+ assert_eq!(
+ program_for_cwd("./bin/tool", cwd),
+ cwd.join("bin/tool").into_os_string()
+ );
+ assert_eq!(program_for_cwd("tool", cwd), OsString::from("tool"));
+ }
+
+ #[test]
fn recognizes_windows_batch_extensions_case_insensitively() {
- assert!(is_windows_batch_file_name("npm.cmd"));
- assert!(is_windows_batch_file_name("script.BAT"));
- assert!(!is_windows_batch_file_name("node.exe"));
- assert!(!is_windows_batch_file_name("node"));
+ assert!(is_windows_batch_file_name(OsStr::new("npm.cmd")));
+ assert!(is_windows_batch_file_name(OsStr::new("script.BAT")));
+ assert!(!is_windows_batch_file_name(OsStr::new("node.exe")));
+ assert!(!is_windows_batch_file_name(OsStr::new("node")));
}
#[cfg(windows)]
@@ -135,10 +162,12 @@ mod tests {
std::process::id()
));
std::fs::write(&path, "@echo off\r\necho plugin-%1\r\n").expect("write batch fixture");
+ let cwd = path.parent().expect("batch fixture parent");
- let output = command_for_argv(&path.display().to_string(), &["ready".to_string()])
- .output()
- .expect("run batch fixture");
+ let output =
+ command_for_argv_in_dir(&path.display().to_string(), &["ready".to_string()], cwd)
+ .output()
+ .expect("run batch fixture");
let _ = std::fs::remove_file(&path);
assert!(output.status.success(), "{output:?}");
@@ -147,4 +176,26 @@ mod tests {
"plugin-ready"
);
}
+
+ #[cfg(windows)]
+ #[test]
+ fn windows_explicit_relative_executable_runs_from_working_directory() {
+ let root = std::env::temp_dir().join(format!(
+ "herdr-plugin-relative-command-{}",
+ std::process::id()
+ ));
+ std::fs::create_dir_all(&root).expect("create relative command fixture");
+ let source = PathBuf::from(std::env::var_os("SystemRoot").expect("SystemRoot"))
+ .join("System32")
+ .join("where.exe");
+ let executable = root.join("tool.exe");
+ std::fs::copy(source, &executable).expect("copy relative command fixture");
+
+ let output = command_for_argv_in_dir("./tool.exe", &["/?".to_string()], &root)
+ .output()
+ .expect("run relative executable");
+ let _ = std::fs::remove_dir_all(&root);
+
+ assert!(output.status.success(), "{output:?}");
+ }
}