use crate::fs::path::Path; use crate::fs::SEPARATOR; use crate::{CStr16, CString16, Char16}; use core::fmt::{Display, Formatter}; /// A path buffer similar to the `PathBuf` of the standard library, but based on /// [`CString16`] strings and [`SEPARATOR`] as separator. /// /// `/` is replaced by [`SEPARATOR`] on the fly. #[derive(Clone, Debug, Default, Eq, PartialOrd, Ord)] pub struct PathBuf(CString16); impl PathBuf { /// Constructor. #[must_use] pub fn new() -> Self { Self::default() } /// Constructor that replaces all occurrences of `/` with `\`. fn new_from_cstring16(mut string: CString16) -> Self { const SEARCH: Char16 = unsafe { Char16::from_u16_unchecked('/' as u16) }; string.replace_char(SEARCH, SEPARATOR); Self(string) } /// Extends self with path. /// /// UNIX separators (`/`) will be replaced by [`SEPARATOR`] on the fly. pub fn push>(&mut self, path: P) { const SEARCH: Char16 = unsafe { Char16::from_u16_unchecked('/' as u16) }; // do nothing on empty path if path.as_ref().is_empty() { return; } let empty = self.0.is_empty(); let needs_sep = *self .0 .as_slice_with_nul() .last() .expect("Should have at least null character") != SEPARATOR; if !empty && needs_sep { self.0.push(SEPARATOR) } self.0.push_str(path.as_ref().to_cstr16()); self.0.replace_char(SEARCH, SEPARATOR); } } impl PartialEq for PathBuf { fn eq(&self, other: &Self) -> bool { let path1: &Path = self.as_ref(); let path2: &Path = other.as_ref(); path1 == path2 } } impl Display for PathBuf { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { Display::fmt(self.to_cstr16(), f) } } mod convenience_impls { use super::*; use core::borrow::Borrow; use core::ops::Deref; impl From for PathBuf { fn from(value: CString16) -> Self { Self::new_from_cstring16(value) } } impl From<&CStr16> for PathBuf { fn from(value: &CStr16) -> Self { Self::new_from_cstring16(CString16::from(value)) } } impl Deref for PathBuf { type Target = Path; fn deref(&self) -> &Self::Target { Path::new(&self.0) } } impl AsRef for PathBuf { fn as_ref(&self) -> &Path { // falls back to deref impl self } } impl Borrow for PathBuf { fn borrow(&self) -> &Path { // falls back to deref impl self } } impl AsRef for PathBuf { fn as_ref(&self) -> &CStr16 { &self.0 } } impl Borrow for PathBuf { fn borrow(&self) -> &CStr16 { &self.0 } } } #[cfg(test)] mod tests { use super::*; use crate::cstr16; use alloc::string::ToString; #[test] fn from_cstr16() { let source: &CStr16 = cstr16!("\\hello\\foo\\bar"); let _path: PathBuf = source.into(); } #[test] fn from_cstring16() { let source = CString16::try_from("\\hello\\foo\\bar").unwrap(); let _path: PathBuf = source.as_ref().into(); let _path: PathBuf = source.clone().into(); let _path: PathBuf = PathBuf::new_from_cstring16(source); } #[test] fn from_std_string() { let std_string = "\\hello\\foo\\bar".to_string(); let _path = PathBuf::new_from_cstring16(CString16::try_from(std_string.as_str()).unwrap()); } #[test] fn push() { let mut pathbuf = PathBuf::new(); pathbuf.push(cstr16!("first")); pathbuf.push(cstr16!("second")); pathbuf.push(cstr16!("third")); assert_eq!(pathbuf.to_cstr16(), cstr16!("first\\second\\third")); let mut pathbuf = PathBuf::new(); pathbuf.push(cstr16!("\\first")); pathbuf.push(cstr16!("second")); assert_eq!(pathbuf.to_cstr16(), cstr16!("\\first\\second")); // empty pushes should be ignored and have no effect let empty_cstring16 = CString16::try_from("").unwrap(); let mut pathbuf = PathBuf::new(); pathbuf.push(cstr16!("first")); pathbuf.push(empty_cstring16.as_ref()); pathbuf.push(empty_cstring16.as_ref()); pathbuf.push(empty_cstring16.as_ref()); pathbuf.push(cstr16!("second")); assert_eq!(pathbuf.to_cstr16(), cstr16!("first\\second")); } #[test] fn partial_eq() { let mut pathbuf1 = PathBuf::new(); pathbuf1.push(cstr16!("first")); pathbuf1.push(cstr16!("second")); pathbuf1.push(cstr16!("third")); assert_eq!(pathbuf1, pathbuf1); let mut pathbuf2 = PathBuf::new(); pathbuf2.push(cstr16!("\\first")); pathbuf2.push(cstr16!("second")); assert_eq!(pathbuf2, pathbuf2); assert_ne!(pathbuf1, pathbuf2); } }