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}