use std::borrow::Cow;
use std::fmt;
use std::io;
use std::str;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
#[repr(transparent)]
pub struct Encoded<Str>(pub Str);
impl<Str: AsRef<[u8]>> Encoded<Str> {
#[inline(always)]
pub fn new(string: Str) -> Self {
Self(string)
}
#[inline(always)]
pub fn to_str(&self) -> Cow<str> {
encode_binary(self.0.as_ref())
}
#[inline]
#[allow(clippy::inherent_to_string_shadow_display)]
pub fn to_string(&self) -> String {
self.to_str().into_owned()
}
#[inline]
pub fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
encode_into(self.0.as_ref(), false, |s| writer.write_all(s.as_bytes()))?;
Ok(())
}
#[inline]
pub fn append_to(&self, string: &mut String) {
append_string(self.0.as_ref(), string, false);
}
}
impl<'a> Encoded<&'a str> {
#[inline(always)]
pub fn str(string: &'a str) -> Self {
Self(string)
}
}
impl<String: AsRef<[u8]>> fmt::Display for Encoded<String> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
encode_into(self.0.as_ref(), false, |s| f.write_str(s))?;
Ok(())
}
}
#[inline(always)]
pub fn encode(data: &str) -> Cow<str> {
encode_binary(data.as_bytes())
}
#[inline]
pub fn encode_binary(data: &[u8]) -> Cow<str> {
let mut escaped = String::with_capacity(data.len() | 15);
let unmodified = append_string(data, &mut escaped, true);
if unmodified {
return Cow::Borrowed(unsafe {
str::from_utf8_unchecked(data)
});
}
Cow::Owned(escaped)
}
fn append_string(data: &[u8], escaped: &mut String, may_skip: bool) -> bool {
encode_into(data, may_skip, |s| {
escaped.push_str(s);
Ok::<_, std::convert::Infallible>(())
}).unwrap()
}
fn encode_into<E>(mut data: &[u8], may_skip_write: bool, mut push_str: impl FnMut(&str) -> Result<(), E>) -> Result<bool, E> {
let mut pushed = false;
loop {
let ascii_len = data.iter()
.take_while(|&&c| matches!(c, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'-' | b'.' | b'_' | b'~')).count();
let (safe, rest) = if ascii_len >= data.len() {
if !pushed && may_skip_write {
return Ok(true);
}
(data, &[][..]) } else {
data.split_at(ascii_len)
};
pushed = true;
if !safe.is_empty() {
push_str(unsafe { str::from_utf8_unchecked(safe) })?;
}
if rest.is_empty() {
break;
}
match rest.split_first() {
Some((byte, rest)) => {
let enc = &[b'%', to_hex_digit(byte >> 4), to_hex_digit(byte & 15)];
push_str(unsafe { str::from_utf8_unchecked(enc) })?;
data = rest;
}
None => break,
};
}
Ok(false)
}
#[inline]
fn to_hex_digit(digit: u8) -> u8 {
match digit {
0..=9 => b'0' + digit,
10..=255 => b'A' - 10 + digit,
}
}