enquote/
lib.rs

1//! This Rust library quotes, unquotes, and unescapes strings.
2//!
3//! # Examples
4//! ```
5//! extern crate enquote;
6//!
7//! fn main() {
8//!     assert_eq!(enquote::enquote('\'', "foo'bar"), "'foo\\'bar'");
9//!     assert_eq!(enquote::unquote("'foo\\'bar\\n'").unwrap(), "foo'bar\n");
10//!     assert_eq!(enquote::unescape("\\n", None).unwrap(), "\n");
11//! }
12//! ```
13
14mod error;
15
16pub use error::Error;
17
18/// Enquotes `s` with `quote`.
19pub fn enquote(quote: char, s: &str) -> String {
20    // escapes any `quote` in `s`
21    let escaped = s
22        .chars()
23        .map(|c| match c {
24            // escapes the character if it's the quote
25            _ if c == quote => format!("\\{}", quote),
26            // escapes backslashes
27            '\\' => "\\\\".into(),
28            // no escape required
29            _ => c.to_string(),
30        })
31        .collect::<String>();
32
33    // enquotes escaped string
34    quote.to_string() + &escaped + &quote.to_string()
35}
36
37/// Unquotes `s`.
38pub 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    // removes quote characters
54    // the sanity checks performed above ensure that the quotes will be ASCII and this will not
55    // panic
56    let s = &s[1..s.len() - 1];
57
58    unescape(s, Some(quote))
59}
60
61/// Returns `s` after processing escapes such as `\n` and `\x00`.
62pub 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                        // octal
82                        '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                        // hex
88                        'x' => {
89                            let hex = take(&mut chars, 2);
90                            u8::from_str_radix(&hex, 16).map_err(|_| Error::UnrecognizedEscape)?
91                                as char
92                        }
93                        // unicode
94                        '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]
108// Iterator#take cannot be used because it consumes the iterator
109fn 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}