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_chars(s: &str, max_len: usize) -> String {
if s.len() <= max_len || s.chars().count() <= max_len {
return s.to_string();
}
let keep = max_len.saturating_sub(3);
let mut truncated: String = s.chars().take(keep).collect();
truncated.push_str("...");
truncated
}
pub(crate) fn truncate_command(command: &[String], max_len: usize) -> String {
truncate_chars(&format_command_line(command), max_len)
}
#[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, "...");
}
#[test]
fn truncate_chars_short_passes_through() {
assert_eq!(truncate_chars("hello", 40), "hello");
}
#[test]
fn truncate_chars_handles_multibyte_utf8_at_byte_boundary() {
let prefix: String = "x".repeat(53);
let line = format!("{prefix}\u{1F600}tail");
assert_eq!(line.chars().count(), 58);
assert_eq!(line.len(), 61);
assert!(!line.is_char_boundary(54));
let result = truncate_chars(&line, 57);
assert!(result.ends_with("..."));
assert!(result.chars().count() <= 57);
}
#[test]
fn truncate_chars_max_len_smaller_than_ellipsis() {
assert_eq!(truncate_chars("hello world", 2), "...");
}
#[test]
fn truncate_chars_no_spurious_truncation_of_multibyte_string() {
let s = "\u{1F600}".repeat(5); assert_eq!(s.len(), 20);
assert_eq!(s.chars().count(), 5);
assert_eq!(truncate_chars(&s, 10), s); }
}