diff --git a/src/lib.rs b/src/lib.rs index 8b13789..f174860 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1 @@ - +pub mod virtual_fs; diff --git a/src/virtual_fs.rs b/src/virtual_fs.rs new file mode 100644 index 0000000..a9672c0 --- /dev/null +++ b/src/virtual_fs.rs @@ -0,0 +1,154 @@ +use std::collections::HashMap; + +#[derive(Debug, Default, Clone)] +pub struct VFolder { + folders: HashMap, + files: HashMap, +} +impl VFolder { + pub fn new() -> VFolder { + VFolder { + folders: HashMap::new(), + files: HashMap::new(), + } + } + + pub fn get_folders(&self) -> &HashMap { + &self.folders + } + pub fn get_files(&self) -> &HashMap { + &self.files + } + + pub fn add_folder(&mut self, name: &str) { + self.add_existing_folder(name, VFolder::new()); + } + pub fn add_existing_folder(&mut self, name: &str, folder: VFolder) { + let (head, tail) = name + .split_once('/') + .map(|(h, t)| (h, (!t.is_empty()).then_some(t))) + .unwrap_or((name, None)); + if let Some(tail) = tail { + if let Some(subfolder) = self.get_folder_mut(head) { + subfolder.add_folder(tail); + } else { + let mut new_folder = VFolder::new(); + new_folder.add_folder(tail); + self.add_existing_folder(head, new_folder); + } + } else { + self.folders.insert(name.to_string(), folder); + } + } + pub fn add_file(&mut self, name: &str, file: VFile) { + let (head, tail) = name + .split_once('/') + .map(|(h, t)| (h, (!t.is_empty()).then_some(t))) + .unwrap_or((name, None)); + if let Some(tail) = tail { + if let Some(subfolder) = self.get_folder_mut(head) { + subfolder.add_file(tail, file); + } else { + let mut new_folder = VFolder::new(); + new_folder.add_file(tail, file); + self.add_existing_folder(head, new_folder); + } + } else { + self.files.insert(name.to_string(), file); + } + } + + pub fn get_folder(&self, name: &str) -> Option<&VFolder> { + let (head, tail) = name + .split_once('/') + .map(|(h, t)| (h, (!t.is_empty()).then_some(t))) + .unwrap_or((name, None)); + if let Some(tail) = tail { + self.folders.get(head)?.get_folder(tail) + } else { + self.folders.get(name) + } + } + pub fn get_folder_mut(&mut self, name: &str) -> Option<&mut VFolder> { + let (head, tail) = name + .split_once('/') + .map(|(h, t)| (h, (!t.is_empty()).then_some(t))) + .unwrap_or((name, None)); + if let Some(tail) = tail { + self.folders.get_mut(head)?.get_folder_mut(tail) + } else { + self.folders.get_mut(name) + } + } + pub fn get_file(&self, name: &str) -> Option<&VFile> { + let (head, tail) = name + .split_once('/') + .map(|(h, t)| (h, (!t.is_empty()).then_some(t))) + .unwrap_or((name, None)); + if let Some(tail) = tail { + self.folders.get(head)?.get_file(tail) + } else { + self.files.get(name) + } + } + pub fn get_file_mut(&mut self, name: &str) -> Option<&mut VFile> { + let (head, tail) = name + .split_once('/') + .map(|(h, t)| (h, (!t.is_empty()).then_some(t))) + .unwrap_or((name, None)); + if let Some(tail) = tail { + self.folders.get_mut(head)?.get_file_mut(tail) + } else { + self.files.get_mut(name) + } + } +} + +#[derive(Debug, Clone)] +pub enum VFile { + Text(String), + Binary(Vec), +} + +impl From for VFile { + fn from(value: String) -> Self { + VFile::Text(value) + } +} +impl From<&str> for VFile { + fn from(value: &str) -> Self { + VFile::Text(value.to_string()) + } +} +impl Default for VFile { + fn default() -> Self { + VFile::Text(String::new()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vfolder() { + let mut v_folder = VFolder::new(); + let v_file_1 = VFile::from("foo"); + v_folder.add_file("foo.txt", v_file_1); + + assert_eq!(v_folder.get_files().len(), 1); + assert_eq!(v_folder.get_folders().len(), 0); + + let v_file_2 = VFile::from("baz"); + v_folder.add_file("bar/baz.txt", v_file_2); + + assert_eq!(v_folder.get_files().len(), 1); + assert_eq!(v_folder.get_folders().len(), 1); + assert!(v_folder.get_file("bar/baz.txt").is_some()); + assert!(v_folder + .get_folder("bar") + .expect("folder not found") + .get_file("baz.txt") + .is_some()); + } +}