use alloc::{
alloc::handle_alloc_error,
borrow::Cow,
collections::TryReserveError,
ffi::{CString, IntoStringError, NulError},
string::String,
};
use core::{
alloc::Layout,
borrow::Borrow,
ffi::CStr,
fmt,
str::{self, FromStr},
};
#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub struct WriteBuffer {
buffer: String,
}
impl WriteBuffer {
const REPLACEMENT_STR: &str = "�";
const TERMINATION_STR: &str = "\0";
pub const fn new() -> Self {
Self {
buffer: String::new(),
}
}
pub fn with_capacity(cap: usize) -> Self {
Self {
buffer: String::with_capacity(cap),
}
}
pub fn as_str(&self) -> &str {
match self.buffer.as_bytes().split_last() {
Some((last, rest)) => {
debug_assert_eq!(*last, 0);
match cfg!(debug_assertions) {
true => str::from_utf8(rest).unwrap(),
false => unsafe { str::from_utf8_unchecked(rest) },
}
}
None => "",
}
}
pub fn as_cstr(&self) -> &CStr {
if self.buffer.is_empty() {
return c"";
}
let bytes = self.buffer.as_bytes();
match cfg!(debug_assertions) {
true => CStr::from_bytes_with_nul(bytes).unwrap(),
false => unsafe { CStr::from_bytes_with_nul_unchecked(bytes) },
}
}
pub fn clear(&mut self) {
unsafe { self.buffer_mut() }.clear();
}
pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
unsafe { self.buffer_mut() }.try_reserve(additional)
}
unsafe fn buffer_mut(&mut self) -> &mut String {
&mut self.buffer
}
unsafe fn try_push_str(&mut self, s: &str) -> fmt::Result {
let Some(len) = s.len().checked_add(Self::TERMINATION_STR.len()) else {
return Err(fmt::Error);
};
match self.try_reserve(len) {
Ok(()) => {
unsafe { self.buffer_mut() }.push_str(s);
Ok(())
}
Err(_) => Err(fmt::Error),
}
}
fn terminate(&mut self) {
unsafe { self.buffer_mut() }.push_str(Self::TERMINATION_STR);
}
}
impl fmt::Write for WriteBuffer {
fn write_str(&mut self, s: &str) -> fmt::Result {
unsafe { self.buffer_mut() }.pop();
let wrote = (|| {
let mut spans = s.split('\0').peekable();
while let Some(span) = spans.next() {
unsafe {
self.try_push_str(span)?;
if spans.peek().is_some() {
self.try_push_str(Self::REPLACEMENT_STR)?
}
}
}
Ok(())
})();
match wrote {
Ok(()) => {
self.terminate();
Ok(())
}
Err(fmt::Error) => {
unsafe { self.buffer_mut().pop() };
self.terminate();
Err(fmt::Error)
}
}
}
}
impl fmt::Display for WriteBuffer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl FromStr for WriteBuffer {
type Err = NulError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Ok(WriteBuffer::new());
}
Ok(CString::new(s)?.try_into().unwrap())
}
}
impl From<&str> for WriteBuffer {
fn from(value: &str) -> Self {
let mut this = Self::new();
match fmt::write(&mut this, format_args!("{}", value)) {
Ok(()) => this,
Err(fmt::Error) => handle_alloc_error(Layout::for_value(value)),
}
}
}
impl From<WriteBuffer> for Cow<'static, CStr> {
fn from(value: WriteBuffer) -> Self {
match value.buffer.is_empty() {
true => Cow::Borrowed(c""),
false => Cow::Owned(match cfg!(debug_assertions) {
true => CString::from_vec_with_nul(value.buffer.into()).unwrap(),
false => unsafe { CString::from_vec_with_nul_unchecked(value.buffer.into()) },
}),
}
}
}
impl From<WriteBuffer> for String {
fn from(value: WriteBuffer) -> Self {
let mut buffer = value.buffer;
buffer.pop();
buffer
}
}
impl From<WriteBuffer> for CString {
fn from(value: WriteBuffer) -> Self {
Cow::from(value).into_owned()
}
}
impl TryFrom<CString> for WriteBuffer {
type Error = IntoStringError;
fn try_from(value: CString) -> Result<Self, Self::Error> {
let mut buffer = CString::into_string(value)?;
buffer.push_str(Self::TERMINATION_STR);
Ok(Self { buffer })
}
}
impl AsRef<str> for WriteBuffer {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<CStr> for WriteBuffer {
fn as_ref(&self) -> &CStr {
self.as_cstr()
}
}
impl Borrow<str> for WriteBuffer {
fn borrow(&self) -> &str {
self.as_str()
}
}
impl Borrow<CStr> for WriteBuffer {
fn borrow(&self) -> &CStr {
self.as_cstr()
}
}
impl PartialEq<str> for WriteBuffer {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<WriteBuffer> for str {
fn eq(&self, other: &WriteBuffer) -> bool {
other == self
}
}
impl PartialEq<CStr> for WriteBuffer {
fn eq(&self, other: &CStr) -> bool {
self.as_cstr() == other
}
}
impl PartialEq<WriteBuffer> for CStr {
fn eq(&self, other: &WriteBuffer) -> bool {
other == self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let mut buf = WriteBuffer::from("hello");
assert_eq!(&buf, "hello");
assert_eq!(&buf, c"hello");
fmt::write(&mut buf, format_args!("\0world!")).unwrap();
assert_eq!(&buf, "hello�world!");
assert_eq!(&buf, c"hello�world!");
}
}