file_system/fundamentals/path/
path_reference.rs1use core::borrow::Borrow;
2
3use alloc::{borrow::ToOwned, format, string::ToString};
4
5use super::*;
6
7#[derive(Debug, PartialEq, Eq, Hash)]
11#[repr(transparent)]
12pub struct Path(str);
13
14impl Path {
15 pub const ROOT: &'static Path = Self::from_str("/");
16 pub const EMPTY: &'static Path = Self::from_str("");
17
18 pub const SYSTEM: &'static Path = Self::from_str("/system");
21
22 pub const DEVICES: &'static Path = Self::from_str("/devices");
24
25 pub const CONFIGURATION: &'static Path = Self::from_str("/configuration");
27
28 pub const SHARED_CONFIGURATION: &'static Path = Self::from_str("/configuration/shared");
30
31 pub const DATA: &'static Path = Self::from_str("/data");
33
34 pub const SHARED_DATA: &'static Path = Self::from_str("/data/shared");
36
37 pub const BINARIES: &'static Path = Self::from_str("/binaries");
39
40 pub const USERS: &'static Path = Self::from_str("/users");
42
43 pub const TEMPORARY: &'static Path = Self::from_str("/temporary");
45
46 pub const LOGS: &'static Path = Self::from_str("/temporary/logs");
48
49 pub const fn from_str(path: &str) -> &Path {
52 unsafe { &*(path as *const str as *const Path) }
53 }
54
55 pub fn new<S: AsRef<str> + ?Sized>(path: &S) -> &Path {
58 unsafe { &*(path.as_ref() as *const str as *const Path) }
59 }
60
61 pub fn is_valid(&self) -> bool {
62 is_valid_string(&self.0)
63 }
64
65 pub fn is_absolute(&self) -> bool {
66 self.0.starts_with('/')
67 }
68
69 pub fn is_root(&self) -> bool {
70 &self.0 == "/"
71 }
72
73 pub fn is_empty(&self) -> bool {
74 self.0.is_empty()
75 }
76
77 pub fn is_canonical(&self) -> bool {
78 self.0.chars().all(|c| c != '.')
79 }
80
81 pub fn go_parent(&self) -> Option<&Path> {
82 let characters_to_remove = match self.0.rfind(SEPARATOR) {
83 Some(index) => index,
84 None => {
85 if self.get_length() > 0 {
87 return Some(Self::EMPTY);
89 } else {
90 return None;
91 }
92 }
93 };
94
95 if characters_to_remove == 0 {
96 if self.get_length() == 1 {
97 return None;
98 }
99
100 if self.is_absolute() {
101 return Some(Self::ROOT);
102 } else {
103 return Some(Self::from_str(""));
104 }
105 }
106
107 Some(Self::from_str(&self.0[..characters_to_remove]))
108 }
109
110 pub fn get_file_prefix(&self) -> Option<&str> {
111 let extension_start = self
112 .0
113 .rfind(EXTENSION_SEPARATOR)
114 .or_else(|| Some(self.get_length()))?; let file_prefix_start = self.0.rfind(SEPARATOR).map(|i| i + 1).unwrap_or(0); if extension_start <= file_prefix_start {
118 return None;
119 }
120
121 Some(&self.0[file_prefix_start..extension_start])
122 }
123
124 pub fn get_file_name(&self) -> Option<&str> {
125 let file_prefix_start = self.0.rfind(SEPARATOR).map(|i| i + 1).unwrap_or(0); if file_prefix_start >= self.get_length() {
128 return None;
129 }
130
131 Some(&self.0[file_prefix_start..])
132 }
133
134 pub fn get_extension(&self) -> Option<&str> {
135 let extension_start = self.0.rfind(EXTENSION_SEPARATOR)?;
136
137 Some(&self.0[extension_start + 1..])
138 }
139
140 pub fn set_extension(&self, extension: &str) -> Option<PathOwned> {
141 let extension_start = self
142 .0
143 .rfind(EXTENSION_SEPARATOR)
144 .unwrap_or(self.get_length());
145
146 Some(unsafe {
147 PathOwned::new_unchecked(format!("{}{}", &self.0[..extension_start], extension))
148 })
149 }
150
151 pub fn strip_prefix<'b>(&'b self, path_prefix: &Path) -> Option<&'b Path> {
152 let mut stripped_prefix = self.0.strip_prefix(&path_prefix.0)?;
153
154 if stripped_prefix.starts_with(SEPARATOR) {
155 stripped_prefix = &stripped_prefix[1..]
156 }
157
158 Some(Self::from_str(stripped_prefix))
159 }
160
161 pub fn strip_prefix_absolute<'b>(&'b self, path_prefix: &Path) -> Option<&'b Path> {
162 if path_prefix.is_root() {
163 return Some(self);
164 }
165
166 let stripped_prefix = self.0.strip_prefix(&path_prefix.0)?;
167
168 if stripped_prefix.is_empty() {
169 return Some(Self::ROOT);
170 }
171
172 Some(Self::from_str(stripped_prefix))
173 }
174
175 pub fn strip_suffix<'b>(&'b self, path_suffix: &Path) -> Option<&'b Path> {
176 let stripped_suffix = self.0.strip_suffix(&path_suffix.0)?;
177
178 if stripped_suffix.ends_with(SEPARATOR) {
179 return Some(Self::from_str(
180 &stripped_suffix[..stripped_suffix.len() - 1],
181 ));
182 }
183
184 Some(Self::from_str(stripped_suffix))
185 }
186
187 pub fn get_components(&'_ self) -> Components<'_> {
188 Components::new(self)
189 }
190
191 pub fn join(&self, path: &Path) -> Option<PathOwned> {
192 self.to_owned().join(path)
193 }
194
195 pub fn append(&self, path: &str) -> Option<PathOwned> {
196 self.to_owned().append(path)
197 }
198
199 pub fn get_length(&self) -> usize {
200 self.0.len()
201 }
202
203 pub fn as_str(&self) -> &str {
204 &self.0
205 }
206}
207
208#[cfg(feature = "std")]
209impl AsRef<std::path::Path> for Path {
210 fn as_ref(&self) -> &std::path::Path {
211 std::path::Path::new(&self.0)
212 }
213}
214
215impl ToOwned for Path {
216 type Owned = PathOwned;
217
218 fn to_owned(&self) -> Self::Owned {
219 unsafe { PathOwned::new_unchecked(self.0.to_string()) }
220 }
221
222 fn clone_into(&self, target: &mut Self::Owned) {
223 *target = self.to_owned();
224 }
225}
226
227impl Borrow<Path> for PathOwned {
228 fn borrow(&self) -> &Path {
229 Path::from_str(&self.0)
230 }
231}
232
233impl AsRef<Path> for str {
234 fn as_ref(&self) -> &Path {
235 Path::from_str(self)
236 }
237}
238
239impl AsRef<str> for &Path {
240 fn as_ref(&self) -> &str {
241 &self.0
242 }
243}
244
245impl AsRef<Path> for Path {
246 fn as_ref(&self) -> &Path {
247 self
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254
255 #[test]
256 fn test_strip_prefix() {
257 let path = Path::from_str("/home/user/file.txt");
258 let prefix = Path::from_str("/home/user");
259 assert_eq!(path.strip_prefix(prefix).unwrap().as_str(), "file.txt");
260
261 let path = Path::from_str("/home/user/file.txt");
262 let prefix = Path::from_str("/");
263 assert_eq!(
264 path.strip_prefix(prefix).unwrap().as_str(),
265 "home/user/file.txt"
266 );
267
268 let invalid_prefix = Path::from_str("/home/invalid/");
269 assert_eq!(path.strip_prefix(invalid_prefix), None);
270 }
271
272 #[test]
273 fn test_strip_prefix_absolute() {
274 let path = Path::from_str("/");
275 let prefix = Path::from_str("/");
276 assert_eq!(path.strip_prefix_absolute(prefix).unwrap().as_str(), "/");
277
278 let path = Path::from_str("/Foo/Bar");
279 let prefix = Path::from_str("/Foo/Bar");
280 assert_eq!(path.strip_prefix_absolute(prefix).unwrap().as_str(), "/");
281
282 let path = Path::from_str("/home/user/file.txt");
283 let prefix = Path::from_str("/home/user");
284 assert_eq!(
285 path.strip_prefix_absolute(prefix).unwrap().as_str(),
286 "/file.txt"
287 );
288
289 let path = Path::from_str("/home/user/file.txt");
290 let prefix = Path::from_str("/");
291 assert_eq!(
292 path.strip_prefix_absolute(prefix).unwrap().as_str(),
293 "/home/user/file.txt"
294 );
295
296 let invalid_prefix = Path::from_str("/home/invalid/");
297 assert_eq!(path.strip_prefix_absolute(invalid_prefix), None);
298 }
299
300 #[test]
301 fn test_strip_suffix() {
302 let path = Path::from_str("/home/user/file.txt");
303 let suffix = Path::from_str("user/file.txt");
304 assert_eq!(path.strip_suffix(suffix).unwrap().as_str(), "/home");
305
306 let invalid_suffix = Path::from_str("user/invalid.txt");
307 assert_eq!(path.strip_suffix(invalid_suffix), None);
308 }
309
310 #[test]
311 fn test_go_parent() {
312 let path = Path::from_str("/home/user/file.txt");
313 assert_eq!(&path.go_parent().unwrap().0, "/home/user");
314 assert_eq!(&path.go_parent().unwrap().go_parent().unwrap().0, "/home");
315 assert_eq!(
316 &path
317 .go_parent()
318 .unwrap()
319 .go_parent()
320 .unwrap()
321 .go_parent()
322 .unwrap()
323 .0,
324 "/"
325 );
326 assert_eq!(
327 path.go_parent()
328 .unwrap()
329 .go_parent()
330 .unwrap()
331 .go_parent()
332 .unwrap()
333 .go_parent(),
334 None
335 );
336
337 let path = Path::from_str("home/user/file.txt");
338 assert_eq!(&path.go_parent().unwrap().0, "home/user");
339 assert_eq!(&path.go_parent().unwrap().go_parent().unwrap().0, "home");
340 assert_eq!(
341 &path
342 .go_parent()
343 .unwrap()
344 .go_parent()
345 .unwrap()
346 .go_parent()
347 .unwrap()
348 .0,
349 ""
350 );
351 }
352
353 #[test]
354 fn test_path_file() {
355 let path = Path::from_str("/Directory/File.txt");
357 assert_eq!(path.get_extension(), Some("txt"));
358 assert_eq!(path.get_file_prefix(), Some("File"));
359 assert_eq!(path.get_file_name(), Some("File.txt"));
360
361 let path = Path::from_str("/Directory/File");
363 assert_eq!(path.get_extension(), None);
364 assert_eq!(path.get_file_prefix(), Some("File"));
365 assert_eq!(path.get_file_name(), Some("File"));
366
367 let path = Path::from_str("File.txt");
369 assert_eq!(path.get_extension(), Some("txt"));
370 assert_eq!(path.get_file_prefix(), Some("File"));
371 assert_eq!(path.get_file_name(), Some("File.txt"));
372
373 let path = Path::from_str("/");
375 assert_eq!(path.get_extension(), None);
376 assert_eq!(path.get_file_prefix(), None);
377 assert_eq!(path.get_file_name(), None);
378
379 let path = Path::from_str("File");
381 assert_eq!(path.get_extension(), None);
382 assert_eq!(path.get_file_prefix(), Some("File"));
383 assert_eq!(path.get_file_name(), Some("File"));
384 }
385}