path_list.rs

  1use std::{
  2    hash::{Hash, Hasher},
  3    path::{Path, PathBuf},
  4    sync::Arc,
  5};
  6
  7use crate::paths::SanitizedPath;
  8use itertools::Itertools;
  9use serde::{Deserialize, Serialize};
 10
 11/// A list of absolute paths, with an associated display order.
 12///
 13/// Two `PathList` values are considered equal if they contain the same paths,
 14/// regardless of the order in which those paths were originally provided.
 15///
 16/// The paths can be retrieved in the original order using `ordered_paths()`.
 17#[derive(Default, Debug, Clone)]
 18pub struct PathList {
 19    /// The paths, in lexicographic order.
 20    paths: Arc<[PathBuf]>,
 21    /// The order in which the paths were provided.
 22    ///
 23    /// See `ordered_paths()` for a way to get the paths in the original order.
 24    order: Arc<[usize]>,
 25}
 26
 27impl PartialEq for PathList {
 28    fn eq(&self, other: &Self) -> bool {
 29        self.paths == other.paths
 30    }
 31}
 32
 33impl Eq for PathList {}
 34
 35impl Hash for PathList {
 36    fn hash<H: Hasher>(&self, state: &mut H) {
 37        self.paths.hash(state);
 38    }
 39}
 40
 41#[derive(Debug, Serialize, Deserialize)]
 42pub struct SerializedPathList {
 43    pub paths: String,
 44    pub order: String,
 45}
 46
 47impl PathList {
 48    pub fn new<P: AsRef<Path>>(paths: &[P]) -> Self {
 49        let mut indexed_paths: Vec<(usize, PathBuf)> = paths
 50            .iter()
 51            .enumerate()
 52            .map(|(ix, path)| (ix, SanitizedPath::new(path).into()))
 53            .collect();
 54        indexed_paths.sort_by(|(_, a), (_, b)| a.cmp(b));
 55        let order = indexed_paths.iter().map(|e| e.0).collect::<Vec<_>>().into();
 56        let paths = indexed_paths
 57            .into_iter()
 58            .map(|e| e.1)
 59            .collect::<Vec<_>>()
 60            .into();
 61        Self { order, paths }
 62    }
 63
 64    pub fn is_empty(&self) -> bool {
 65        self.paths.is_empty()
 66    }
 67
 68    /// Get the paths in lexicographic order.
 69    pub fn paths(&self) -> &[PathBuf] {
 70        self.paths.as_ref()
 71    }
 72
 73    /// Get the paths in the lexicographic order.
 74    pub fn paths_owned(&self) -> Arc<[PathBuf]> {
 75        self.paths.clone()
 76    }
 77
 78    /// Get the order in which the paths were provided.
 79    pub fn order(&self) -> &[usize] {
 80        self.order.as_ref()
 81    }
 82
 83    /// Get the paths in the original order.
 84    pub fn ordered_paths(&self) -> impl Iterator<Item = &PathBuf> {
 85        self.order
 86            .iter()
 87            .zip(self.paths.iter())
 88            .sorted_by_key(|(i, _)| **i)
 89            .map(|(_, path)| path)
 90    }
 91
 92    pub fn is_lexicographically_ordered(&self) -> bool {
 93        self.order.iter().enumerate().all(|(i, &j)| i == j)
 94    }
 95
 96    pub fn deserialize(serialized: &SerializedPathList) -> Self {
 97        let mut paths: Vec<PathBuf> = if serialized.paths.is_empty() {
 98            Vec::new()
 99        } else {
100            serialized.paths.split('\n').map(PathBuf::from).collect()
101        };
102
103        let mut order: Vec<usize> = serialized
104            .order
105            .split(',')
106            .filter_map(|s| s.parse().ok())
107            .collect();
108
109        if !paths.is_sorted() || order.len() != paths.len() {
110            order = (0..paths.len()).collect();
111            paths.sort();
112        }
113
114        Self {
115            paths: paths.into(),
116            order: order.into(),
117        }
118    }
119
120    pub fn serialize(&self) -> SerializedPathList {
121        use std::fmt::Write as _;
122
123        let mut paths = String::new();
124        for path in self.paths.iter() {
125            if !paths.is_empty() {
126                paths.push('\n');
127            }
128            paths.push_str(&path.to_string_lossy());
129        }
130
131        let mut order = String::new();
132        for ix in self.order.iter() {
133            if !order.is_empty() {
134                order.push(',');
135            }
136            write!(&mut order, "{}", *ix).unwrap();
137        }
138        SerializedPathList { paths, order }
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn test_path_list() {
148        let list1 = PathList::new(&["a/d", "a/c"]);
149        let list2 = PathList::new(&["a/c", "a/d"]);
150
151        assert_eq!(list1.paths(), list2.paths(), "paths differ");
152        assert_eq!(list1.order(), &[1, 0], "list1 order incorrect");
153        assert_eq!(list2.order(), &[0, 1], "list2 order incorrect");
154
155        // Same paths in different order are equal (order is display-only).
156        assert_eq!(
157            list1, list2,
158            "same paths with different order should be equal"
159        );
160
161        let list1_deserialized = PathList::deserialize(&list1.serialize());
162        assert_eq!(list1_deserialized, list1, "list1 deserialization failed");
163
164        let list2_deserialized = PathList::deserialize(&list2.serialize());
165        assert_eq!(list2_deserialized, list2, "list2 deserialization failed");
166
167        assert_eq!(
168            list1.ordered_paths().collect_array().unwrap(),
169            [&PathBuf::from("a/d"), &PathBuf::from("a/c")],
170            "list1 ordered paths incorrect"
171        );
172        assert_eq!(
173            list2.ordered_paths().collect_array().unwrap(),
174            [&PathBuf::from("a/c"), &PathBuf::from("a/d")],
175            "list2 ordered paths incorrect"
176        );
177    }
178
179    #[test]
180    fn test_path_list_ordering() {
181        let list = PathList::new(&["b", "a", "c"]);
182        assert_eq!(
183            list.paths(),
184            &[PathBuf::from("a"), PathBuf::from("b"), PathBuf::from("c")]
185        );
186        assert_eq!(list.order(), &[1, 0, 2]);
187        assert!(!list.is_lexicographically_ordered());
188
189        let serialized = list.serialize();
190        let deserialized = PathList::deserialize(&serialized);
191        assert_eq!(deserialized, list);
192
193        assert_eq!(
194            deserialized.ordered_paths().collect_array().unwrap(),
195            [
196                &PathBuf::from("b"),
197                &PathBuf::from("a"),
198                &PathBuf::from("c")
199            ]
200        );
201
202        let list = PathList::new(&["b", "c", "a"]);
203        assert_eq!(
204            list.paths(),
205            &[PathBuf::from("a"), PathBuf::from("b"), PathBuf::from("c")]
206        );
207        assert_eq!(list.order(), &[2, 0, 1]);
208        assert!(!list.is_lexicographically_ordered());
209
210        let serialized = list.serialize();
211        let deserialized = PathList::deserialize(&serialized);
212        assert_eq!(deserialized, list);
213
214        assert_eq!(
215            deserialized.ordered_paths().collect_array().unwrap(),
216            [
217                &PathBuf::from("b"),
218                &PathBuf::from("c"),
219                &PathBuf::from("a"),
220            ]
221        );
222    }
223}