easyofd_reader/
resource_locator.rs1use crate::error_path_exception::ErrorPathException;
9
10#[derive(Debug, Clone)]
17pub struct ResourceLocator {
18 work_dir: Vec<String>,
20 saved_stack: Vec<Vec<String>>,
22}
23
24impl ResourceLocator {
25 #[must_use]
27 pub fn new() -> Self {
28 Self {
29 work_dir: vec!["/".to_string()],
30 saved_stack: Vec::new(),
31 }
32 }
33
34 pub fn save(&mut self) {
38 self.saved_stack.push(self.work_dir.clone());
39 }
40
41 pub fn restore(&mut self) {
47 if let Some(saved) = self.saved_stack.pop() {
48 self.work_dir = saved;
49 }
50 }
51
52 pub fn cd(&mut self, path: &str) -> Result<(), ErrorPathException> {
60 if path.is_empty() {
61 return Ok(());
62 }
63 let path = path.trim();
64 if path == "/" {
65 self.work_dir.clear();
66 self.work_dir.push("/".to_string());
67 return Ok(());
68 }
69 let abs_path = self.to_absolute_path(path);
71 self.work_dir.clear();
73 self.work_dir.push("/".to_string());
74 for segment in abs_path.split('/') {
75 let segment = segment.trim();
76 if segment.is_empty() || segment == "." {
77 continue;
78 }
79 self.work_dir.push(segment.to_string());
80 }
81 Ok(())
82 }
83
84 pub fn reset(&mut self) {
88 self.work_dir.clear();
89 self.work_dir.push("/".to_string());
90 }
91
92 #[must_use]
96 pub fn pwd(&self) -> String {
97 Self::pwd_of(&self.work_dir)
98 }
99
100 #[must_use]
104 pub fn to_absolute_path(&self, path: &str) -> String {
105 if path.is_empty() {
106 return self.pwd();
107 }
108 let path = path.trim();
109 let mut segments: Vec<String> = if path.starts_with('/') {
110 vec!["/".to_string()]
111 } else {
112 self.work_dir.clone()
113 };
114
115 for item in path.split('/') {
116 let item = item.trim();
117 if item == "." || item.is_empty() {
118 continue;
119 }
120 if item == ".." {
121 segments.pop();
122 if segments.is_empty() {
123 segments.push("/".to_string());
124 }
125 } else {
126 segments.push(item.to_string());
127 }
128 }
129 Self::pwd_of(&segments)
130 }
131
132 #[must_use]
136 pub fn get_abs_to(&self, path: &str) -> String {
137 if path.is_empty() {
138 return self.pwd();
139 }
140 if path.starts_with('/') {
141 return path.to_string();
142 }
143 if let Some(idx) = path.rfind('/') {
145 let dir_part = &path[..=idx];
146 let file_part = &path[idx + 1..];
147 let wd = self.work_dir.clone();
148 let abs_dir = Self::to_absolute_path_of(&wd, dir_part);
149 if abs_dir.ends_with('/') {
150 format!("{abs_dir}{file_part}")
151 } else {
152 format!("{abs_dir}/{file_part}")
153 }
154 } else {
155 let pwd = self.pwd();
156 if pwd.ends_with('/') {
157 format!("{pwd}{path}")
158 } else {
159 format!("{pwd}/{path}")
160 }
161 }
162 }
163
164 fn pwd_of(segments: &[String]) -> String {
166 if segments.len() <= 1 {
167 return "/".to_string();
168 }
169 let mut result = String::new();
170 for (i, item) in segments.iter().enumerate() {
171 let item = item.trim();
172 if item.is_empty() {
173 continue;
174 }
175 result.push_str(item);
176 if item != "/" && i != segments.len() - 1 {
177 result.push('/');
178 }
179 }
180 result
181 }
182
183 fn to_absolute_path_of(segments: &[String], path: &str) -> String {
185 if path.is_empty() {
186 return Self::pwd_of(segments);
187 }
188 let path = path.trim();
189 let mut work: Vec<String> = if path.starts_with('/') {
190 vec!["/".to_string()]
191 } else {
192 segments.to_vec()
193 };
194
195 for item in path.split('/') {
196 let item = item.trim();
197 if item == "." || item.is_empty() {
198 continue;
199 }
200 if item == ".." {
201 work.pop();
202 if work.is_empty() {
203 work.push("/".to_string());
204 }
205 } else {
206 work.push(item.to_string());
207 }
208 }
209 Self::pwd_of(&work)
210 }
211}
212
213impl Default for ResourceLocator {
214 fn default() -> Self {
215 Self::new()
216 }
217}
218
219pub mod patterns {
223 pub fn is_doc_dir(segment: &str) -> bool {
225 segment.starts_with("Doc_")
226 && !segment[4..].is_empty()
227 && segment[4..].chars().all(|c| c.is_ascii_digit())
228 }
229
230 pub fn is_page_dir(segment: &str) -> bool {
232 segment.starts_with("Page_")
233 && !segment[5..].is_empty()
234 && segment[5..].chars().all(|c| c.is_ascii_digit())
235 }
236
237 pub fn is_sign_dir(segment: &str) -> bool {
239 segment.starts_with("Sign_")
240 && !segment[5..].is_empty()
241 && segment[5..].chars().all(|c| c.is_ascii_digit())
242 }
243
244 pub fn is_res_dir(segment: &str) -> bool {
246 segment == "Res"
247 }
248
249 pub fn is_pages_dir(segment: &str) -> bool {
251 segment == "Pages"
252 }
253
254 pub fn is_signs_dir(segment: &str) -> bool {
256 segment == "Signs"
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn test_new_default_root() {
266 let rl = ResourceLocator::new();
267 assert_eq!(rl.pwd(), "/");
268 }
269
270 #[test]
271 fn test_cd_root() {
272 let mut rl = ResourceLocator::new();
273 rl.cd("/Doc_0").unwrap();
274 assert_eq!(rl.pwd(), "/Doc_0");
275 rl.cd("/").unwrap();
276 assert_eq!(rl.pwd(), "/");
277 }
278
279 #[test]
280 fn test_cd_relative() {
281 let mut rl = ResourceLocator::new();
282 rl.cd("Doc_0").unwrap();
283 assert_eq!(rl.pwd(), "/Doc_0");
284 rl.cd("Pages").unwrap();
285 assert_eq!(rl.pwd(), "/Doc_0/Pages");
286 }
287
288 #[test]
289 fn test_cd_parent() {
290 let mut rl = ResourceLocator::new();
291 rl.cd("/Doc_0/Pages/Page_0").unwrap();
292 rl.cd("..").unwrap();
293 assert_eq!(rl.pwd(), "/Doc_0/Pages");
294 }
295
296 #[test]
297 fn test_save_restore() {
298 let mut rl = ResourceLocator::new();
299 rl.cd("/Doc_0").unwrap();
300 rl.save();
301 rl.cd("Pages").unwrap();
302 assert_eq!(rl.pwd(), "/Doc_0/Pages");
303 rl.restore();
304 assert_eq!(rl.pwd(), "/Doc_0");
305 }
306
307 #[test]
308 fn test_save_restore_nested() {
309 let mut rl = ResourceLocator::new();
310 rl.cd("/Doc_0").unwrap();
311 rl.save();
312 rl.cd("Pages").unwrap();
313 rl.save();
314 rl.cd("Page_0").unwrap();
315 assert_eq!(rl.pwd(), "/Doc_0/Pages/Page_0");
316 rl.restore();
317 assert_eq!(rl.pwd(), "/Doc_0/Pages");
318 rl.restore();
319 assert_eq!(rl.pwd(), "/Doc_0");
320 }
321
322 #[test]
323 fn test_restore_empty_stack() {
324 let mut rl = ResourceLocator::new();
325 rl.cd("/Doc_0").unwrap();
326 rl.restore();
328 assert_eq!(rl.pwd(), "/Doc_0");
329 }
330
331 #[test]
332 fn test_to_absolute_path_absolute() {
333 let mut rl = ResourceLocator::new();
334 rl.cd("/Doc_0/Pages").unwrap();
335 assert_eq!(rl.to_absolute_path("/Doc_1/Res"), "/Doc_1/Res");
336 }
337
338 #[test]
339 fn test_to_absolute_path_relative() {
340 let mut rl = ResourceLocator::new();
341 rl.cd("/Doc_0").unwrap();
342 assert_eq!(rl.to_absolute_path("Res/image.png"), "/Doc_0/Res/image.png");
343 }
344
345 #[test]
346 fn test_to_absolute_path_parent() {
347 let mut rl = ResourceLocator::new();
348 rl.cd("/Doc_0/Pages/Page_0").unwrap();
349 assert_eq!(rl.to_absolute_path("../Res"), "/Doc_0/Pages/Res");
350 }
351
352 #[test]
353 fn test_get_abs_to_absolute() {
354 let rl = ResourceLocator::new();
355 assert_eq!(rl.get_abs_to("/Doc_0/Res"), "/Doc_0/Res");
356 }
357
358 #[test]
359 fn test_get_abs_to_relative() {
360 let mut rl = ResourceLocator::new();
361 rl.cd("/Doc_0").unwrap();
362 assert_eq!(rl.get_abs_to("Res/image.png"), "/Doc_0/Res/image.png");
363 }
364
365 #[test]
366 fn test_get_abs_to_filename_only() {
367 let mut rl = ResourceLocator::new();
368 rl.cd("/Doc_0/Pages/Page_0").unwrap();
369 assert_eq!(
370 rl.get_abs_to("Content.xml"),
371 "/Doc_0/Pages/Page_0/Content.xml"
372 );
373 }
374
375 #[test]
376 fn test_reset() {
377 let mut rl = ResourceLocator::new();
378 rl.cd("/Doc_0/Pages").unwrap();
379 rl.reset();
380 assert_eq!(rl.pwd(), "/");
381 }
382
383 #[test]
384 fn test_cd_empty_string() {
385 let mut rl = ResourceLocator::new();
386 rl.cd("/Doc_0").unwrap();
387 rl.cd("").unwrap();
388 assert_eq!(rl.pwd(), "/Doc_0");
389 }
390
391 #[test]
392 fn test_patterns() {
393 assert!(patterns::is_doc_dir("Doc_0"));
394 assert!(patterns::is_doc_dir("Doc_12"));
395 assert!(!patterns::is_doc_dir("Doc_"));
396 assert!(!patterns::is_doc_dir("Pages"));
397
398 assert!(patterns::is_page_dir("Page_0"));
399 assert!(patterns::is_page_dir("Page_99"));
400 assert!(!patterns::is_page_dir("Page_"));
401
402 assert!(patterns::is_sign_dir("Sign_0"));
403 assert!(patterns::is_res_dir("Res"));
404 assert!(patterns::is_pages_dir("Pages"));
405 assert!(patterns::is_signs_dir("Signs"));
406 }
407}