#[cfg(windows)]
use std::ffi::OsString;
#[cfg(windows)]
use std::path::Path;
#[cfg(windows)]
pub(super) fn cmd_raw_command_line(program: &Path, args: &[OsString]) -> String {
format!("/d /s /c \"{}\"", build_cmd_line(program, args))
}
#[cfg(windows)]
fn build_cmd_line(program: &Path, args: &[OsString]) -> String {
let mut line = quote_arg(&program.to_string_lossy());
for a in args {
line.push(' ');
line.push_str("e_arg(&a.to_string_lossy()));
}
line
}
#[cfg(any(windows, test))]
fn quote_arg(arg: &str) -> String {
if !arg.is_empty() && !arg.contains([' ', '\t', '\n', '\x0b', '"']) {
return arg.to_string();
}
let mut out = String::with_capacity(arg.len() + 2);
out.push('"');
let mut backslashes = 0usize;
for c in arg.chars() {
match c {
'\\' => {
backslashes += 1;
out.push('\\');
}
'"' => {
for _ in 0..=backslashes {
out.push('\\');
}
out.push('"');
backslashes = 0;
}
_ => {
backslashes = 0;
out.push(c);
}
}
}
for _ in 0..backslashes {
out.push('\\');
}
out.push('"');
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_args_are_not_quoted() {
assert_eq!(quote_arg("add"), "add");
assert_eq!(quote_arg("--noEmit"), "--noEmit");
assert_eq!(quote_arg("C:/x/y"), "C:/x/y");
}
#[test]
fn args_with_spaces_are_quoted() {
assert_eq!(quote_arg("C:/Program Files/x"), "\"C:/Program Files/x\"");
}
#[test]
fn empty_arg_becomes_empty_quotes() {
assert_eq!(quote_arg(""), "\"\"");
}
#[test]
fn inner_quotes_are_escaped() {
assert_eq!(quote_arg("say \"hi\""), "\"say \\\"hi\\\"\"");
}
#[test]
fn an_arg_without_spaces_is_left_alone_even_with_backslashes() {
assert_eq!(quote_arg("C:\\dir\\"), "C:\\dir\\");
assert_eq!(quote_arg("C:\\x\\y"), "C:\\x\\y");
}
#[test]
fn trailing_backslashes_are_doubled_when_quoting() {
assert_eq!(quote_arg("a b\\"), "\"a b\\\\\"");
assert_eq!(quote_arg("C:\\a b\\"), "\"C:\\a b\\\\\"");
}
#[test]
fn backslashes_before_a_quote_are_doubled_and_the_quote_escaped() {
assert_eq!(quote_arg("a\\\"b"), "\"a\\\\\\\"b\"");
}
#[test]
fn a_plain_backslash_is_untouched_inside_quotes() {
assert_eq!(quote_arg("C:\\a b\\c"), "\"C:\\a b\\c\"");
}
#[cfg(windows)]
#[test]
fn a_cmd_shim_is_wrapped_in_cmd_exe() {
let shim = Path::new("C:\\path with space\\pnpm.cmd");
let args = vec![OsString::from("add"), OsString::from("serde")];
let line = cmd_raw_command_line(shim, &args);
assert!(line.starts_with("/d /s /c "), "{line}");
assert!(line.ends_with('"'), "{line}");
assert_eq!(
line.matches('"').count(),
4,
"one pair for the path + one outer pair: {line}"
);
assert!(line.contains("\"C:\\path with space\\pnpm.cmd\""), "{line}");
assert!(line.ends_with("add serde\""), "{line}");
}
#[cfg(windows)]
#[test]
fn the_outer_quote_pair_is_always_present() {
let line = cmd_raw_command_line(Path::new("C:\\x\\pnpm.cmd"), &[]);
assert_eq!(line, "/d /s /c \"C:\\x\\pnpm.cmd\"");
}
}