use std::{ path::{Path, PathBuf}, ops::{Deref, DerefMut}, env, io }; use serde::{ ser::{Serialize, Serializer}, de::{Deserialize, Deserializer} }; #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct CwdBaseDir(PathBuf); impl CwdBaseDir { /// Creates a new `CwdBaseDir` instance containing exactly the given path. pub fn new_unchanged(path: PathBuf) -> Self { CwdBaseDir(path) } /// Creates a `CwdBaseDir` from a path by prefixing the path with the /// current working dir if it's relative. /// /// If the path is not relative it's directly used. /// /// # Os state side effects /// /// As this function accesses the current working directory (CWD) it's /// not pure as the CWD can be changed (e.g. by `std::env::set_current_dir`). /// /// # Error /// /// As getting the CWD can fail this function can fail with a I/O Error, too. pub fn from_path

(path: P) -> Result where P: AsRef + Into { let path = if path.as_ref().is_absolute() { path.into() } else { let mut cwd = env::current_dir()?; cwd.push(path.as_ref()); cwd }; Ok(CwdBaseDir(path)) } /// Turns this path into a `PathBuf` by stripping the current working dir /// if it starts with it. /// /// If this path does not start with the CWD it's returned directly. /// /// # Os state side effects /// /// As this function used the current working dir (CWD) it is affected /// by any function changing the CWD as a side effect. /// /// # Error /// /// Accessing the current working dir can fail, as such this function /// can fail. pub fn to_base_path(&self) -> Result<&Path, io::Error> { let cwd = env::current_dir()?; self.strip_prefix(&cwd) .or_else(|_err_does_not_has_that_prefix| { Ok(&self) }) } /// Turns this instance into the `PathBuf` it dereferences to. pub fn into_inner_with_prefix(self) -> PathBuf { let CwdBaseDir(path) = self; path } } impl Deref for CwdBaseDir { type Target = PathBuf; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for CwdBaseDir { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl AsRef for CwdBaseDir { fn as_ref(&self) -> &Path { &self.0 } } impl<'de> Deserialize<'de> for CwdBaseDir { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { use serde::de::Error; let path_buf = PathBuf::deserialize(deserializer)?; Self::from_path(path_buf) .map_err(|err| D::Error::custom(err)) } } impl Serialize for CwdBaseDir { fn serialize(&self, serializer: S) -> Result where S: Serializer, { use serde::ser::Error; let path = self.to_base_path() .map_err(|err| S::Error::custom(err))?; path.serialize(serializer) } } #[cfg(test)] mod tests { use super::*; #[test] fn from_path_does_not_affect_absolute_paths() { let path = Path::new("/the/dog"); let base_dir = CwdBaseDir::from_path(path).unwrap(); assert_eq!(&*base_dir, Path::new("/the/dog")) } #[test] fn from_path_prefixes_with_cwd() { let cwd = env::current_dir().unwrap(); let expected = cwd.join("./the/dog"); let base_dir = CwdBaseDir::from_path("./the/dog").unwrap(); assert_eq!(&*base_dir, &expected); } #[test] fn to_base_path_removes_cwd_prefix() { let cwd = env::current_dir().unwrap(); let dir = cwd.join("hy/there"); let base_dir = CwdBaseDir::new_unchanged(dir); let path = base_dir.to_base_path().unwrap(); assert_eq!(path, Path::new("hy/there")); } }