keramics_formats/
path_component.rs

1/* Copyright 2024-2025 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
14// TODO: add Utf16String support.
15use keramics_types::{ByteString, Ucs2String};
16
17/// Path component for file resolver.
18#[derive(Clone, Debug, PartialEq)]
19pub enum PathComponent {
20    ByteString(ByteString),
21    String(String),
22    Ucs2String(Ucs2String),
23}
24
25impl PathComponent {
26    /// Retrieves the extension if available.
27    pub fn extension(&self) -> Option<PathComponent> {
28        match self {
29            PathComponent::String(string) => {
30                if string.is_empty() {
31                    None
32                } else {
33                    match string[1..].chars().rev().position(|value| value == '.') {
34                        Some(value_index) => {
35                            // Note that value_index is relative to end of the string.
36                            let string_index: usize = string.len() - value_index;
37                            Some(PathComponent::String(string[string_index..].to_string()))
38                        }
39                        None => None,
40                    }
41                }
42            }
43            _ => todo!(),
44        }
45    }
46
47    /// Retrieves the file stem if available.
48    pub fn file_stem(&self) -> Option<PathComponent> {
49        match self {
50            PathComponent::String(string) => {
51                if string.is_empty() {
52                    None
53                } else {
54                    match string[1..].chars().rev().position(|value| value == '.') {
55                        Some(value_index) => {
56                            // Note that value_index is relative to end of the string.
57                            let string_size: usize = string.len() - value_index - 1;
58                            Some(PathComponent::String(string[0..string_size].to_string()))
59                        }
60                        None => Some(PathComponent::String(string.clone())),
61                    }
62                }
63            }
64            _ => todo!(),
65        }
66    }
67
68    /// Converts the path components to a `String`.
69    pub fn to_string(&self) -> String {
70        match self {
71            PathComponent::ByteString(byte_string) => byte_string.to_string(),
72            PathComponent::String(string) => string.clone(),
73            PathComponent::Ucs2String(ucs2_string) => ucs2_string.to_string(),
74        }
75    }
76}
77
78impl From<&str> for PathComponent {
79    /// Converts a [`&str`] into a [`PathComponent`]
80    fn from(string: &str) -> Self {
81        Self::String(string.to_string())
82    }
83}
84
85impl From<&String> for PathComponent {
86    /// Converts a [`&String`] into a [`PathComponent`]
87    fn from(string: &String) -> Self {
88        Self::String(string.clone())
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn test_extension() {
98        let path_component: PathComponent = PathComponent::from("");
99        let result: Option<PathComponent> = path_component.extension();
100        assert_eq!(result, None);
101
102        let path_component: PathComponent = PathComponent::from("file");
103        let result: Option<PathComponent> = path_component.extension();
104        assert_eq!(result, None);
105
106        let path_component: PathComponent = PathComponent::from(".file");
107        let result: Option<PathComponent> = path_component.extension();
108        assert_eq!(result, None);
109
110        let path_component: PathComponent = PathComponent::from("file.txt");
111        let result: Option<PathComponent> = path_component.extension();
112        assert_eq!(result, Some(PathComponent::from("txt")));
113    }
114
115    #[test]
116    fn test_file_stem() {
117        let path_component: PathComponent = PathComponent::from("");
118        let result: Option<PathComponent> = path_component.file_stem();
119        assert_eq!(result, None);
120
121        let path_component: PathComponent = PathComponent::from("file");
122        let result: Option<PathComponent> = path_component.file_stem();
123        assert_eq!(result, Some(PathComponent::from("file")));
124
125        let path_component: PathComponent = PathComponent::from(".file");
126        let result: Option<PathComponent> = path_component.file_stem();
127        assert_eq!(result, Some(PathComponent::from(".file")));
128
129        let path_component: PathComponent = PathComponent::from("file.txt");
130        let result: Option<PathComponent> = path_component.file_stem();
131        assert_eq!(result, Some(PathComponent::from("file")));
132    }
133
134    #[test]
135    fn test_to_string() {
136        let path_component: PathComponent = PathComponent::from("test");
137
138        assert_eq!(path_component.to_string(), String::from("test"));
139    }
140
141    #[test]
142    fn test_from_str() {
143        let path_component: PathComponent = PathComponent::from("test");
144
145        assert_eq!(path_component, PathComponent::String(String::from("test")));
146    }
147
148    #[test]
149    fn test_from_string() {
150        let string: String = String::from("test");
151        let path_component: PathComponent = PathComponent::from(&string);
152
153        assert_eq!(path_component, PathComponent::String(String::from("test")));
154    }
155}