text_utils/utils/
text_escape.rs

1use crate::{Result, TextError};
2use std::intrinsics::transmute;
3
4/// Takes in a string with backslash escapes written out with literal backslash characters and
5/// converts it to a string with the proper escaped characters.
6///
7/// | Escape | Unicode | Description                    |
8///  | ------ | ------- | ------------------------------ |
9///  | \b     | \u{08}  | Backspace                      |
10///  | \v     | \u{0B}  | Vertical tab                   |
11///  | \f     | \u{0C}  | Form feed                      |
12///  | \n     | \u{0A}  | Newline                        |
13///  | \r     | \u{0D}  | Carriage return                |
14///  | \t     | \u{09}  | Tab                            |
15///  | \\     | \u{5C}  | Backslash                      |
16///  | \'     | \u{27}  | Single quote                   |
17///  | \"     | \u{22}  | Double quote                   |
18///  | \$     | \u{24}  | Dollar sign (sh compatibility) |
19///  | \`     | \u{60}  | Backtick (sh compatibility)    |
20///  | other  | self    | Just remove `\`                |
21pub fn unescape(text: impl AsRef<str>) -> Result<String> {
22    let text = text.as_ref();
23    let mut out = String::with_capacity(text.len());
24    let mut chars = text.chars().enumerate();
25    while let Some((index, c)) = chars.next() {
26        if c != '\\' {
27            out.push(c);
28            continue;
29        }
30        if let Some(next) = chars.next() {
31            match escape_chars(next.1) {
32                Some(c) => out.push(c),
33                None => {
34                    return TextError::unescape_error(index, format!("\\{}", next.1));
35                }
36            }
37        }
38    }
39    Ok(out)
40}
41
42/// unchecked version of unescape
43///
44///
45/// ### Safety
46///
47/// transmute unescape_unicode_char
48pub unsafe fn unescape_unchecked(text: impl AsRef<str>) -> String {
49    let text = text.as_ref();
50    let mut out = String::with_capacity(text.len());
51    let mut chars = text.chars();
52    while let Some(c) = chars.next() {
53        if c != '\\' {
54            out.push(c);
55            continue;
56        }
57        if let Some(next) = chars.next() {
58            match escape_chars(next) {
59                Some(c) => out.push(c),
60                None => out.push(next),
61            }
62        }
63    }
64    return out;
65}
66
67fn escape_chars(c: char) -> Option<char> {
68    match c {
69        'b' => Some('\u{0008}'),
70        'f' => Some('\u{000C}'),
71        'n' => Some('\n'),
72        'r' => Some('\r'),
73        't' => Some('\t'),
74        '\'' => Some('\''),
75        '\"' => Some('\"'),
76        '\\' => Some('\\'),
77        _ => None,
78    }
79}
80
81/// unescape_utf8
82#[allow(unused_variables)]
83pub fn unescape_utf8(text: impl AsRef<str>) {
84    unimplemented!()
85}
86/// unescape_only
87#[allow(unused_variables)]
88pub fn unescape_only(text: impl AsRef<str>, c: char) {
89    unimplemented!()
90}
91
92/// unescape \U{xx xx xx}
93pub fn unescape_hex_chars(text: impl AsRef<str>) -> Option<String> {
94    unsafe { unescape_unicode_char(text.as_ref(), 16) }
95}
96
97/// unescape \u{xx xx xx}
98pub fn unescape_dec_chars(text: impl AsRef<str>) -> Option<String> {
99    unsafe { unescape_unicode_char(text.as_ref(), 10) }
100}
101
102unsafe fn unescape_unicode_char(text: &str, radix: u32) -> Option<String> {
103    let mut out = String::with_capacity(text.len());
104    for c in text.split_whitespace() {
105        match u32::from_str_radix(c, radix) {
106            Ok(o) => out.push(transmute::<u32, char>(o)),
107            Err(_) => return None,
108        }
109    }
110    Some(out)
111}