use std::iter;
#[must_use]
pub(crate) fn windows_command_line(exe: &str, args: &[String]) -> String {
let mut line = quote_windows_arg(exe);
for arg in args {
line.push(' ');
line.push_str("e_windows_arg(arg));
}
line
}
#[must_use]
pub(crate) fn quote_windows_arg(arg: &str) -> String {
if arg.is_empty() {
return "\"\"".to_string();
}
let needs_quotes = arg
.bytes()
.any(|byte| matches!(byte, b' ' | b'\t' | b'\n' | b'\r' | b'"'));
if !needs_quotes {
return arg.to_string();
}
let mut quoted = String::from("\"");
let mut backslashes = 0_usize;
for ch in arg.chars() {
if ch == '\\' {
backslashes = backslashes.saturating_add(1);
continue;
}
if ch == '"' {
quoted.extend(iter::repeat_n(
'\\',
backslashes.saturating_mul(2).saturating_add(1),
));
quoted.push('"');
backslashes = 0;
continue;
}
quoted.extend(iter::repeat_n('\\', backslashes));
quoted.push(ch);
backslashes = 0;
}
quoted.extend(iter::repeat_n('\\', backslashes.saturating_mul(2)));
quoted.push('"');
quoted
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn plain_args_are_unquoted() {
assert_eq!(quote_windows_arg("app.exe"), "app.exe");
assert_eq!(
windows_command_line("app.exe", &["--foo".to_string()]),
"app.exe --foo"
);
}
#[test]
fn spaces_and_quotes_are_escaped() {
assert_eq!(quote_windows_arg(""), "\"\"");
assert_eq!(quote_windows_arg("a b"), "\"a b\"");
assert_eq!(quote_windows_arg(r#"say "hi""#), r#""say \"hi\"""#);
assert_eq!(quote_windows_arg(r"C:\dir\"), r"C:\dir\");
assert_eq!(
quote_windows_arg(r"C:\Program Files\"),
r#""C:\Program Files\\""#
);
}
}