// This file is part of bsv. // // bsv is free software: you can redistribute it and/or modify it under the // terms of the GNU Affero General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) // any later version. // // bsv is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for // more details. // // You should have received a copy of the Affero GNU General Public License // along with bsv. If not, see . use camino::{Utf8Path, Utf8PathBuf}; use toml::{Value, Table}; use cas_core::{err, Error, Result}; #[derive(Debug, Clone)] pub struct PathPair { pub logic: Utf8PathBuf, pub physic: Utf8PathBuf, } impl PathPair { pub fn new(logic: Utf8PathBuf, physic: Utf8PathBuf) -> Self { Self { logic, physic } } pub fn physic_from_logic>(&self, logic_path: P) -> Result { let mut physic = self.physic.clone(); physic.push( logic_path.as_ref().strip_prefix(&self.logic)? ); Ok(physic) } pub fn logic_from_physic>(&self, physic_path: P) -> Result { let mut logic = self.logic.clone(); logic.push( physic_path.as_ref().strip_prefix(&self.physic)? ); Ok(logic) } } #[derive(Clone, Debug)] pub struct PathMap { pairs: Vec, logic_order: Vec, physic_order: Vec, } impl PathMap { pub fn new() -> PathMap { PathMap { pairs: vec![], logic_order: vec![], physic_order: vec![], } } pub fn from_vec(vec: Vec) -> Result { if let Some(pair) = vec.iter() .filter(|p| !p.logic.is_absolute()) .next() { return err!("relative logic path: {}", pair.logic); } let mut logic_order: Vec<_> = (0..vec.len()).collect(); logic_order.sort_by(|&i0, &i1| vec[i0].logic.cmp(&vec[i1].logic).reverse()); if let Some((&index, _)) = logic_order[..logic_order.len()-1].iter() .zip(logic_order[1..].iter()) .filter(|(&i0, &i1)| vec[i0].logic == vec[i1].logic) .next() { return err!("duplicate logic path: {:?}", vec[index]); } let mut physic_order: Vec<_> = (0..vec.len()).collect(); physic_order.sort_by(|&i0, &i1| vec[i0].physic.cmp(&vec[i1].physic).reverse()); if let Some((&index, _)) = physic_order[..physic_order.len()-1].iter() .zip(physic_order[1..].iter()) .filter(|(&i0, &i1)| vec[i0].physic == vec[i1].physic) .next() { return err!("duplicate physic path: {:?}", vec[index]); } Ok(Self { pairs: vec, logic_order, physic_order, }) } pub fn from_toml_value(config: &Value) -> Result { let map = if let Some(mapping) = config.get("mapping") { if let Some(ref table) = mapping.as_table() { table.iter() .map(|(k, v)| Ok(PathPair{ logic: k.into(), physic: Utf8PathBuf::from_path_buf( Utf8Path::new( v.as_str() .ok_or_else(|| Error::unknown("mapping values must be strings"))? ).canonicalize()? ).or_else(|e| err!("non-unicode character in path: {:?}", e))? })) .collect::>()? } else { err!("mapping must be a table, got {}", mapping)? } } else { vec![] }; Self::from_vec(map) } pub fn to_toml_value(&self) -> Result { let mut table = Table::new(); for pair in self.pairs.iter() { table[pair.logic.as_str()] = pair.physic.as_str().into(); } Ok(Value::Table(table)) } pub fn path_pair_from_logic>(&self, logic_path: P) -> Option<&PathPair> { let path = logic_path.as_ref(); Some(self.logic_order.iter() .map(|&i| &self.pairs[i]) .filter(|p| path.starts_with(&p.logic)) .next()? ) } pub fn path_pair_from_physic>(&self, physic_path: P) -> Option<&PathPair> { let path = physic_path.as_ref(); Some(self.physic_order.iter() .map(|&i| &self.pairs[i]) .filter(|p| path.starts_with(&p.physic)) .next()? ) } pub fn physic_from_logic>(&self, logic_path: P) -> Option { self.path_pair_from_logic(&logic_path)? .physic_from_logic(&logic_path).ok() } pub fn logic_from_physic>(&self, physic_path: P) -> Option { self.path_pair_from_physic(&physic_path)? .logic_from_physic(&physic_path).ok() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_path_map() { let path_map = PathMap::from_vec(vec![ PathPair::new("/foo/bar".into(), "/home/user/bar".into()), ]).unwrap(); assert_eq!( path_map.physic_from_logic("/bar"), None, ); assert_eq!( path_map.physic_from_logic("/foo/bar"), Some("/home/user/bar".into()), ); assert_eq!( path_map.physic_from_logic("/foo/bar/file.txt"), Some("/home/user/bar/file.txt".into()), ); assert_eq!( path_map.logic_from_physic("/home/user"), None, ); assert_eq!( path_map.logic_from_physic("/home/user/bar"), Some("/foo/bar".into()), ); assert_eq!( path_map.logic_from_physic("/home/user/bar/file.txt"), Some("/foo/bar/file.txt".into()), ); } #[test] fn test_path_map_subdirs() { let path_map = PathMap::from_vec(vec![ PathPair::new("/foo/bar/baz".into(), "/home/user/bar/test".into()), PathPair::new("/foo/bar".into(), "/home/user/bar".into()), ]).unwrap(); assert_eq!( path_map.physic_from_logic("/foo/bar/file.txt"), Some("/home/user/bar/file.txt".into()), ); assert_eq!( path_map.physic_from_logic("/foo/bar/baz/file.txt"), Some("/home/user/bar/test/file.txt".into()), ); assert_eq!( path_map.logic_from_physic("/home/user/bar/file.txt"), Some("/foo/bar/file.txt".into()), ); assert_eq!( path_map.logic_from_physic("/home/user/bar/test/file.txt"), Some("/foo/bar/baz/file.txt".into()), ); } #[test] fn test_path_map_crossed_subdirs() { let path_map = PathMap::from_vec(vec![ PathPair::new("/foo/bar/baz".into(), "/home/user/bar".into()), PathPair::new("/foo/bar".into(), "/home/user/bar/test".into()), ]).unwrap(); assert_eq!( path_map.physic_from_logic("/foo/bar/baz/file.txt"), Some("/home/user/bar/file.txt".into()), ); assert_eq!( path_map.physic_from_logic("/foo/bar/file.txt"), Some("/home/user/bar/test/file.txt".into()), ); assert_eq!( path_map.logic_from_physic("/home/user/bar/test/file.txt"), Some("/foo/bar/file.txt".into()), ); assert_eq!( path_map.logic_from_physic("/home/user/bar/file.txt"), Some("/foo/bar/baz/file.txt".into()), ); } #[test] fn test_path_fail_on_duplicates() { PathMap::from_vec(vec![ PathPair::new("/foo/bar".into(), "/home/user/bar".into()), PathPair::new("/foo/bar".into(), "/home/user/bar/test".into()), ]).expect_err("should fail on duplicate logic path"); PathMap::from_vec(vec![ PathPair::new("/foo/bar/baz".into(), "/home/user/bar".into()), PathPair::new("/foo/bar".into(), "/home/user/bar".into()), ]).expect_err("should fail on duplicate physic path"); } }