tui_markup/generator/helper/
unescape.rs

1/// Convert a escaped string into a iterator of unescaped strings.
2///
3/// In implementation, the returned iterator will skip any `\` character
4/// except itself is after a skipped `\`.
5///
6/// ## Example
7///
8/// ```
9/// # use tui_markup::generator::helper::unescape;
10/// assert_eq!(unescape("\\>").collect::<Vec<_>>(), vec![">"]);
11/// assert_eq!(unescape("1\\<2").collect::<Vec<_>>(), vec!["1", "<2"]);
12///
13/// // notice the final `\` is not returned
14/// assert_eq!(unescape("A\\\\B\\").collect::<Vec<_>>(), vec!["A", "\\B"]);
15/// ```
16#[must_use]
17pub fn unescape(escaped: &str) -> Unescape {
18    let cursor = escaped.starts_with('\\').into();
19    Unescape { escaped, cursor }
20}
21
22/// Iterator type for [unescape].
23#[derive(Debug)]
24pub struct Unescape<'a> {
25    escaped: &'a str,
26    cursor: usize,
27}
28
29impl<'a> Iterator for Unescape<'a> {
30    type Item = &'a str;
31
32    fn next(&mut self) -> Option<Self::Item> {
33        if self.cursor >= self.escaped.len() {
34            return None;
35        }
36
37        let start = self.cursor + 1;
38        let end = if start >= self.escaped.len() {
39            self.escaped.len()
40        } else {
41            self.escaped[start..]
42                .find('\\')
43                .map_or(self.escaped.len(), |i| start + i)
44        };
45
46        let result = Some(&self.escaped[self.cursor..end]);
47        self.cursor = end + 1;
48
49        result
50    }
51}
52
53#[cfg(test)]
54mod test {
55    macro_rules! test_unescape {
56        ($escaped:expr => $($result:expr),* $(,)?) => {
57           assert_eq!(crate::generator::helper::unescape($escaped).collect::<Vec<_>>(), vec![$($result),+])
58        };
59    }
60
61    #[test]
62    fn test_escaped_string_at_begin() {
63        test_unescape!("\\<b" => "<b");
64        test_unescape!("\\>b" => ">b");
65        test_unescape!("\\\\b" => "\\b");
66    }
67
68    #[test]
69    fn test_escaped_string_at_middle() {
70        test_unescape!("a\\<b" => "a", "<b");
71        test_unescape!("a\\>b" => "a", ">b");
72        test_unescape!("a\\\\b" => "a", "\\b");
73    }
74
75    #[test]
76    fn test_escaped_string_at_end() {
77        test_unescape!("a\\<" => "a", "<");
78        test_unescape!("a\\>" => "a", ">");
79        test_unescape!("a\\\\" => "a", "\\");
80    }
81
82    #[test]
83    fn test_escaped_string_multi() {
84        test_unescape!("1\\<2\\<3 \\\\ 3\\>2\\>1" => "1", "<2", "<3 ", "\\ 3", ">2", ">1");
85    }
86}