use std::borrow::Cow;
fn quote_arg(arg: &str) -> Cow<'_, str> {
match shlex::try_quote(arg) {
Ok(quoted) => quoted,
Err(_) => Cow::Owned(format!("'{}'", arg.replace('\'', "'\\''"))),
}
}
pub(crate) fn format_command_line(command: &[String]) -> String {
command
.iter()
.map(|a| quote_arg(a))
.collect::<Vec<_>>()
.join(" ")
}
pub(crate) fn truncate_command(command: &[String], max_len: usize) -> String {
let full = format_command_line(command);
if full.len() <= max_len {
return full;
}
let keep = max_len.saturating_sub(3);
let mut truncated: String = full.chars().take(keep).collect();
truncated.push_str("...");
truncated
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_args_unquoted() {
assert_eq!(
format_command_line(&["echo".to_string(), "hello".to_string()]),
"echo hello"
);
}
#[test]
fn args_with_spaces_are_quoted() {
let out =
format_command_line(&["echo".to_string(), "foo bar".to_string(), "baz".to_string()]);
let reparsed = shlex::split(&out).expect("round-trips through shlex");
assert_eq!(reparsed, vec!["echo", "foo bar", "baz"]);
}
#[test]
fn args_with_single_quotes_are_quoted() {
let out = format_command_line(&["echo".to_string(), "it's".to_string()]);
let reparsed = shlex::split(&out).expect("round-trips through shlex");
assert_eq!(reparsed, vec!["echo", "it's"]);
}
#[test]
fn args_with_double_quotes_are_quoted() {
let out = format_command_line(&["echo".to_string(), "a\"b".to_string()]);
let reparsed = shlex::split(&out).expect("round-trips through shlex");
assert_eq!(reparsed, vec!["echo", "a\"b"]);
}
#[test]
fn args_with_dollar_and_backslash_are_quoted() {
let out =
format_command_line(&["echo".to_string(), "$HOME".to_string(), "a\\b".to_string()]);
let reparsed = shlex::split(&out).expect("round-trips through shlex");
assert_eq!(reparsed, vec!["echo", "$HOME", "a\\b"]);
}
#[test]
fn empty_arg_is_quoted() {
let out = format_command_line(&["echo".to_string(), String::new()]);
let reparsed = shlex::split(&out).expect("round-trips through shlex");
assert_eq!(reparsed, vec!["echo", ""]);
}
#[test]
fn empty_command_returns_empty_string() {
assert_eq!(format_command_line(&[]), "");
}
#[test]
fn issue_660_repro() {
let rendered =
format_command_line(&["echo".to_string(), "foo bar".to_string(), "baz".to_string()]);
let naive = ["echo", "foo bar", "baz"].join(" ");
assert_eq!(naive, "echo foo bar baz"); assert_ne!(rendered, naive);
let reparsed = shlex::split(&rendered).expect("round-trips through shlex");
assert_eq!(reparsed, vec!["echo", "foo bar", "baz"]);
}
#[test]
fn truncate_command_short_passes_through() {
let cmd = vec!["echo".to_string(), "hello".to_string()];
assert_eq!(truncate_command(&cmd, 40), "echo hello");
}
#[test]
fn truncate_command_handles_multibyte_utf8() {
let cmd = vec!["éééééé".to_string()];
let result = truncate_command(&cmd, 5);
assert_eq!(result, "'é...");
assert!(result.chars().count() <= 5);
}
#[test]
fn truncate_command_max_len_smaller_than_ellipsis() {
let cmd = vec!["echo".to_string(), "hello world".to_string()];
let result = truncate_command(&cmd, 2);
assert_eq!(result, "...");
}
}