1 use crate::fs::path::Path; 2 use crate::fs::SEPARATOR; 3 use crate::{CStr16, CString16, Char16}; 4 use core::fmt::{Display, Formatter}; 5 6 /// A path buffer similar to the `PathBuf` of the standard library, but based on 7 /// [`CString16`] strings and [`SEPARATOR`] as separator. 8 /// 9 /// `/` is replaced by [`SEPARATOR`] on the fly. 10 #[derive(Clone, Debug, Default, Eq, PartialOrd, Ord)] 11 pub struct PathBuf(CString16); 12 13 impl PathBuf { 14 /// Constructor. 15 #[must_use] new() -> Self16 pub fn new() -> Self { 17 Self::default() 18 } 19 20 /// Constructor that replaces all occurrences of `/` with `\`. new_from_cstring16(mut string: CString16) -> Self21 fn new_from_cstring16(mut string: CString16) -> Self { 22 const SEARCH: Char16 = unsafe { Char16::from_u16_unchecked('/' as u16) }; 23 string.replace_char(SEARCH, SEPARATOR); 24 Self(string) 25 } 26 27 /// Extends self with path. 28 /// 29 /// UNIX separators (`/`) will be replaced by [`SEPARATOR`] on the fly. push<P: AsRef<Path>>(&mut self, path: P)30 pub fn push<P: AsRef<Path>>(&mut self, path: P) { 31 const SEARCH: Char16 = unsafe { Char16::from_u16_unchecked('/' as u16) }; 32 33 // do nothing on empty path 34 if path.as_ref().is_empty() { 35 return; 36 } 37 38 let empty = self.0.is_empty(); 39 let needs_sep = *self 40 .0 41 .as_slice_with_nul() 42 .last() 43 .expect("Should have at least null character") 44 != SEPARATOR; 45 if !empty && needs_sep { 46 self.0.push(SEPARATOR) 47 } 48 49 self.0.push_str(path.as_ref().to_cstr16()); 50 self.0.replace_char(SEARCH, SEPARATOR); 51 } 52 } 53 54 impl PartialEq for PathBuf { eq(&self, other: &Self) -> bool55 fn eq(&self, other: &Self) -> bool { 56 let path1: &Path = self.as_ref(); 57 let path2: &Path = other.as_ref(); 58 path1 == path2 59 } 60 } 61 62 impl Display for PathBuf { fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result63 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 64 Display::fmt(self.to_cstr16(), f) 65 } 66 } 67 68 mod convenience_impls { 69 use super::*; 70 use core::borrow::Borrow; 71 use core::ops::Deref; 72 73 impl From<CString16> for PathBuf { from(value: CString16) -> Self74 fn from(value: CString16) -> Self { 75 Self::new_from_cstring16(value) 76 } 77 } 78 79 impl From<&CStr16> for PathBuf { from(value: &CStr16) -> Self80 fn from(value: &CStr16) -> Self { 81 Self::new_from_cstring16(CString16::from(value)) 82 } 83 } 84 85 impl Deref for PathBuf { 86 type Target = Path; 87 deref(&self) -> &Self::Target88 fn deref(&self) -> &Self::Target { 89 Path::new(&self.0) 90 } 91 } 92 93 impl AsRef<Path> for PathBuf { as_ref(&self) -> &Path94 fn as_ref(&self) -> &Path { 95 // falls back to deref impl 96 self 97 } 98 } 99 100 impl Borrow<Path> for PathBuf { borrow(&self) -> &Path101 fn borrow(&self) -> &Path { 102 // falls back to deref impl 103 self 104 } 105 } 106 107 impl AsRef<CStr16> for PathBuf { as_ref(&self) -> &CStr16108 fn as_ref(&self) -> &CStr16 { 109 &self.0 110 } 111 } 112 113 impl Borrow<CStr16> for PathBuf { borrow(&self) -> &CStr16114 fn borrow(&self) -> &CStr16 { 115 &self.0 116 } 117 } 118 } 119 120 #[cfg(test)] 121 mod tests { 122 use super::*; 123 use crate::cstr16; 124 use alloc::string::ToString; 125 126 #[test] from_cstr16()127 fn from_cstr16() { 128 let source: &CStr16 = cstr16!("\\hello\\foo\\bar"); 129 let _path: PathBuf = source.into(); 130 } 131 132 #[test] from_cstring16()133 fn from_cstring16() { 134 let source = CString16::try_from("\\hello\\foo\\bar").unwrap(); 135 let _path: PathBuf = source.as_ref().into(); 136 let _path: PathBuf = source.clone().into(); 137 let _path: PathBuf = PathBuf::new_from_cstring16(source); 138 } 139 140 #[test] from_std_string()141 fn from_std_string() { 142 let std_string = "\\hello\\foo\\bar".to_string(); 143 let _path = PathBuf::new_from_cstring16(CString16::try_from(std_string.as_str()).unwrap()); 144 } 145 146 #[test] push()147 fn push() { 148 let mut pathbuf = PathBuf::new(); 149 pathbuf.push(cstr16!("first")); 150 pathbuf.push(cstr16!("second")); 151 pathbuf.push(cstr16!("third")); 152 assert_eq!(pathbuf.to_cstr16(), cstr16!("first\\second\\third")); 153 154 let mut pathbuf = PathBuf::new(); 155 pathbuf.push(cstr16!("\\first")); 156 pathbuf.push(cstr16!("second")); 157 assert_eq!(pathbuf.to_cstr16(), cstr16!("\\first\\second")); 158 159 // empty pushes should be ignored and have no effect 160 let empty_cstring16 = CString16::try_from("").unwrap(); 161 let mut pathbuf = PathBuf::new(); 162 pathbuf.push(cstr16!("first")); 163 pathbuf.push(empty_cstring16.as_ref()); 164 pathbuf.push(empty_cstring16.as_ref()); 165 pathbuf.push(empty_cstring16.as_ref()); 166 pathbuf.push(cstr16!("second")); 167 assert_eq!(pathbuf.to_cstr16(), cstr16!("first\\second")); 168 } 169 170 #[test] partial_eq()171 fn partial_eq() { 172 let mut pathbuf1 = PathBuf::new(); 173 pathbuf1.push(cstr16!("first")); 174 pathbuf1.push(cstr16!("second")); 175 pathbuf1.push(cstr16!("third")); 176 177 assert_eq!(pathbuf1, pathbuf1); 178 179 let mut pathbuf2 = PathBuf::new(); 180 pathbuf2.push(cstr16!("\\first")); 181 pathbuf2.push(cstr16!("second")); 182 183 assert_eq!(pathbuf2, pathbuf2); 184 assert_ne!(pathbuf1, pathbuf2); 185 } 186 } 187