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 order in which the paths were provided.
 74    pub fn order(&self) -> &[usize] {
 75        self.order.as_ref()
 76    }
 77
 78    /// Get the paths in the original order.
 79    pub fn ordered_paths(&self) -> impl Iterator<Item = &PathBuf> {
 80        self.order
 81            .iter()
 82            .zip(self.paths.iter())
 83            .sorted_by_key(|(i, _)| **i)
 84            .map(|(_, path)| path)
 85    }
 86
 87    pub fn is_lexicographically_ordered(&self) -> bool {
 88        self.order.iter().enumerate().all(|(i, &j)| i == j)
 89    }
 90
 91    pub fn deserialize(serialized: &SerializedPathList) -> Self {
 92        let mut paths: Vec<PathBuf> = if serialized.paths.is_empty() {
 93            Vec::new()
 94        } else {
 95            serialized.paths.split('\n').map(PathBuf::from).collect()
 96        };
 97
 98        let mut order: Vec<usize> = serialized
 99            .order
100            .split(',')
101            .filter_map(|s| s.parse().ok())
102            .collect();
103
104        if !paths.is_sorted() || order.len() != paths.len() {
105            order = (0..paths.len()).collect();
106            paths.sort();
107        }
108
109        Self {
110            paths: paths.into(),
111            order: order.into(),
112        }
113    }
114
115    pub fn serialize(&self) -> SerializedPathList {
116        use std::fmt::Write as _;
117
118        let mut paths = String::new();
119        for path in self.paths.iter() {
120            if !paths.is_empty() {
121                paths.push('\n');
122            }
123            paths.push_str(&path.to_string_lossy());
124        }
125
126        let mut order = String::new();
127        for ix in self.order.iter() {
128            if !order.is_empty() {
129                order.push(',');
130            }
131            write!(&mut order, "{}", *ix).unwrap();
132        }
133        SerializedPathList { paths, order }
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_path_list() {
143        let list1 = PathList::new(&["a/d", "a/c"]);
144        let list2 = PathList::new(&["a/c", "a/d"]);
145
146        assert_eq!(list1.paths(), list2.paths(), "paths differ");
147        assert_eq!(list1.order(), &[1, 0], "list1 order incorrect");
148        assert_eq!(list2.order(), &[0, 1], "list2 order incorrect");
149
150        // Same paths in different order are equal (order is display-only).
151        assert_eq!(
152            list1, list2,
153            "same paths with different order should be equal"
154        );
155
156        let list1_deserialized = PathList::deserialize(&list1.serialize());
157        assert_eq!(list1_deserialized, list1, "list1 deserialization failed");
158
159        let list2_deserialized = PathList::deserialize(&list2.serialize());
160        assert_eq!(list2_deserialized, list2, "list2 deserialization failed");
161
162        assert_eq!(
163            list1.ordered_paths().collect_array().unwrap(),
164            [&PathBuf::from("a/d"), &PathBuf::from("a/c")],
165            "list1 ordered paths incorrect"
166        );
167        assert_eq!(
168            list2.ordered_paths().collect_array().unwrap(),
169            [&PathBuf::from("a/c"), &PathBuf::from("a/d")],
170            "list2 ordered paths incorrect"
171        );
172    }
173
174    #[test]
175    fn test_path_list_ordering() {
176        let list = PathList::new(&["b", "a", "c"]);
177        assert_eq!(
178            list.paths(),
179            &[PathBuf::from("a"), PathBuf::from("b"), PathBuf::from("c")]
180        );
181        assert_eq!(list.order(), &[1, 0, 2]);
182        assert!(!list.is_lexicographically_ordered());
183
184        let serialized = list.serialize();
185        let deserialized = PathList::deserialize(&serialized);
186        assert_eq!(deserialized, list);
187
188        assert_eq!(
189            deserialized.ordered_paths().collect_array().unwrap(),
190            [
191                &PathBuf::from("b"),
192                &PathBuf::from("a"),
193                &PathBuf::from("c")
194            ]
195        );
196
197        let list = PathList::new(&["b", "c", "a"]);
198        assert_eq!(
199            list.paths(),
200            &[PathBuf::from("a"), PathBuf::from("b"), PathBuf::from("c")]
201        );
202        assert_eq!(list.order(), &[2, 0, 1]);
203        assert!(!list.is_lexicographically_ordered());
204
205        let serialized = list.serialize();
206        let deserialized = PathList::deserialize(&serialized);
207        assert_eq!(deserialized, list);
208
209        assert_eq!(
210            deserialized.ordered_paths().collect_array().unwrap(),
211            [
212                &PathBuf::from("b"),
213                &PathBuf::from("c"),
214                &PathBuf::from("a"),
215            ]
216        );
217    }
218}