1use crate::error::CliError;
6
7#[derive(Debug, PartialEq)]
24pub struct Path<'a, const MAX_DEPTH: usize> {
25 is_absolute: bool,
27
28 segments: heapless::Vec<&'a str, MAX_DEPTH>,
30}
31
32impl<'a, const MAX_DEPTH: usize> Path<'a, MAX_DEPTH> {
33 pub fn parse(input: &'a str) -> Result<Self, CliError> {
39 if input.is_empty() {
41 return Err(CliError::InvalidPath);
42 }
43
44 let is_absolute = input.starts_with('/');
46
47 let path_str = if is_absolute { &input[1..] } else { input };
49
50 let mut segments = heapless::Vec::new();
52
53 if path_str.is_empty() {
55 if is_absolute {
57 return Ok(Self {
58 is_absolute,
59 segments,
60 });
61 } else {
62 return Err(CliError::InvalidPath);
64 }
65 }
66
67 for segment in path_str.split('/') {
69 if segment.is_empty() {
71 continue;
72 }
73
74 segments.push(segment).map_err(|_| CliError::PathTooDeep)?;
76 }
77
78 Ok(Self {
79 is_absolute,
80 segments,
81 })
82 }
83
84 pub fn is_absolute(&self) -> bool {
86 self.is_absolute
87 }
88
89 pub fn segments(&self) -> &[&'a str] {
91 &self.segments
92 }
93
94 pub fn segment_count(&self) -> usize {
96 self.segments.len()
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use crate::config::{DefaultConfig, MinimalConfig, ShellConfig};
104
105 type TestPath<'a> = Path<'a, { DefaultConfig::MAX_PATH_DEPTH }>;
107
108 #[test]
109 fn test_empty_path_is_invalid() {
110 let result = TestPath::parse("");
111 assert_eq!(result, Err(CliError::InvalidPath));
112 }
113
114 #[test]
115 fn test_absolute_root() {
116 let path = TestPath::parse("/").unwrap();
117 assert!(path.is_absolute());
118 assert_eq!(path.segments(), &[] as &[&str]);
119 assert_eq!(path.segment_count(), 0);
120 }
121
122 #[test]
123 fn test_absolute_single_segment() {
124 let path = TestPath::parse("/system").unwrap();
125 assert!(path.is_absolute());
126 assert_eq!(path.segments(), &["system"]);
127 assert_eq!(path.segment_count(), 1);
128 }
129
130 #[test]
131 fn test_absolute_multiple_segments() {
132 let path = TestPath::parse("/system/network/status").unwrap();
133 assert!(path.is_absolute());
134 assert_eq!(path.segments(), &["system", "network", "status"]);
135 assert_eq!(path.segment_count(), 3);
136 }
137
138 #[test]
139 fn test_relative_single_segment() {
140 let path = TestPath::parse("help").unwrap();
141 assert!(!path.is_absolute());
142 assert_eq!(path.segments(), &["help"]);
143 }
144
145 #[test]
146 fn test_relative_multiple_segments() {
147 let path = TestPath::parse("system/network").unwrap();
148 assert!(!path.is_absolute());
149 assert_eq!(path.segments(), &["system", "network"]);
150 }
151
152 #[test]
153 fn test_parent_navigation() {
154 let path = TestPath::parse("..").unwrap();
155 assert!(!path.is_absolute());
156 assert_eq!(path.segments(), &[".."]);
157
158 let path = TestPath::parse("../system").unwrap();
159 assert_eq!(path.segments(), &["..", "system"]);
160
161 let path = TestPath::parse("../../hw/led").unwrap();
162 assert_eq!(path.segments(), &["..", "..", "hw", "led"]);
163 }
164
165 #[test]
166 fn test_current_directory() {
167 let path = TestPath::parse(".").unwrap();
168 assert!(!path.is_absolute());
169 assert_eq!(path.segments(), &["."]);
170
171 let path = TestPath::parse("./cmd").unwrap();
172 assert_eq!(path.segments(), &[".", "cmd"]);
173 }
174
175 #[test]
176 fn test_mixed_navigation() {
177 let path = TestPath::parse("../system/./network").unwrap();
178 assert_eq!(path.segments(), &["..", "system", ".", "network"]);
179 }
180
181 #[test]
182 fn test_trailing_slash_ignored() {
183 let path = TestPath::parse("/system/").unwrap();
184 assert!(path.is_absolute());
185 assert_eq!(path.segments(), &["system"]);
186
187 let path = TestPath::parse("network/").unwrap();
188 assert!(!path.is_absolute());
189 assert_eq!(path.segments(), &["network"]);
190 }
191
192 #[test]
193 fn test_double_slash_treated_as_single() {
194 let path = TestPath::parse("/system//network").unwrap();
195 assert_eq!(path.segments(), &["system", "network"]);
196
197 let path = TestPath::parse("//system").unwrap();
198 assert!(path.is_absolute());
199 assert_eq!(path.segments(), &["system"]);
200 }
201
202 #[test]
203 fn test_path_too_deep() {
204 let deep_path = "a/b/c/d/e/f/g/h/i/j/k";
206 let result = TestPath::parse(deep_path);
207 assert_eq!(result, Err(CliError::PathTooDeep));
208 }
209
210 #[test]
211 fn test_max_depth_exactly() {
212 let path = TestPath::parse("a/b/c/d/e/f/g/h").unwrap();
214 assert_eq!(path.segment_count(), 8);
215 }
216
217 #[test]
218 fn test_absolute_path_with_parent() {
219 let path = TestPath::parse("/../system").unwrap();
220 assert!(path.is_absolute());
221 assert_eq!(path.segments(), &["..", "system"]);
222 }
223
224 #[test]
225 fn test_minimal_config_respects_depth() {
226 type MinimalPath<'a> = Path<'a, { MinimalConfig::MAX_PATH_DEPTH }>;
227
228 let path = MinimalPath::parse("a/b/c/d").unwrap();
231 assert_eq!(path.segment_count(), 4);
232
233 let result = MinimalPath::parse("a/b/c/d/e");
235 assert_eq!(result, Err(CliError::PathTooDeep));
236 }
237
238 #[test]
239 fn test_default_config_allows_deeper_paths() {
240 let path = TestPath::parse("a/b/c/d/e/f/g/h").unwrap();
242 assert_eq!(path.segment_count(), 8);
243
244 type MinimalPath<'a> = Path<'a, { MinimalConfig::MAX_PATH_DEPTH }>;
246 let result = MinimalPath::parse("a/b/c/d/e/f/g/h");
247 assert_eq!(result, Err(CliError::PathTooDeep));
248 }
249}