use std::{cmp, fmt, mem, ops, str};
use crate::util::{split_f, FromBytes};
#[derive(Eq, Ord, Hash)]
pub struct CStr {
bytes: [u8],
}
impl CStr {
pub fn empty() -> &'static CStr {
unsafe { CStr::from_bytes_unchecked(&[0]) }
}
pub fn from_bytes(bytes: &[u8]) -> Option<&CStr> {
let len = bytes.iter().position(|&byte| byte == 0)?;
Some(unsafe { CStr::from_bytes_unchecked(bytes.get_unchecked(..len + 1)) })
}
pub unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &CStr {
mem::transmute(bytes)
}
pub fn c_str(&self) -> &[u8] {
&self.bytes
}
pub fn to_str(&self) -> Result<&str, str::Utf8Error> {
str::from_utf8(self.as_ref())
}
}
impl FromBytes for CStr {
const MIN_SIZE_OF: usize = 0;
const ALIGN_OF: usize = 1;
unsafe fn from_bytes(bytes: &[u8]) -> Option<&CStr> {
CStr::from_bytes(bytes)
}
}
impl<T: AsRef<[u8]> + ?Sized> PartialEq<T> for CStr {
fn eq(&self, rhs: &T) -> bool {
self.as_ref() == rhs.as_ref()
}
}
impl<T: AsRef<[u8]> + ?Sized> PartialOrd<T> for CStr {
fn partial_cmp(&self, rhs: &T) -> Option<cmp::Ordering> {
self.as_ref().partial_cmp(rhs.as_ref())
}
}
impl ops::Deref for CStr {
type Target = [u8];
fn deref(&self) -> &[u8] {
self.as_ref()
}
}
impl AsRef<[u8]> for CStr {
fn as_ref(&self) -> &[u8] {
let len = self.bytes.len() - 1;
unsafe { self.bytes.get_unchecked(..len) }
}
}
impl fmt::Debug for CStr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("\"")?;
let mut bytes = self.as_ref();
while bytes.len() > 0 {
let byte = bytes[0];
match byte {
b'\0' => {
bytes = &bytes[1..];
f.write_str("\\0")?;
},
b'\n' => {
bytes = &bytes[1..];
f.write_str("\\n")?;
},
b'\r' => {
bytes = &bytes[1..];
f.write_str("\\r")?;
},
b'\t' => {
bytes = &bytes[1..];
f.write_str("\\t")?;
},
b'"' => {
bytes = &bytes[1..];
f.write_str("\\\"")?;
},
b'\\' => {
bytes = &bytes[1..];
f.write_str("\\\\")?;
},
0x20...0x7E => {
let (s, tail) = split_f(bytes, |&byte| byte < 0x20 || byte >= 0x80 || byte == b'"' || byte == b'\\');
bytes = tail;
let s = unsafe { str::from_utf8_unchecked(s) };
f.write_str(s)?;
},
_ => {
let (s, tail) = split_f(bytes, |&byte| byte >= 0x20 && byte < 0x80);
bytes = tail;
for &byte in s {
write!(f, "\\x{:02X}", byte)?;
}
},
};
}
f.write_str("\"")
}
}
impl fmt::Display for CStr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut bytes = self.as_ref();
while bytes.len() > 0 {
let byte = bytes[0];
if byte < 0x80 {
let (s, tail) = split_f(bytes, |&byte| byte >= 0x80);
bytes = tail;
let s = unsafe { str::from_utf8_unchecked(s) };
f.write_str(s)?;
}
else {
let (s, tail) = split_f(bytes, |&byte| byte < 0x80);
bytes = tail;
for &byte in s {
write!(f, "\\x{:02X}", byte)?;
}
}
}
Ok(())
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for CStr {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(test)]
mod tests {
use super::CStr;
#[test]
fn from_bytes() {
assert_eq!(CStr::from_bytes(b"this is a c str\0").unwrap().c_str(), b"this is a c str\0");
assert_eq!(CStr::from_bytes(b"no nul terminator"), None);
assert_eq!(CStr::from_bytes(b"valid utf8\0").unwrap().to_str(), Ok("valid utf8"));
assert_eq!(CStr::from_bytes(b"length is eighteen\0").unwrap().len(), 18);
assert_eq!(CStr::from_bytes(b"length is eighteen\0").unwrap().len(), 18);
}
#[test]
fn fmt() {
assert_eq!(format!("{}", unsafe { CStr::from_bytes_unchecked(b"\tabc\n\xFFhello\x80world\0") }), "\tabc\n\\xFFhello\\x80world");
assert_eq!(format!("{:?}", unsafe { CStr::from_bytes_unchecked(b"\tabc\n\xFFhello\x80world\0") }), r#""\tabc\n\xFFhello\x80world""#);
}
}