//! Interface to use paths in the filesystem use alloc::borrow::ToOwned; use alloc::string::String; use derive_more::{Deref, DerefMut}; use super::node::{Interface, Ref}; /// Memory allocated non-empty path structure #[derive(Debug, Deref, DerefMut, Clone, PartialEq, Eq)] pub struct Path(String); impl From<Ref<dyn Interface>> for Path { fn from(value: Ref<dyn Interface>) -> Self { let node = value.lock(); if node.is_root() { Self::new_absolute() } else { let mut parent_path = Self::from(node.get_parent_dir() as Ref<dyn Interface>); parent_path.push(&node.name()); parent_path } } } impl Path { /// Returns a new absolute path (the path of the filesystem's root) pub fn new_absolute() -> Self { Self("/".to_owned()) } /// Returns a new relative path (the path of the "current" node) #[allow(unused)] pub fn new_relative() -> Self { Self(".".to_owned()) } /// Returns whether `self` is relative or not #[allow(unused)] pub fn is_relative(&self) -> bool { // SAFETY: it is ensure in this structure that no empty path can be created unsafe { self.chars().next().unwrap_unchecked() == '.' } } /// Returns whether `self` is absolute or not #[allow(unused)] pub fn is_absolute(&self) -> bool { !self.is_relative() } /// Returns whether `self` represents the root or not /// /// Is `is_relative` if `true`, then this function will always return `false`. #[allow(unused)] pub fn is_root(&self) -> bool { self.is_absolute() && self.ends_with('/') } /// Extends `self` with a node represented as a [`String`] pub fn push(&mut self, node: &str) { if self.0 != "/" { self.extend_one('/'); } self.extend(node.chars()); } /// Truncates `self` to its parent dir /// /// Returns the name of the node truncated, and [`None`] if it does nothing #[allow(unused)] pub fn pop(&mut self) -> Option<String> { if self.0 == "." || self.0 == "/" { return None; }; let binding = self.clone(); let relative = self.is_relative(); let mut ancestors = binding.split('/'); // SAFETY: the path is non empty and not reduced to "/" or "." let node = unsafe { ancestors.next_back().unwrap_unchecked() }.to_owned(); if relative { ancestors.next(); }; self.clone_from(&if relative { Self::new_relative() } else { Self::new_absolute() }); ancestors.for_each(|ancestor| self.push(ancestor)); Some(node) } } #[cfg(test)] mod test { use super::Path; #[test_case] fn absolute() { let root = Path::new_absolute(); assert!(root.is_absolute()); assert!(root.is_root()); assert_eq!(&root.0, "/"); } #[test_case] fn relative() { let current = Path::new_relative(); assert!(current.is_relative()); assert!(!current.is_root()); assert_eq!(¤t.0, "."); } #[test_case] fn absolute_manipulation() { let mut path = Path::new_absolute(); path.push("bin"); path.push("ls"); assert_eq!(&path.0, "/bin/ls"); assert_eq!(&path.pop().unwrap(), "ls"); assert_eq!(&path.0, "/bin"); } #[test_case] fn relative_manipulation() { let mut path = Path::new_relative(); path.push("foo"); path.push("bar"); assert_eq!(&path.0, "./foo/bar"); assert_eq!(&path.pop().unwrap(), "bar"); assert_eq!(&path.0, "./foo"); } }