use std::borrow::Cow::{self, Borrowed, Owned};
pub fn quote(s: &str) -> Cow<'_, str> {
if !s.is_empty() && !str_needs_quoting(s) {
return Borrowed(s);
}
if s.find('\'').is_none() {
return Owned(format!("'{}'", s));
}
let mut result = String::with_capacity(s.len().saturating_add(8));
result.push('"');
for c in s.chars() {
if matches!(c, '"' | '`' | '$' | '\\') {
result.push('\\');
}
result.push(c);
}
result.push('"');
Owned(result)
}
fn str_needs_quoting(s: &str) -> bool {
if let Some(c) = s.chars().next() {
if c == '#' || c == '~' {
return true;
}
}
if s.chars().any(char_needs_quoting) {
return true;
}
if s.contains(":~") {
return true;
}
if let Some(i) = s.find('{') {
if s[i + 1..].contains('}') {
return true;
}
}
if let Some(i) = s.find('[') {
if s[i + 1..].contains(']') {
return true;
}
}
false
}
fn char_needs_quoting(c: char) -> bool {
match c {
';' | '&' | '|' | '(' | ')' | '<' | '>' | ' ' | '\t' | '\n' => true,
'$' | '`' | '\\' | '"' | '\'' | '=' | '*' | '?' => true,
_ => c.is_whitespace(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_quoting() {
fn test(s: &str) {
assert_eq!(quote(s), Borrowed(s));
}
test("a");
test("z");
test("_");
test("!#%+,-./:@^~");
test("{");
test("{x");
test("}");
test("x}");
test("}{");
test("[");
test("[x");
test("]");
test("x]");
test("][");
}
#[test]
fn single_quoted() {
fn test(s: &str) {
assert_eq!(quote(s), Owned::<str>(format!("'{}'", s)));
}
test("");
for c in ";&|()<> \t\n\u{3000}$`\\\"=*?#~".chars() {
test(&c.to_string());
}
test("{}");
test("{a}");
test("[]");
test("[a]");
test("foo:~bar");
}
#[test]
fn double_quoted() {
fn test(input: &str, output: &str) {
assert_eq!(quote(input), Owned::<str>(output.to_string()));
}
test("'", r#""'""#);
test(r#"'"'"#, r#""'\"'""#);
test("'$", r#""'\$""#);
test("'foo'", r#""'foo'""#);
test(r#"'\'\\''"#, r#""'\\'\\\\''""#);
test("'{\n}'", "\"'{\n}'\"");
}
}