file_system/fundamentals/path/
path_owned.rs1use core::{
2 fmt::{Display, Formatter},
3 ops::Deref,
4};
5
6use alloc::{
7 string::{String, ToString},
8 vec::Vec,
9};
10
11use super::{EXTENSION_SEPARATOR, Path, SEPARATOR};
12
13#[derive(Clone, PartialEq, Eq, Debug, Hash)]
14#[repr(transparent)]
15pub struct PathOwned(String);
16
17impl PathOwned {
18 pub unsafe fn new_unchecked(path: String) -> Self {
21 PathOwned(path)
22 }
23
24 pub fn new(path: String) -> Option<Self> {
25 let path = if path.ends_with(SEPARATOR) && path.len() > 1 {
26 path[..path.len() - 1].to_string()
27 } else {
28 path
29 };
30
31 if is_valid_string(&path) {
32 Some(PathOwned(path))
33 } else {
34 None
35 }
36 }
37
38 pub fn root() -> PathOwned {
39 PathOwned("/".to_string())
40 }
41
42 pub fn join(mut self, path: impl AsRef<Path>) -> Option<Self> {
43 if path.as_ref().is_absolute() {
44 return None;
45 }
46
47 if path.as_ref().is_empty() {
48 return Some(self);
49 }
50
51 if !self.0.ends_with(SEPARATOR) {
52 self.0.push(SEPARATOR);
53 }
54 self.0.push_str(path.as_ref().as_str());
55
56 Some(self)
57 }
58
59 pub fn append(self, path: &str) -> Option<Self> {
60 self.join(Path::from_str(path))
61 }
62
63 pub fn revert_parent_directory(&mut self) -> &mut Self {
64 let mut last_index = 0;
65 for (i, c) in self.0.chars().enumerate() {
66 if c == SEPARATOR {
67 last_index = i;
68 }
69 }
70 if last_index == 0 {
71 self.0.clear();
72 return self;
73 }
74
75 self.0.truncate(last_index);
76 self
77 }
78
79 pub fn get_extension(&self) -> Option<&str> {
80 let mut extension = None;
81
82 for (i, c) in self.0.char_indices() {
83 if c == EXTENSION_SEPARATOR {
84 extension = Some(&self.0[i..]);
85 }
86 }
87 extension
88 }
89
90 pub fn get_file_name(&self) -> &str {
91 let mut last_index = 0;
92 for (i, c) in self.0.chars().enumerate() {
93 if c == SEPARATOR {
94 last_index = i;
95 }
96 }
97 if last_index >= self.0.len() {
98 return &self.0[last_index..];
99 }
100 &self.0[last_index + 1..]
101 }
102
103 pub fn get_relative_to(&self, path: &PathOwned) -> Option<PathOwned> {
104 if !self.0.starts_with(path.0.as_str()) {
105 return None;
106 }
107
108 Some(PathOwned(self.0[path.0.len()..].to_string()))
109 }
110
111 pub fn canonicalize(mut self) -> Self {
112 let mut stack: Vec<&str> = Vec::new();
113
114 if self.is_absolute() {
115 stack.push("");
116 }
117
118 for component in self.0.split(SEPARATOR) {
119 match component {
120 ".." => {
121 stack.pop();
122 }
123 "." | "" => continue,
124 _ => stack.push(component),
125 }
126 }
127
128 self.0 = stack.join("/");
129
130 self
131 }
132}
133
134pub fn is_valid_string(string: &str) -> bool {
135 let invalid = ['\0', ':', '*', '?', '"', '<', '>', '|', ' '];
136
137 for character in string.chars() {
138 if invalid.contains(&character) {
140 return false;
141 }
142 }
143
144 if string.ends_with(SEPARATOR) && string.len() > 1 {
145 return false;
147 }
148
149 true
150}
151
152impl TryFrom<&str> for PathOwned {
153 type Error = ();
154
155 fn try_from(item: &str) -> Result<Self, Self::Error> {
156 if is_valid_string(item) {
157 Ok(PathOwned(item.to_string()))
158 } else {
159 Err(())
160 }
161 }
162}
163
164impl TryFrom<String> for PathOwned {
165 type Error = ();
166
167 fn try_from(item: String) -> Result<Self, Self::Error> {
168 if is_valid_string(&item) {
169 Ok(PathOwned(item))
170 } else {
171 Err(())
172 }
173 }
174}
175
176impl Display for PathOwned {
177 fn fmt(&self, formatter: &mut Formatter) -> Result<(), core::fmt::Error> {
178 write!(formatter, "{}", self.0)
179 }
180}
181
182impl AsRef<str> for PathOwned {
183 fn as_ref(&self) -> &str {
184 self.0.as_str()
185 }
186}
187
188impl Deref for PathOwned {
189 type Target = Path;
190
191 fn deref(&self) -> &Self::Target {
192 Path::from_str(self.0.as_str())
193 }
194}
195
196impl AsRef<Path> for PathOwned {
197 fn as_ref(&self) -> &Path {
198 self
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn test_path_addition() {
208 let path = PathOwned::try_from("/").unwrap();
209 assert_eq!(path.as_str(), "/");
210 let path = path.append("Folder").unwrap();
211 assert_eq!(path.as_str(), "/Folder");
212 let path = path.append("File").unwrap();
213 assert_eq!(path.as_str(), "/Folder/File");
214 }
215
216 #[test]
217 fn test_valid_string() {
218 assert!(is_valid_string("Hello"));
219 assert!(is_valid_string("Hello/World"));
220 assert!(is_valid_string("Hello/World.txt"));
221 assert!(!is_valid_string("Hello/World.txt/"));
222 assert!(!is_valid_string("Hello/World.txt:"));
223 assert!(!is_valid_string("Hello/World.txt*"));
224 assert!(!is_valid_string("Hello/World.txt?"));
225 assert!(!is_valid_string("Hello/World.txt\""));
226 assert!(!is_valid_string("Hello/World.txt<"));
227 assert!(!is_valid_string("Hello/World.txt>"));
228 assert!(!is_valid_string("Hello/World.txt|"));
229 assert!(!is_valid_string("Hello/World.txt "));
230 assert!(!is_valid_string("Hello/World.txt\0"));
231 assert!(is_valid_string(""));
232 assert!(!is_valid_string("Hello/Wo rld.txt/"));
233 }
234
235 #[test]
236 fn test_canonicalize() {
237 let path = PathOwned::try_from("/home/../home/user/./file.txt").unwrap();
238 assert_eq!(path.canonicalize().as_str(), "/home/user/file.txt");
239
240 let path = PathOwned::try_from("./home/../home/user/./file.txt").unwrap();
241 assert_eq!(path.canonicalize().as_str(), "home/user/file.txt");
242 }
243}