// Take a look at the license at the top of the repository in the LICENSE file. use std::{cmp, fmt, hash, mem, ops, ptr, slice, str}; use crate::{translate::*, GStr}; wrapper! { // rustdoc-stripper-ignore-next /// A mutable text buffer that grows automatically. #[doc(alias = "GString")] #[must_use = "The builder must be built to be used"] pub struct GStringBuilder(BoxedInline); match fn { copy => |ptr| ffi::g_string_new_len((*ptr).str, (*ptr).len as isize), free => |ptr| ffi::g_string_free(ptr, ffi::GTRUE), init => |ptr| unsafe { let inner = ffi::GString { str: ffi::g_malloc(64) as *mut _, len: 0, allocated_len: 64, }; ptr::write(inner.str, 0); *ptr = inner; }, copy_into => |dest, src| { debug_assert!((*src).allocated_len > (*src).len); let allocated_len = (*src).allocated_len; let inner = ffi::GString { str: ffi::g_malloc(allocated_len) as *mut _, len: 0, allocated_len, }; // +1 to also copy the NUL-terminator ptr::copy_nonoverlapping((*src).str, inner.str, (*src).len + 1); *dest = inner; }, clear => |ptr| { ffi::g_free((*ptr).str as *mut _); }, type_ => || ffi::g_gstring_get_type(), } } unsafe impl Send for GStringBuilder {} unsafe impl Sync for GStringBuilder {} impl GStringBuilder { #[doc(alias = "g_string_new_len")] #[inline] pub fn new>(data: T) -> GStringBuilder { let data = data.as_ref(); assert!(data.len() < usize::MAX - 1); unsafe { let allocated_len = usize::next_power_of_two(std::cmp::max(data.len(), 64) + 1); assert_ne!(allocated_len, 0); let inner = ffi::GString { str: ffi::g_malloc(allocated_len) as *mut _, len: data.len(), allocated_len, }; if data.is_empty() { ptr::write(inner.str, 0); } else { ptr::copy_nonoverlapping(data.as_ptr() as *const _, inner.str, data.len()); ptr::write(inner.str.add(data.len()), 0); } Self { inner } } } #[doc(alias = "g_string_append")] #[doc(alias = "g_string_append_len")] #[inline] pub fn append(&mut self, val: &str) { unsafe { ffi::g_string_append_len( self.to_glib_none_mut().0, val.as_ptr() as *const _, val.len() as isize, ); } } #[doc(alias = "g_string_prepend")] #[doc(alias = "g_string_prepend_len")] #[inline] pub fn prepend(&mut self, val: &str) { unsafe { ffi::g_string_prepend_len( self.to_glib_none_mut().0, val.as_ptr() as *const _, val.len() as isize, ); } } #[doc(alias = "g_string_append_c")] #[doc(alias = "g_string_append_unichar")] #[inline] pub fn append_c(&mut self, val: char) { unsafe { ffi::g_string_append_unichar(self.to_glib_none_mut().0, val.into_glib()); } } #[doc(alias = "g_string_prepend_c")] #[doc(alias = "g_string_prepend_unichar")] #[inline] pub fn prepend_c(&mut self, val: char) { unsafe { ffi::g_string_prepend_unichar(self.to_glib_none_mut().0, val.into_glib()); } } // rustdoc-stripper-ignore-next /// Returns `&[str]` slice. #[inline] pub fn as_str(&self) -> &str { unsafe { let ptr: *const u8 = self.inner.str as _; let len: usize = self.inner.len; if len == 0 { return ""; } let slice = slice::from_raw_parts(ptr, len); std::str::from_utf8_unchecked(slice) } } // rustdoc-stripper-ignore-next /// Returns &[GStr] slice. #[inline] pub fn as_gstr(&self) -> &GStr { unsafe { let ptr: *const u8 = self.inner.str as _; let len: usize = self.inner.len; if len == 0 { return Default::default(); } let slice = slice::from_raw_parts(ptr, len + 1); GStr::from_utf8_with_nul_unchecked(slice) } } // rustdoc-stripper-ignore-next /// Finalizes the builder, converting it to a [`GString`]. #[must_use = "String returned from the builder should probably be used"] #[inline] pub fn into_string(self) -> crate::GString { unsafe { let s = mem::ManuallyDrop::new(self); crate::GString::from_ptr_and_len_unchecked(s.inner.str, s.inner.len) } } } impl Default for GStringBuilder { // rustdoc-stripper-ignore-next /// Creates a new empty string. #[inline] fn default() -> Self { Self::new("") } } impl fmt::Debug for GStringBuilder { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self.as_str()) } } impl fmt::Display for GStringBuilder { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self.as_str()) } } impl PartialEq for GStringBuilder { #[inline] fn eq(&self, other: &Self) -> bool { self.as_str() == other.as_str() } } impl PartialEq for GStringBuilder { #[inline] fn eq(&self, other: &str) -> bool { self.as_str() == other } } impl PartialEq for str { #[inline] fn eq(&self, other: &GStringBuilder) -> bool { self == other.as_str() } } impl PartialEq for GStringBuilder { #[inline] fn eq(&self, other: &GStr) -> bool { self.as_gstr() == other } } impl PartialEq for GStr { #[inline] fn eq(&self, other: &GStringBuilder) -> bool { self == other.as_gstr() } } impl Eq for GStringBuilder {} impl cmp::PartialOrd for GStringBuilder { #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl cmp::PartialOrd for GStringBuilder { #[inline] fn partial_cmp(&self, other: &str) -> Option { Some(self.as_str().cmp(other)) } } impl cmp::PartialOrd for str { #[inline] fn partial_cmp(&self, other: &GStringBuilder) -> Option { Some(self.cmp(other.as_str())) } } impl cmp::PartialOrd for GStringBuilder { #[inline] fn partial_cmp(&self, other: &GStr) -> Option { Some(self.as_gstr().cmp(other)) } } impl cmp::PartialOrd for GStr { #[inline] fn partial_cmp(&self, other: &GStringBuilder) -> Option { Some(self.cmp(other.as_gstr())) } } impl cmp::Ord for GStringBuilder { #[inline] fn cmp(&self, other: &Self) -> cmp::Ordering { self.as_str().cmp(other.as_str()) } } impl hash::Hash for GStringBuilder { #[inline] fn hash(&self, state: &mut H) where H: hash::Hasher, { self.as_str().hash(state) } } impl AsRef<[u8]> for GStringBuilder { #[inline] fn as_ref(&self) -> &[u8] { self.as_bytes() } } impl AsRef for GStringBuilder { #[inline] fn as_ref(&self) -> &str { self.as_str() } } impl AsRef for GStringBuilder { #[inline] fn as_ref(&self) -> &GStr { self.as_gstr() } } impl ops::Deref for GStringBuilder { type Target = str; #[inline] fn deref(&self) -> &str { self.as_str() } } impl fmt::Write for GStringBuilder { #[inline] fn write_str(&mut self, s: &str) -> fmt::Result { self.append(s); Ok(()) } #[inline] fn write_char(&mut self, c: char) -> fmt::Result { self.append_c(c); Ok(()) } } #[cfg(test)] mod tests { #[test] fn append() { let mut s = crate::GStringBuilder::new(""); assert_eq!(&*s, ""); s.append("Hello"); s.append(" "); s.append("there!"); assert_eq!(&*s, "Hello there!"); assert_eq!(s.into_string().as_str(), "Hello there!"); } #[test] fn prepend() { let mut s = crate::GStringBuilder::new("456"); assert_eq!(&*s, "456"); s.prepend("123"); assert_eq!(&*s, "123456"); } #[test] fn default() { let s1: crate::GStringBuilder = Default::default(); assert_eq!(&*s1, ""); } #[test] fn display() { let s: crate::GStringBuilder = crate::GStringBuilder::new("This is a string."); assert_eq!(&format!("{s}"), "This is a string."); } #[test] fn eq() { let a1 = crate::GStringBuilder::new("a"); let a2 = crate::GStringBuilder::new("a"); let b = crate::GStringBuilder::new("b"); assert_eq!(a1, a2); assert_ne!(a1, b); assert_ne!(a2, b); } #[test] fn write() { use std::fmt::Write; let mut s = crate::GStringBuilder::default(); write!(&mut s, "bla bla {} bla", 123).unwrap(); assert_eq!(&*s, "bla bla 123 bla"); } }