Skip to main content

keramics_vfs/windows/
path.rs

1/* Copyright 2024-2026 Joachim Metz <joachim.metz@gmail.com>
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may
5 * obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software
8 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 * License for the specific language governing permissions and limitations
11 * under the License.
12 */
13
14use keramics_formats::{Path, PathComponent};
15use keramics_types::Ucs2String;
16
17/// Windows path
18pub struct WindowsPath {}
19
20impl WindowsPath {
21    /// Retrieves the data fork name from a [`&Ucs2String`] if available.
22    pub fn data_fork_name(ucs2_string: &Ucs2String) -> Option<Ucs2String> {
23        if ucs2_string.is_empty() {
24            return None;
25        }
26        match ucs2_string
27            .elements
28            .iter()
29            .rposition(|value| *value == 0x003a)
30        {
31            Some(value_index) => {
32                let data_fork_name_string: Ucs2String =
33                    Ucs2String::from(&ucs2_string.elements[value_index + 1..]);
34
35                Some(data_fork_name_string)
36            }
37            None => None,
38        }
39    }
40
41    /// Converts a [`&str`] containing a Windows path into a [`Path`]
42    pub fn from_str(mut string: &str) -> Path {
43        let components: Vec<PathComponent> = if string.is_empty() {
44            vec![]
45        } else if string == "\\" {
46            vec![PathComponent::Root]
47        } else {
48            if string.starts_with("\\\\.\\")
49                || string.starts_with("\\\\?\\")
50                || string.starts_with("\\??\\")
51            {
52                string = &string[4..];
53            }
54            let mut components: Vec<PathComponent> = Vec::new();
55
56            for string_segment in string.split("\\") {
57                if string_segment.is_empty() {
58                    if components.is_empty() {
59                        components.push(PathComponent::Root);
60                    }
61                } else if string_segment == "." {
62                    if components.is_empty() {
63                        components.push(PathComponent::Current);
64                    }
65                } else if string_segment == ".." {
66                    match components.last() {
67                        None | Some(PathComponent::Parent) => {
68                            components.push(PathComponent::Parent);
69                        }
70                        Some(PathComponent::Root) => {}
71                        _ => _ = components.pop(),
72                    }
73                } else {
74                    if let Some(PathComponent::Current) = components.last() {
75                        _ = components.pop();
76                    }
77                    let path_component: PathComponent =
78                        PathComponent::Ucs2String(Ucs2String::from(string_segment));
79
80                    components.push(path_component);
81                }
82            }
83            components
84        };
85        Path { components }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_data_fork_name() {
95        let ucs2_string: Ucs2String = Ucs2String::from("");
96        let result: Option<Ucs2String> = WindowsPath::data_fork_name(&ucs2_string);
97        assert_eq!(result, None);
98
99        let ucs2_string: Ucs2String = Ucs2String::from("file");
100        let result: Option<Ucs2String> = WindowsPath::data_fork_name(&ucs2_string);
101        assert_eq!(result, None);
102
103        let ucs2_string: Ucs2String = Ucs2String::from(":fork");
104        let result: Option<Ucs2String> = WindowsPath::data_fork_name(&ucs2_string);
105        assert_eq!(result, Some(Ucs2String::from("fork")));
106
107        let ucs2_string: Ucs2String = Ucs2String::from("file:fork");
108        let result: Option<Ucs2String> = WindowsPath::data_fork_name(&ucs2_string);
109        assert_eq!(result, Some(Ucs2String::from("fork")));
110
111        let ucs2_string: Ucs2String = Ucs2String::from("file:fork1:fork2");
112        let result: Option<Ucs2String> = WindowsPath::data_fork_name(&ucs2_string);
113        // Only the last colon delimiter is used.
114        assert_eq!(result, Some(Ucs2String::from("fork2")));
115
116        let ucs2_string: Ucs2String = Ucs2String::from("file:");
117        let result: Option<Ucs2String> = WindowsPath::data_fork_name(&ucs2_string);
118        assert_eq!(result, Some(Ucs2String::from("")));
119    }
120
121    #[test]
122    fn test_from_str() {
123        let test_struct: Path = WindowsPath::from_str("\\");
124        assert_eq!(test_struct.components, vec![PathComponent::Root]);
125
126        let test_struct: Path = WindowsPath::from_str("\\directory");
127        assert_eq!(
128            test_struct,
129            Path {
130                components: vec![
131                    PathComponent::Root,
132                    PathComponent::Ucs2String(Ucs2String::from("directory"))
133                ]
134            }
135        );
136
137        let test_struct: Path = WindowsPath::from_str("\\directory\\filename.txt");
138        assert_eq!(
139            test_struct,
140            Path {
141                components: vec![
142                    PathComponent::Root,
143                    PathComponent::Ucs2String(Ucs2String::from("directory")),
144                    PathComponent::Ucs2String(Ucs2String::from("filename.txt")),
145                ]
146            }
147        );
148
149        let test_struct: Path = WindowsPath::from_str("\\directory\\.\\filename.txt");
150        assert_eq!(
151            test_struct,
152            Path {
153                components: vec![
154                    PathComponent::Root,
155                    PathComponent::Ucs2String(Ucs2String::from("directory")),
156                    PathComponent::Ucs2String(Ucs2String::from("filename.txt")),
157                ]
158            }
159        );
160
161        let test_struct: Path = WindowsPath::from_str("\\directory\\");
162        assert_eq!(
163            test_struct,
164            Path {
165                components: vec![
166                    PathComponent::Root,
167                    PathComponent::Ucs2String(Ucs2String::from("directory"))
168                ]
169            }
170        );
171
172        let test_struct: Path = WindowsPath::from_str(".\\directory");
173        assert_eq!(
174            test_struct,
175            Path {
176                components: vec![PathComponent::Ucs2String(Ucs2String::from("directory"))]
177            }
178        );
179
180        let test_struct: Path = WindowsPath::from_str("..\\directory");
181        assert_eq!(
182            test_struct,
183            Path {
184                components: vec![
185                    PathComponent::Parent,
186                    PathComponent::Ucs2String(Ucs2String::from("directory"))
187                ]
188            }
189        );
190
191        let test_struct: Path = WindowsPath::from_str("..\\directory\\..\\filename.txt");
192        assert_eq!(
193            test_struct,
194            Path {
195                components: vec![
196                    PathComponent::Parent,
197                    PathComponent::Ucs2String(Ucs2String::from("filename.txt"))
198                ]
199            }
200        );
201
202        let test_struct: Path = WindowsPath::from_str("..\\..\\directory");
203        assert_eq!(
204            test_struct,
205            Path {
206                components: vec![
207                    PathComponent::Parent,
208                    PathComponent::Parent,
209                    PathComponent::Ucs2String(Ucs2String::from("directory")),
210                ]
211            }
212        );
213
214        let test_struct: Path = WindowsPath::from_str("\\..\\directory");
215        assert_eq!(
216            test_struct,
217            Path {
218                components: vec![
219                    PathComponent::Root,
220                    PathComponent::Ucs2String(Ucs2String::from("directory"))
221                ]
222            }
223        );
224
225        let test_struct: Path = WindowsPath::from_str(".");
226        assert_eq!(
227            test_struct,
228            Path {
229                components: vec![PathComponent::Current]
230            }
231        );
232
233        // Note that the result of from_str may be different when the path is invalid.
234        let test_struct: Path = WindowsPath::from_str("");
235        assert_eq!(test_struct, Path { components: vec![] });
236    }
237
238    #[test]
239    fn test_from_str_with_prefix() {
240        let test_struct: Path = WindowsPath::from_str("\\\\.\\C:\\testdir1\\testfile1");
241        assert_eq!(
242            test_struct,
243            Path {
244                components: vec![
245                    PathComponent::Ucs2String(Ucs2String::from("C:")),
246                    PathComponent::Ucs2String(Ucs2String::from("testdir1")),
247                    PathComponent::Ucs2String(Ucs2String::from("testfile1")),
248                ]
249            }
250        );
251
252        let test_struct: Path = WindowsPath::from_str("\\\\?\\C:\\testdir1\\testfile1");
253        assert_eq!(
254            test_struct,
255            Path {
256                components: vec![
257                    PathComponent::Ucs2String(Ucs2String::from("C:")),
258                    PathComponent::Ucs2String(Ucs2String::from("testdir1")),
259                    PathComponent::Ucs2String(Ucs2String::from("testfile1")),
260                ]
261            }
262        );
263
264        let test_struct: Path = WindowsPath::from_str("\\??\\C:\\testdir1\\testfile1");
265        assert_eq!(
266            test_struct,
267            Path {
268                components: vec![
269                    PathComponent::Ucs2String(Ucs2String::from("C:")),
270                    PathComponent::Ucs2String(Ucs2String::from("testdir1")),
271                    PathComponent::Ucs2String(Ucs2String::from("testfile1")),
272                ]
273            }
274        );
275    }
276
277    #[test]
278    fn test_from_str_with_drive_letter() {
279        let test_struct: Path = WindowsPath::from_str("C:");
280        assert_eq!(
281            test_struct,
282            Path {
283                components: vec![PathComponent::Ucs2String(Ucs2String::from("C:"))]
284            }
285        );
286    }
287}