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