path_list.rs

  1use std::{
  2    path::{Path, PathBuf},
  3    sync::Arc,
  4};
  5
  6use util::paths::SanitizedPath;
  7
  8/// A list of absolute paths, in a specific order.
  9///
 10/// The paths are stored in lexicographic order, so that they can be compared to
 11/// other path lists without regard to the order of the paths.
 12#[derive(Default, PartialEq, Eq, Debug, Clone)]
 13pub struct PathList {
 14    paths: Arc<[PathBuf]>,
 15    order: Arc<[usize]>,
 16}
 17
 18#[derive(Debug)]
 19pub struct SerializedPathList {
 20    pub paths: String,
 21    pub order: String,
 22}
 23
 24impl PathList {
 25    pub fn new<P: AsRef<Path>>(paths: &[P]) -> Self {
 26        let mut indexed_paths: Vec<(usize, PathBuf)> = paths
 27            .iter()
 28            .enumerate()
 29            .map(|(ix, path)| (ix, SanitizedPath::new(path).into()))
 30            .collect();
 31        indexed_paths.sort_by(|(_, a), (_, b)| a.cmp(b));
 32        let order = indexed_paths.iter().map(|e| e.0).collect::<Vec<_>>().into();
 33        let paths = indexed_paths
 34            .into_iter()
 35            .map(|e| e.1)
 36            .collect::<Vec<_>>()
 37            .into();
 38        Self { order, paths }
 39    }
 40
 41    pub fn is_empty(&self) -> bool {
 42        self.paths.is_empty()
 43    }
 44
 45    pub fn paths(&self) -> &[PathBuf] {
 46        self.paths.as_ref()
 47    }
 48
 49    pub fn order(&self) -> &[usize] {
 50        self.order.as_ref()
 51    }
 52
 53    pub fn is_lexicographically_ordered(&self) -> bool {
 54        self.order.iter().enumerate().all(|(i, &j)| i == j)
 55    }
 56
 57    pub fn deserialize(serialized: &SerializedPathList) -> Self {
 58        let mut paths: Vec<PathBuf> = if serialized.paths.is_empty() {
 59            Vec::new()
 60        } else {
 61            serialized.paths.split('\n').map(PathBuf::from).collect()
 62        };
 63
 64        let mut order: Vec<usize> = serialized
 65            .order
 66            .split(',')
 67            .filter_map(|s| s.parse().ok())
 68            .collect();
 69
 70        if !paths.is_sorted() || order.len() != paths.len() {
 71            order = (0..paths.len()).collect();
 72            paths.sort();
 73        }
 74
 75        Self {
 76            paths: paths.into(),
 77            order: order.into(),
 78        }
 79    }
 80
 81    pub fn serialize(&self) -> SerializedPathList {
 82        use std::fmt::Write as _;
 83
 84        let mut paths = String::new();
 85        for path in self.paths.iter() {
 86            if !paths.is_empty() {
 87                paths.push('\n');
 88            }
 89            paths.push_str(&path.to_string_lossy());
 90        }
 91
 92        let mut order = String::new();
 93        for ix in self.order.iter() {
 94            if !order.is_empty() {
 95                order.push(',');
 96            }
 97            write!(&mut order, "{}", *ix).unwrap();
 98        }
 99        SerializedPathList { paths, order }
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_path_list() {
109        let list1 = PathList::new(&["a/d", "a/c"]);
110        let list2 = PathList::new(&["a/c", "a/d"]);
111
112        assert_eq!(list1.paths(), list2.paths());
113        assert_ne!(list1, list2);
114        assert_eq!(list1.order(), &[1, 0]);
115        assert_eq!(list2.order(), &[0, 1]);
116
117        let list1_deserialized = PathList::deserialize(&list1.serialize());
118        assert_eq!(list1_deserialized, list1);
119
120        let list2_deserialized = PathList::deserialize(&list2.serialize());
121        assert_eq!(list2_deserialized, list2);
122    }
123}