|
| 1 | +// allow "path.rs" in "path" |
| 2 | +#![allow(clippy::module_inception)] |
| 3 | + |
| 4 | +use crate::fs::path::{PathBuf, SEPARATOR}; |
| 5 | +use crate::CStr16; |
| 6 | +use core::fmt::{Display, Formatter}; |
| 7 | +use uefi::CString16; |
| 8 | + |
| 9 | +/// A path similar to the `Path` of the standard library, but based on |
| 10 | +/// [`CStr16`] strings and [`SEPARATOR`] as separator. |
| 11 | +/// |
| 12 | +/// [`SEPARATOR`]: super::SEPARATOR |
| 13 | +#[derive(Debug, Eq, PartialOrd, Ord)] |
| 14 | +pub struct Path(CStr16); |
| 15 | + |
| 16 | +impl Path { |
| 17 | + /// Constructor. |
| 18 | + #[must_use] |
| 19 | + pub fn new<S: AsRef<CStr16> + ?Sized>(s: &S) -> &Self { |
| 20 | + unsafe { &*(s.as_ref() as *const CStr16 as *const Self) } |
| 21 | + } |
| 22 | + |
| 23 | + /// Returns the underlying string. |
| 24 | + #[must_use] |
| 25 | + pub fn to_cstr16(&self) -> &CStr16 { |
| 26 | + &self.0 |
| 27 | + } |
| 28 | + |
| 29 | + /// Returns a path buf from that type. |
| 30 | + #[must_use] |
| 31 | + pub fn to_path_buf(&self) -> PathBuf { |
| 32 | + let cstring = CString16::from(&self.0); |
| 33 | + PathBuf::from(cstring) |
| 34 | + } |
| 35 | + |
| 36 | + /// Iterator over the components of a path. |
| 37 | + #[must_use] |
| 38 | + pub fn components(&self) -> Components { |
| 39 | + Components { |
| 40 | + path: self.as_ref(), |
| 41 | + i: 0, |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + /// Returns the parent directory as [`PathBuf`]. |
| 46 | + /// |
| 47 | + /// If the path is a top-level component, this returns None. |
| 48 | + #[must_use] |
| 49 | + pub fn parent(&self) -> Option<PathBuf> { |
| 50 | + let components_count = self.components().count(); |
| 51 | + if components_count == 0 { |
| 52 | + return None; |
| 53 | + } |
| 54 | + |
| 55 | + // Return None, as we do not treat "\\" as dedicated component. |
| 56 | + let sep_count = self |
| 57 | + .0 |
| 58 | + .as_slice() |
| 59 | + .iter() |
| 60 | + .filter(|char| **char == SEPARATOR) |
| 61 | + .count(); |
| 62 | + if sep_count == 0 { |
| 63 | + return None; |
| 64 | + } |
| 65 | + |
| 66 | + let path = |
| 67 | + self.components() |
| 68 | + .take(components_count - 1) |
| 69 | + .fold(CString16::new(), |mut acc, next| { |
| 70 | + // Add separator, as needed. |
| 71 | + if !acc.is_empty() && *acc.as_slice().last().unwrap() != SEPARATOR { |
| 72 | + acc.push(SEPARATOR); |
| 73 | + } |
| 74 | + acc.push_str(next.as_ref()); |
| 75 | + acc |
| 76 | + }); |
| 77 | + let path = PathBuf::from(path); |
| 78 | + Some(path) |
| 79 | + } |
| 80 | + |
| 81 | + /// Returns of the path is empty. |
| 82 | + #[must_use] |
| 83 | + pub fn is_empty(&self) -> bool { |
| 84 | + self.to_cstr16().is_empty() |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +impl Display for Path { |
| 89 | + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { |
| 90 | + Display::fmt(self.to_cstr16(), f) |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +impl PartialEq for Path { |
| 95 | + fn eq(&self, other: &Self) -> bool { |
| 96 | + self.components().count() == other.components().count() |
| 97 | + && !self |
| 98 | + .components() |
| 99 | + .zip(other.components()) |
| 100 | + .any(|(c1, c2)| c1 != c2) |
| 101 | + } |
| 102 | +} |
| 103 | + |
| 104 | +/// Iterator over the components of a path. For example, the path `\\a\\b\\c` |
| 105 | +/// has the components `[a, b, c]`. This is a more basic approach than the |
| 106 | +/// components type of the standard library. |
| 107 | +#[derive(Debug)] |
| 108 | +pub struct Components<'a> { |
| 109 | + path: &'a CStr16, |
| 110 | + i: usize, |
| 111 | +} |
| 112 | + |
| 113 | +impl<'a> Iterator for Components<'a> { |
| 114 | + // Attention. We can't iterate over &'Ctr16, as we would break any guarantee |
| 115 | + // made for the terminating null character. |
| 116 | + type Item = CString16; |
| 117 | + |
| 118 | + fn next(&mut self) -> Option<Self::Item> { |
| 119 | + if self.path.is_empty() { |
| 120 | + return None; |
| 121 | + } |
| 122 | + if self.path.num_chars() == 1 && self.path.as_slice()[0] == SEPARATOR { |
| 123 | + // The current implementation does not handle the root dir as |
| 124 | + // dedicated component so far. We just return nothing. |
| 125 | + return None; |
| 126 | + } |
| 127 | + |
| 128 | + // If the path is not empty and starts with a separator, skip it. |
| 129 | + if self.i == 0 && *self.path.as_slice().first().unwrap() == SEPARATOR { |
| 130 | + self.i = 1; |
| 131 | + } |
| 132 | + |
| 133 | + // Count how many characters are there until the next separator is |
| 134 | + // found. |
| 135 | + let len = self |
| 136 | + .path |
| 137 | + .iter() |
| 138 | + .skip(self.i) |
| 139 | + .take_while(|c| **c != SEPARATOR) |
| 140 | + .count(); |
| 141 | + |
| 142 | + let progress = self.i + len; |
| 143 | + if progress > self.path.num_chars() { |
| 144 | + None |
| 145 | + } else { |
| 146 | + // select the next component and build an owned string |
| 147 | + let part = &self.path.as_slice()[self.i..self.i + len]; |
| 148 | + let mut string = CString16::new(); |
| 149 | + part.iter().for_each(|c| string.push(*c)); |
| 150 | + |
| 151 | + // +1: skip the separator |
| 152 | + self.i = progress + 1; |
| 153 | + Some(string) |
| 154 | + } |
| 155 | + } |
| 156 | +} |
| 157 | + |
| 158 | +mod convenience_impls { |
| 159 | + use super::*; |
| 160 | + use core::borrow::Borrow; |
| 161 | + |
| 162 | + impl AsRef<Path> for &Path { |
| 163 | + fn as_ref(&self) -> &Path { |
| 164 | + self |
| 165 | + } |
| 166 | + } |
| 167 | + |
| 168 | + impl<'a> From<&'a CStr16> for &'a Path { |
| 169 | + fn from(value: &'a CStr16) -> Self { |
| 170 | + Path::new(value) |
| 171 | + } |
| 172 | + } |
| 173 | + |
| 174 | + impl AsRef<CStr16> for Path { |
| 175 | + fn as_ref(&self) -> &CStr16 { |
| 176 | + &self.0 |
| 177 | + } |
| 178 | + } |
| 179 | + |
| 180 | + impl Borrow<CStr16> for Path { |
| 181 | + fn borrow(&self) -> &CStr16 { |
| 182 | + &self.0 |
| 183 | + } |
| 184 | + } |
| 185 | + |
| 186 | + impl AsRef<Path> for CStr16 { |
| 187 | + fn as_ref(&self) -> &Path { |
| 188 | + Path::new(self) |
| 189 | + } |
| 190 | + } |
| 191 | + |
| 192 | + impl Borrow<Path> for CStr16 { |
| 193 | + fn borrow(&self) -> &Path { |
| 194 | + Path::new(self) |
| 195 | + } |
| 196 | + } |
| 197 | +} |
| 198 | + |
| 199 | +#[cfg(test)] |
| 200 | +mod tests { |
| 201 | + use super::*; |
| 202 | + use alloc::vec::Vec; |
| 203 | + use uefi_macros::cstr16; |
| 204 | + |
| 205 | + #[test] |
| 206 | + fn from_cstr16() { |
| 207 | + let source: &CStr16 = cstr16!("\\hello\\foo\\bar"); |
| 208 | + let _path: &Path = source.into(); |
| 209 | + let _path: &Path = Path::new(source); |
| 210 | + } |
| 211 | + |
| 212 | + #[test] |
| 213 | + fn from_cstring16() { |
| 214 | + let source = CString16::try_from("\\hello\\foo\\bar").unwrap(); |
| 215 | + let _path: &Path = source.as_ref().into(); |
| 216 | + let _path: &Path = Path::new(source.as_ref()); |
| 217 | + } |
| 218 | + |
| 219 | + #[test] |
| 220 | + fn components_iter() { |
| 221 | + let path = Path::new(cstr16!("foo\\bar\\hello")); |
| 222 | + let components = path.components().collect::<Vec<_>>(); |
| 223 | + let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>(); |
| 224 | + let expected: &[&CStr16] = &[cstr16!("foo"), cstr16!("bar"), cstr16!("hello")]; |
| 225 | + assert_eq!(components.as_slice(), expected); |
| 226 | + |
| 227 | + // In case there is a leading slash, it should be ignored. |
| 228 | + let path = Path::new(cstr16!("\\foo\\bar\\hello")); |
| 229 | + let components = path.components().collect::<Vec<_>>(); |
| 230 | + let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>(); |
| 231 | + let expected: &[&CStr16] = &[cstr16!("foo"), cstr16!("bar"), cstr16!("hello")]; |
| 232 | + assert_eq!(components.as_slice(), expected); |
| 233 | + |
| 234 | + // empty path iteration should be just fine |
| 235 | + let empty_cstring16 = CString16::try_from("").unwrap(); |
| 236 | + let path = Path::new(empty_cstring16.as_ref()); |
| 237 | + let components = path.components().collect::<Vec<_>>(); |
| 238 | + let expected: &[CString16] = &[]; |
| 239 | + assert_eq!(components.as_slice(), expected); |
| 240 | + |
| 241 | + // test empty path |
| 242 | + let _path = Path::new(cstr16!()); |
| 243 | + let path = Path::new(cstr16!("")); |
| 244 | + let components = path.components().collect::<Vec<_>>(); |
| 245 | + let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>(); |
| 246 | + let expected: &[&CStr16] = &[]; |
| 247 | + assert_eq!(components.as_slice(), expected); |
| 248 | + |
| 249 | + // test path that has only root component. Treated as empty path by |
| 250 | + // the components iterator. |
| 251 | + let path = Path::new(cstr16!("\\")); |
| 252 | + let components = path.components().collect::<Vec<_>>(); |
| 253 | + let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>(); |
| 254 | + let expected: &[&CStr16] = &[]; |
| 255 | + assert_eq!(components.as_slice(), expected); |
| 256 | + } |
| 257 | + |
| 258 | + #[test] |
| 259 | + fn test_parent() { |
| 260 | + assert_eq!(None, Path::new(cstr16!("")).parent()); |
| 261 | + assert_eq!(None, Path::new(cstr16!("\\")).parent()); |
| 262 | + assert_eq!( |
| 263 | + Path::new(cstr16!("a\\b")).parent(), |
| 264 | + Some(PathBuf::from(cstr16!("a"))), |
| 265 | + ); |
| 266 | + assert_eq!( |
| 267 | + Path::new(cstr16!("\\a\\b")).parent(), |
| 268 | + Some(PathBuf::from(cstr16!("a"))), |
| 269 | + ); |
| 270 | + assert_eq!( |
| 271 | + Path::new(cstr16!("a\\b\\c\\d")).parent(), |
| 272 | + Some(PathBuf::from(cstr16!("a\\b\\c"))), |
| 273 | + ); |
| 274 | + assert_eq!(Path::new(cstr16!("abc")).parent(), None,); |
| 275 | + } |
| 276 | + |
| 277 | + #[test] |
| 278 | + fn partial_eq() { |
| 279 | + let path1 = Path::new(cstr16!(r"a\b")); |
| 280 | + let path2 = Path::new(cstr16!(r"\a\b")); |
| 281 | + let path3 = Path::new(cstr16!(r"a\b\c")); |
| 282 | + |
| 283 | + assert_eq!(path1, path1); |
| 284 | + assert_eq!(path2, path2); |
| 285 | + assert_eq!(path3, path3); |
| 286 | + |
| 287 | + // Equal as currently, we only support absolute paths, so the leading |
| 288 | + // separator is obligatory. |
| 289 | + assert_eq!(path1, path2); |
| 290 | + assert_eq!(path2, path1); |
| 291 | + |
| 292 | + assert_ne!(path1, path3); |
| 293 | + assert_ne!(path3, path1); |
| 294 | + } |
| 295 | +} |
0 commit comments