use std::fmt::Write;
pub enum QuoteStyle {
Single,
Double,
}
pub struct Quoted<T> {
style: QuoteStyle,
value: T,
}
impl<T: AsRef<[u8]>> std::fmt::Display for Quoted<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.style {
QuoteStyle::Single => write!(f, "'")?,
QuoteStyle::Double => write!(f, "\"")?,
}
for &b in self.value.as_ref() {
match (&self.style, b) {
(QuoteStyle::Single, b'\'') => f.write_str("'\\''")?,
(QuoteStyle::Double, b'"') => f.write_str("\\\"")?,
(_, b'\\') => f.write_str("\\\\")?,
(_, b'\n') => f.write_str("\\n")?,
(_, b'\r') => f.write_str("\\r")?,
(_, b'\t') => f.write_str("\\t")?,
(_, b) if b.is_ascii_graphic() || b == b' ' => f.write_char(b as char)?,
(_, b) => write!(f, "\\x{:02x}", b)?,
}
}
match self.style {
QuoteStyle::Single => write!(f, "'"),
QuoteStyle::Double => write!(f, "\""),
}
}
}
pub fn quoted<T: AsRef<[u8]>>(value: T, style: QuoteStyle) -> Quoted<T> {
Quoted { value, style }
}
pub fn q<B: AsRef<[u8]>>(value: B) -> Quoted<B> {
Quoted {
value,
style: QuoteStyle::Single,
}
}
pub fn qq<B: AsRef<[u8]>>(value: B) -> Quoted<B> {
Quoted {
value,
style: QuoteStyle::Double,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_double() {
let p = b"foo\nbar\"baz";
let s = Quoted {
style: QuoteStyle::Double,
value: p.as_slice(),
}
.to_string();
let expected = "\"foo\\nbar\\\"baz\"";
assert_eq!(s, expected);
let data = b"abc\\def\x01";
let quoted = qq(data).to_string();
let expected = "\"abc\\\\def\\x01\"";
assert_eq!(quoted, expected);
}
#[test]
fn test_empty_and_all_ascii() {
let empty = b"";
assert_eq!(qq(empty).to_string(), "\"\"");
let ascii: Vec<u8> = (0x20..=0x7e).collect();
let quoted = qq(&ascii).to_string();
let expected = format!("{:?}", String::from_utf8_lossy(&ascii));
assert_eq!(quoted, expected);
}
#[test]
fn test_control_and_non_ascii() {
let bytes = b"\x00\x1f\x7f\xc0";
let quoted = quoted(bytes, QuoteStyle::Double).to_string();
let expected = "\"\\x00\\x1f\\x7f\\xc0\"";
assert_eq!(quoted, expected);
}
}