use std::borrow::Cow::{self, Borrowed, Owned};
#[must_use]
fn char_needs_quoting(c: char) -> bool {
match c {
';' | '&' | '|' | '(' | ')' | '<' | '>' | ' ' | '\t' | '\n' => true,
'$' | '`' | '\\' | '"' | '\'' | '=' | '*' | '?' => true,
_ => c.is_whitespace(),
}
}
#[must_use]
fn str_needs_quoting(s: &str) -> bool {
if s.is_empty() {
return true;
}
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
}
#[derive(Clone, Copy, Debug)]
#[must_use = "`Quoted` does nothing unless printed"]
pub struct Quoted<'a> {
raw: &'a str,
needs_quoting: bool,
}
impl<'a> Quoted<'a> {
#[inline]
#[must_use]
pub fn as_raw(&self) -> &'a str {
self.raw
}
#[inline]
#[must_use]
pub fn needs_quoting(&self) -> bool {
self.needs_quoting
}
}
impl std::fmt::Display for Quoted<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use std::fmt::Write;
if !self.needs_quoting {
f.write_str(self.raw)
} else if !self.raw.contains('\'') {
write!(f, "'{}'", self.raw)
} else {
f.write_char('"')?;
for c in self.raw.chars() {
if matches!(c, '"' | '`' | '$' | '\\') {
f.write_char('\\')?;
}
f.write_char(c)?;
}
f.write_char('"')
}
}
}
impl<'a> From<&'a str> for Quoted<'a> {
#[inline]
fn from(raw: &'a str) -> Self {
let needs_quoting = str_needs_quoting(raw);
Quoted { raw, needs_quoting }
}
}
impl<'a> From<Quoted<'a>> for Cow<'a, str> {
#[must_use]
fn from(q: Quoted<'a>) -> Self {
if q.needs_quoting() {
Owned(q.to_string())
} else {
Borrowed(q.as_raw())
}
}
}
#[inline]
pub fn quoted(raw: &str) -> Quoted {
Quoted::from(raw)
}
#[inline]
#[must_use]
pub fn quote(raw: &str) -> Cow<'_, str> {
quoted(raw).into()
}
#[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}'\"");
}
}