1use anyhow::{Context, Result};
2use std::str::FromStr;
3
4fn should_encode(c: char) -> bool {
5 !(c != 'x' && c != ':' && c != '\\' && c.is_ascii_graphic() && !c.is_ascii_whitespace())
6}
7
8struct EncodedChar(char);
9impl ToString for EncodedChar {
10 fn to_string(&self) -> String {
11 format!("x{:02x}", self.0 as u8)
12 }
13}
14
15impl FromStr for EncodedChar {
16 type Err = anyhow::Error;
17
18 fn from_str(str: &str) -> Result<Self, Self::Err> {
19 let str_chars: Vec<char> = str.chars().collect();
20 let chars: String = str_chars[1..=2].iter().collect();
21 let c = u8::from_str_radix(&chars, 16)
22 .with_context(|| format!("Unable to convert `{}` to u8", &chars))?
23 as char;
24
25 Ok(Self(c))
26 }
27}
28
29pub fn encode(str: String) -> String {
30 str.chars()
31 .map(|c| {
32 if should_encode(c) {
33 EncodedChar(c).to_string()
34 } else {
35 c.to_string()
36 }
37 })
38 .collect()
39}
40
41pub fn decode(str: String) -> Result<String> {
42 let str_chars: Vec<char> = str.chars().collect();
43
44 Ok(str
45 .match_indices('x')
46 .map(|(i, _)| -> Result<(String, String)> {
47 let chars: String = str_chars[i..=i + 2].iter().collect();
48 let encoded = EncodedChar::from_str(&chars)?;
49 Ok((chars, encoded.0.to_string()))
50 })
51 .filter_map(Result::ok)
52 .fold(str.to_owned(), |str, (replace, replacement)| {
53 str.replace(&replace, &replacement)
54 }))
55}