use crate::error_path_exception::ErrorPathException;
#[derive(Debug, Clone)]
pub struct ResourceLocator {
work_dir: Vec<String>,
saved_stack: Vec<Vec<String>>,
}
impl ResourceLocator {
#[must_use]
pub fn new() -> Self {
Self {
work_dir: vec!["/".to_string()],
saved_stack: Vec::new(),
}
}
pub fn save(&mut self) {
self.saved_stack.push(self.work_dir.clone());
}
pub fn restore(&mut self) {
if let Some(saved) = self.saved_stack.pop() {
self.work_dir = saved;
}
}
pub fn cd(&mut self, path: &str) -> Result<(), ErrorPathException> {
if path.is_empty() {
return Ok(());
}
let path = path.trim();
if path == "/" {
self.work_dir.clear();
self.work_dir.push("/".to_string());
return Ok(());
}
let abs_path = self.to_absolute_path(path);
self.work_dir.clear();
self.work_dir.push("/".to_string());
for segment in abs_path.split('/') {
let segment = segment.trim();
if segment.is_empty() || segment == "." {
continue;
}
self.work_dir.push(segment.to_string());
}
Ok(())
}
pub fn reset(&mut self) {
self.work_dir.clear();
self.work_dir.push("/".to_string());
}
#[must_use]
pub fn pwd(&self) -> String {
Self::pwd_of(&self.work_dir)
}
#[must_use]
pub fn to_absolute_path(&self, path: &str) -> String {
if path.is_empty() {
return self.pwd();
}
let path = path.trim();
let mut segments: Vec<String> = if path.starts_with('/') {
vec!["/".to_string()]
} else {
self.work_dir.clone()
};
for item in path.split('/') {
let item = item.trim();
if item == "." || item.is_empty() {
continue;
}
if item == ".." {
segments.pop();
if segments.is_empty() {
segments.push("/".to_string());
}
} else {
segments.push(item.to_string());
}
}
Self::pwd_of(&segments)
}
#[must_use]
pub fn get_abs_to(&self, path: &str) -> String {
if path.is_empty() {
return self.pwd();
}
if path.starts_with('/') {
return path.to_string();
}
if let Some(idx) = path.rfind('/') {
let dir_part = &path[..=idx];
let file_part = &path[idx + 1..];
let wd = self.work_dir.clone();
let abs_dir = Self::to_absolute_path_of(&wd, dir_part);
if abs_dir.ends_with('/') {
format!("{abs_dir}{file_part}")
} else {
format!("{abs_dir}/{file_part}")
}
} else {
let pwd = self.pwd();
if pwd.ends_with('/') {
format!("{pwd}{path}")
} else {
format!("{pwd}/{path}")
}
}
}
fn pwd_of(segments: &[String]) -> String {
if segments.len() <= 1 {
return "/".to_string();
}
let mut result = String::new();
for (i, item) in segments.iter().enumerate() {
let item = item.trim();
if item.is_empty() {
continue;
}
result.push_str(item);
if item != "/" && i != segments.len() - 1 {
result.push('/');
}
}
result
}
fn to_absolute_path_of(segments: &[String], path: &str) -> String {
if path.is_empty() {
return Self::pwd_of(segments);
}
let path = path.trim();
let mut work: Vec<String> = if path.starts_with('/') {
vec!["/".to_string()]
} else {
segments.to_vec()
};
for item in path.split('/') {
let item = item.trim();
if item == "." || item.is_empty() {
continue;
}
if item == ".." {
work.pop();
if work.is_empty() {
work.push("/".to_string());
}
} else {
work.push(item.to_string());
}
}
Self::pwd_of(&work)
}
}
impl Default for ResourceLocator {
fn default() -> Self {
Self::new()
}
}
pub mod patterns {
pub fn is_doc_dir(segment: &str) -> bool {
segment.starts_with("Doc_")
&& !segment[4..].is_empty()
&& segment[4..].chars().all(|c| c.is_ascii_digit())
}
pub fn is_page_dir(segment: &str) -> bool {
segment.starts_with("Page_")
&& !segment[5..].is_empty()
&& segment[5..].chars().all(|c| c.is_ascii_digit())
}
pub fn is_sign_dir(segment: &str) -> bool {
segment.starts_with("Sign_")
&& !segment[5..].is_empty()
&& segment[5..].chars().all(|c| c.is_ascii_digit())
}
pub fn is_res_dir(segment: &str) -> bool {
segment == "Res"
}
pub fn is_pages_dir(segment: &str) -> bool {
segment == "Pages"
}
pub fn is_signs_dir(segment: &str) -> bool {
segment == "Signs"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_default_root() {
let rl = ResourceLocator::new();
assert_eq!(rl.pwd(), "/");
}
#[test]
fn test_cd_root() {
let mut rl = ResourceLocator::new();
rl.cd("/Doc_0").unwrap();
assert_eq!(rl.pwd(), "/Doc_0");
rl.cd("/").unwrap();
assert_eq!(rl.pwd(), "/");
}
#[test]
fn test_cd_relative() {
let mut rl = ResourceLocator::new();
rl.cd("Doc_0").unwrap();
assert_eq!(rl.pwd(), "/Doc_0");
rl.cd("Pages").unwrap();
assert_eq!(rl.pwd(), "/Doc_0/Pages");
}
#[test]
fn test_cd_parent() {
let mut rl = ResourceLocator::new();
rl.cd("/Doc_0/Pages/Page_0").unwrap();
rl.cd("..").unwrap();
assert_eq!(rl.pwd(), "/Doc_0/Pages");
}
#[test]
fn test_save_restore() {
let mut rl = ResourceLocator::new();
rl.cd("/Doc_0").unwrap();
rl.save();
rl.cd("Pages").unwrap();
assert_eq!(rl.pwd(), "/Doc_0/Pages");
rl.restore();
assert_eq!(rl.pwd(), "/Doc_0");
}
#[test]
fn test_save_restore_nested() {
let mut rl = ResourceLocator::new();
rl.cd("/Doc_0").unwrap();
rl.save();
rl.cd("Pages").unwrap();
rl.save();
rl.cd("Page_0").unwrap();
assert_eq!(rl.pwd(), "/Doc_0/Pages/Page_0");
rl.restore();
assert_eq!(rl.pwd(), "/Doc_0/Pages");
rl.restore();
assert_eq!(rl.pwd(), "/Doc_0");
}
#[test]
fn test_restore_empty_stack() {
let mut rl = ResourceLocator::new();
rl.cd("/Doc_0").unwrap();
rl.restore();
assert_eq!(rl.pwd(), "/Doc_0");
}
#[test]
fn test_to_absolute_path_absolute() {
let mut rl = ResourceLocator::new();
rl.cd("/Doc_0/Pages").unwrap();
assert_eq!(rl.to_absolute_path("/Doc_1/Res"), "/Doc_1/Res");
}
#[test]
fn test_to_absolute_path_relative() {
let mut rl = ResourceLocator::new();
rl.cd("/Doc_0").unwrap();
assert_eq!(rl.to_absolute_path("Res/image.png"), "/Doc_0/Res/image.png");
}
#[test]
fn test_to_absolute_path_parent() {
let mut rl = ResourceLocator::new();
rl.cd("/Doc_0/Pages/Page_0").unwrap();
assert_eq!(rl.to_absolute_path("../Res"), "/Doc_0/Pages/Res");
}
#[test]
fn test_get_abs_to_absolute() {
let rl = ResourceLocator::new();
assert_eq!(rl.get_abs_to("/Doc_0/Res"), "/Doc_0/Res");
}
#[test]
fn test_get_abs_to_relative() {
let mut rl = ResourceLocator::new();
rl.cd("/Doc_0").unwrap();
assert_eq!(rl.get_abs_to("Res/image.png"), "/Doc_0/Res/image.png");
}
#[test]
fn test_get_abs_to_filename_only() {
let mut rl = ResourceLocator::new();
rl.cd("/Doc_0/Pages/Page_0").unwrap();
assert_eq!(
rl.get_abs_to("Content.xml"),
"/Doc_0/Pages/Page_0/Content.xml"
);
}
#[test]
fn test_reset() {
let mut rl = ResourceLocator::new();
rl.cd("/Doc_0/Pages").unwrap();
rl.reset();
assert_eq!(rl.pwd(), "/");
}
#[test]
fn test_cd_empty_string() {
let mut rl = ResourceLocator::new();
rl.cd("/Doc_0").unwrap();
rl.cd("").unwrap();
assert_eq!(rl.pwd(), "/Doc_0");
}
#[test]
fn test_patterns() {
assert!(patterns::is_doc_dir("Doc_0"));
assert!(patterns::is_doc_dir("Doc_12"));
assert!(!patterns::is_doc_dir("Doc_"));
assert!(!patterns::is_doc_dir("Pages"));
assert!(patterns::is_page_dir("Page_0"));
assert!(patterns::is_page_dir("Page_99"));
assert!(!patterns::is_page_dir("Page_"));
assert!(patterns::is_sign_dir("Sign_0"));
assert!(patterns::is_res_dir("Res"));
assert!(patterns::is_pages_dir("Pages"));
assert!(patterns::is_signs_dir("Signs"));
}
}