1pub(crate) fn single_quote(value: &str) -> String {
10 format!("'{}'", value.replace('\'', "'\\''"))
11}
12
13pub(crate) fn quote_if_needed(value: &str) -> String {
16 if !value.is_empty() && value.chars().all(is_shell_safe) {
17 value.to_string()
18 } else {
19 single_quote(value)
20 }
21}
22
23fn is_shell_safe(c: char) -> bool {
26 c.is_ascii_alphanumeric()
27 || matches!(c, '_' | '-' | '.' | '/' | ':' | ',' | '=' | '@' | '%' | '+')
28}
29
30#[cfg(test)]
31mod tests {
32 use super::*;
33
34 #[test]
35 fn quote_if_needed_leaves_plain_values_unquoted() {
36 assert_eq!(quote_if_needed("hello"), "hello");
37 assert_eq!(quote_if_needed("host:/var/www/"), "host:/var/www/");
38 }
39
40 #[test]
41 fn quote_if_needed_quotes_empty_and_unsafe_values() {
42 assert_eq!(quote_if_needed(""), "''");
43 assert_eq!(quote_if_needed("it's"), "'it'\\''s'");
44 assert_eq!(quote_if_needed("a b"), "'a b'");
45 }
46
47 #[test]
48 fn single_quote_escapes_embedded_quotes() {
49 assert_eq!(single_quote("it's"), "'it'\\''s'");
50 assert_eq!(single_quote(""), "''");
51 }
52}