1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
use cfg_if::cfg_if;
use std::ffi::OsStr;
use std::str::{from_utf8, from_utf8_unchecked};
/// # Examples
/// ```
/// use bczhc_lib::str::escape_utf8_bytes;
///
/// assert_eq!(escape_utf8_bytes(b"normal"), "normal");
/// assert_eq!(escape_utf8_bytes(b"\xE6\x9D"), "\\xE6\\x9D");
/// assert_eq!(escape_utf8_bytes(b"\xE5\xB7ab\nc"), "\\xE5\\xB7ab\\nc");
/// assert_eq!(escape_utf8_bytes(b"a\nb\\c"), "a\\nb\\\\c");
/// ```
pub fn escape_utf8_bytes(data: &[u8]) -> String {
use std::fmt::Write;
let mut str_buf = String::new();
let escape_bytes_to = |sb: &mut String, bytes: &[u8]| {
for x in bytes {
write!(sb, "\\x{:X}", x).unwrap();
}
};
let escape_non_printable_char_to = |sb: &mut String, c: char| match c {
'\t' => write!(sb, "\\t").unwrap(),
'\n' => write!(sb, "\\n").unwrap(),
'\r' => write!(sb, "\\r").unwrap(),
'\\' => write!(sb, "\\\\").unwrap(),
_ => write!(sb, "{}", c).unwrap(),
};
let escape_non_printable_str_to = |sb: &mut String, c: &str| {
for c in c.chars() {
escape_non_printable_char_to(sb, c);
}
};
let mut start = 0_usize;
loop {
let result = from_utf8(&data[start..]);
match result {
Ok(s) => {
// UTF-8 check through all the bytes is passed
escape_non_printable_str_to(&mut str_buf, s);
break;
}
Err(e) => {
let valid_len = e.valid_up_to();
if valid_len != 0 {
// has some valid bytes
unsafe {
// SAFETY: after the check from the safe `from_utf8` function
let str = from_utf8_unchecked(&data[start..(start + valid_len)]);
escape_non_printable_str_to(&mut str_buf, str);
}
start += valid_len;
} else {
// no valid bytes, escape the erroneous bytes
let error_len = e.error_len();
if let Some(len) = error_len {
escape_bytes_to(&mut str_buf, &data[start..(start + len)]);
start += len;
} else {
// unexpected EOF, escape all the remaining bytes
escape_bytes_to(&mut str_buf, &data[start..]);
break;
}
}
}
}
}
str_buf
}
pub trait GenericOsStrExt {
fn escape(&self) -> String;
}
impl<T> GenericOsStrExt for T
where
T: AsRef<OsStr>,
{
fn escape(&self) -> String {
cfg_if! {
if #[cfg(unix)] {
use std::os::unix::ffi::OsStrExt;
escape_utf8_bytes(self.as_ref().as_bytes())
} else {
escape_utf8_bytes(self.as_ref().to_str().expect("Invalid UTF-8 meets").as_bytes())
}
}
}
}