1mod error;
15
16pub use error::Error;
17
18pub fn enquote(quote: char, s: &str) -> String {
20 let escaped = s
22 .chars()
23 .map(|c| match c {
24 _ if c == quote => format!("\\{}", quote),
26 '\\' => "\\\\".into(),
28 _ => c.to_string(),
30 })
31 .collect::<String>();
32
33 quote.to_string() + &escaped + "e.to_string()
35}
36
37pub fn unquote(s: &str) -> Result<String, Error> {
39 if s.chars().count() < 2 {
40 return Err(Error::NotEnoughChars);
41 }
42
43 let quote = s.chars().next().unwrap();
44
45 if quote != '"' && quote != '\'' && quote != '`' {
46 return Err(Error::UnrecognizedQuote);
47 }
48
49 if s.chars().last().unwrap() != quote {
50 return Err(Error::UnexpectedEOF);
51 }
52
53 let s = &s[1..s.len() - 1];
57
58 unescape(s, Some(quote))
59}
60
61pub fn unescape(s: &str, illegal: Option<char>) -> Result<String, Error> {
63 let mut chars = s.chars();
64 let mut unescaped = String::new();
65 loop {
66 match chars.next() {
67 None => break,
68 Some(c) => unescaped.push(match c {
69 _ if Some(c) == illegal => return Err(Error::IllegalChar),
70 '\\' => match chars.next() {
71 None => return Err(Error::UnexpectedEOF),
72 Some(c) => match c {
73 _ if c == '\\' || c == '"' || c == '\'' || c == '`' => c,
74 'a' => '\x07',
75 'b' => '\x08',
76 'f' => '\x0c',
77 'n' => '\n',
78 'r' => '\r',
79 't' => '\t',
80 'v' => '\x0b',
81 '0'..='9' => {
83 let octal = c.to_string() + &take(&mut chars, 2);
84 u8::from_str_radix(&octal, 8).map_err(|_| Error::UnrecognizedEscape)?
85 as char
86 }
87 'x' => {
89 let hex = take(&mut chars, 2);
90 u8::from_str_radix(&hex, 16).map_err(|_| Error::UnrecognizedEscape)?
91 as char
92 }
93 'u' => decode_unicode(&take(&mut chars, 4))?,
95 'U' => decode_unicode(&take(&mut chars, 8))?,
96 _ => return Err(Error::UnrecognizedEscape),
97 },
98 },
99 _ => c,
100 }),
101 }
102 }
103
104 Ok(unescaped)
105}
106
107#[inline]
108fn take<I: Iterator<Item = char>>(iterator: &mut I, n: usize) -> String {
110 let mut s = String::with_capacity(n);
111 for _ in 0..n {
112 s.push(iterator.next().unwrap_or_default());
113 }
114 s
115}
116
117fn decode_unicode(code_point: &str) -> Result<char, Error> {
118 match u32::from_str_radix(code_point, 16) {
119 Err(_) => return Err(Error::UnrecognizedEscape),
120 Ok(n) => std::char::from_u32(n).ok_or(Error::InvalidUnicode),
121 }
122}