pub fn quote(s: &str) -> String {
if !s.is_empty() && s.chars().all(|c| c.is_ascii_alphanumeric() || "-_./=:@,+%".contains(c)) {
return s.to_string();
}
quote_always(s)
}
pub fn quote_always(s: &str) -> String {
format!("'{}'", s.replace('\'', "'\\''"))
}
pub fn sh_c(script: &str) -> String {
format!("sh -c {}", quote_always(script))
}
pub fn join(words: &[String]) -> String {
words.iter().map(|w| quote(w)).collect::<Vec<_>>().join(" ")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn quoting() {
assert_eq!(quote("plain-path/a.b"), "plain-path/a.b");
assert_eq!(quote(""), "''");
assert_eq!(quote("it's"), "'it'\\''s'");
assert_eq!(quote("$HOME"), "'$HOME'");
assert_eq!(quote_always("x"), "'x'");
assert_eq!(join(&["echo".into(), "a b".into()]), "echo 'a b'");
let script = "x=$(echo 'a b'); for w in $x; do printf '[%s]' \"$w\"; done";
let out = std::process::Command::new("sh").arg("-c").arg(sh_c(script)).output().unwrap();
assert_eq!(String::from_utf8_lossy(&out.stdout), "[a][b]");
for s in ["it's", "a\"b", "$(x) `y` \\z", "new\nline", ""] {
let out =
std::process::Command::new("sh").arg("-c").arg(format!("printf %s {}", quote(s))).output().unwrap();
assert_eq!(String::from_utf8_lossy(&out.stdout), s);
}
}
}