use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; //TODO: Clean this up. Shouldn't need duplicated DIR_SRC consts about the workspace. const DIR_SRC: &str = "src/"; const DIR_TARGET: &str = "target/"; const DIR_MAIN: &str = const_format::concatcp!(DIR_SRC, "main/"); const DIR_TEST: &str = const_format::concatcp!(DIR_SRC, "test/"); #[derive(Debug, Clone)] pub struct PathHandler { root_path: PathBuf, // This is a short-living binary. This doesn't need an LRU like Moka. derived_path_cache: HashMap, } impl PathHandler { pub fn new(root_path: PathBuf) -> Self { Self::from(root_path) } pub fn root_path(&self) -> PathBuf { self.root_path.clone() } /// This is a readability helper. /// Make sure to set the root of this `PathHandler` to the project root. /// This simply calls upon the root_path() of the `PathHandler`. pub fn project_root(&self) -> PathBuf { self.root_path() } pub fn dir_src(&mut self) -> PathBuf { self.get_path(DIR_SRC) } pub fn dir_target(&mut self) -> PathBuf { self.get_path(DIR_TARGET) } pub fn dir_main(&mut self) -> PathBuf { self.get_path(DIR_MAIN) } pub fn dir_test(&mut self) -> PathBuf { self.get_path(DIR_TEST) } /// Attempts to load from cache, else generates the path and clones it to the cache. /// Returns the requested path. fn get_path(&mut self, k: S) -> PathBuf where S: ToString + AsRef, { self.from_cache(k.as_ref()) .unwrap_or_else(|| { self.gen_key(k.to_string(), self.root_path().join(k.to_string())); self.get_path(k.as_ref()) }) .to_path_buf() } /// Attempts to pull the value for the given key from the cache. fn from_cache>(&self, path_key: S) -> Option { self.derived_path_cache .get(path_key.as_ref()) .and_then(|v| Some(v.to_owned())) } /// Tries to generate a new key-value pair in the cache fn gen_key(&mut self, k: S, v: P) -> Option where P: AsRef, S: ToString, { self.derived_path_cache .insert(k.to_string(), v.as_ref().to_path_buf()) } } impl

From

for PathHandler where P: AsRef, { fn from(value: P) -> Self { Self { root_path: value.as_ref().to_path_buf(), derived_path_cache: Default::default(), } } } pub trait PathHandled { fn set_path_handler(&mut self, ph: Arc>) {} fn with_path_handler(&mut self, ph: Arc>) -> &mut Self { self } } #[cfg(test)] mod tests { use super::*; const ROOT: &str = "/root"; #[test] fn ph_get_path() { let root = PathBuf::from(ROOT); let expected = root.join(DIR_SRC); let mut ph = PathHandler::from(root); assert!(ph.dir_src() == expected); } #[test] fn ph_cache_gen() { let root = PathBuf::from(ROOT); let expected = root.join(DIR_SRC); let ph = PathHandler::from(root); assert!( ph.derived_path_cache .get(DIR_SRC) .is_some_and(|v| *v == expected) ); } #[test] fn ph_cache_pull() { let faux_path = "faux/path"; let mut ph = PathHandler::from("false/root"); ph.derived_path_cache .insert(faux_path.to_string(), PathBuf::from(faux_path)); // Use the method that attempts a fallback. // By using a false root, this will create a different path to the injected one, // making it possible to determine if the cache load fails. // // I.e. // Expected: faux/path as PathBuf // Failed: false/root/faux/path as PathBuf assert!(ph.get_path(faux_path) == PathBuf::from(faux_path)); } }