1use std::path::{Path, PathBuf};
8
9#[derive(Debug)]
12pub struct CommonDir(
13 PathBuf, Vec<(
15 PathBuf, PathBuf, )>,
18);
19
20impl CommonDir {
21 pub fn try_new<P: AsRef<Path>, I: IntoIterator<Item = P> + Clone>(
23 paths: I,
24 ) -> Result<Self, Error> {
25 naive_find_common_dir(paths)
26 }
27
28 pub fn common_root(&self) -> &Path {
30 self.0.as_path()
31 }
32
33 pub fn input_paths(&self) -> Vec<&Path> {
35 self.1.iter().map(|(lp, _)| lp.as_path()).collect()
36 }
37
38 pub fn path_branches(&self) -> Vec<&Path> {
40 self.1.iter().map(|(_, rp)| rp.as_path()).collect()
41 }
42
43 pub fn path_combinations(&self) -> Vec<(&Path, &Path)> {
45 self.1
46 .iter()
47 .map(|(lp, rp)| (lp.as_path(), rp.as_path()))
48 .collect()
49 }
50}
51
52fn naive_find_common_dir<P: AsRef<Path>, I: IntoIterator<Item = P> + Clone>(
53 paths: I,
54) -> Result<CommonDir, Error> {
55 let mut iter = paths.clone().into_iter();
56 let first_path = iter.next().ok_or(Error::Empty)?;
57 let first_path = first_path.as_ref();
58
59 let path_trunk = first_path
60 .parent()
61 .ok_or_else(|| Error::NoRootDirectory(first_path.to_path_buf()))?;
62 let ancestors = path_trunk.ancestors();
63
64 let set_of_paths = paths
65 .into_iter()
66 .map(|p| p.as_ref().to_path_buf())
67 .collect::<Vec<PathBuf>>();
68
69 for ancestor in ancestors {
70 if set_of_paths.iter().all(|path| path.starts_with(ancestor)) {
72 let vec: Vec<(PathBuf, PathBuf)> = set_of_paths
73 .iter()
74 .map(|path| (path.to_path_buf(), unroot(ancestor, path)))
75 .collect();
76
77 return Ok(CommonDir(ancestor.to_path_buf(), vec));
78 }
79 }
80
81 let set = set_of_paths
82 .iter()
83 .map(|p| {
84 p.file_name()
85 .map(|str| (p.to_path_buf(), PathBuf::from(str.to_os_string())))
86 .ok_or_else(|| Error::InvalidPath(p.to_path_buf()))
87 })
88 .collect::<Result<Vec<_>, _>>()?;
89
90 Ok(CommonDir(path_trunk.to_path_buf(), set))
91}
92
93#[derive(Debug, Eq, PartialEq, thiserror::Error)]
94pub enum Error {
95 #[error("Empty input: got no path(s)")]
96 Empty,
97
98 #[error("Unable to mirror input directory to output: found an invalid file path: '{0}'")]
99 InvalidPath(PathBuf),
100
101 #[error("No root directory for path: '{0}'")]
102 NoRootDirectory(PathBuf),
103}
104
105fn unroot(root: &Path, path: &Path) -> PathBuf {
107 let root_len = root.components().count();
108
109 path.components()
110 .skip(root_len)
111 .fold(PathBuf::new(), |mut parent, child| {
112 parent.push(child.as_os_str());
113
114 parent
115 })
116}
117
118#[cfg(test)]
119mod test {
120 use super::*;
121
122 #[test]
123 fn unroot_test_file_only() {
124 let root = Path::new("/my/common");
125 let full = Path::new("/my/common/a.png");
126
127 let expected = PathBuf::from("a.png".to_string());
128 assert_eq!(unroot(root, full), expected);
129 }
130
131 #[test]
132 fn unroot_with_similar_dir_test() {
133 let root = Path::new("/my");
134 let full = Path::new("/my/common/a.png");
135
136 let expected = PathBuf::from("common/a.png".to_string());
137 assert_eq!(unroot(root, full), expected);
138 }
139
140 #[test]
141 fn uncommon_dir_test() {
142 let common = CommonDir::try_new(vec![
143 "/my/common/path/a.png",
144 "/my/common/path/b.png",
145 "/my/uncommon/path/c.png",
146 ])
147 .unwrap();
148
149 assert_eq!(common.common_root(), Path::new("/my"));
150
151 let stem = common.path_branches();
152
153 assert_eq!(stem[0], Path::new("common/path/a.png"));
154 assert_eq!(stem[1], Path::new("common/path/b.png"));
155 assert_eq!(stem[2], Path::new("uncommon/path/c.png"));
156 }
157
158 #[test]
159 fn common_dir_test() {
160 let common = CommonDir::try_new(vec![
161 "/my/common/path/a.png",
162 "/my/common/path/b.png",
163 "/my/common/path/c.png",
164 ])
165 .unwrap();
166
167 assert_eq!(common.common_root(), Path::new("/my/common/path/"));
168
169 let stem = common.path_branches();
170
171 assert_eq!(stem[0], Path::new("a.png"));
172 assert_eq!(stem[1], Path::new("b.png"));
173 assert_eq!(stem[2], Path::new("c.png"));
174 }
175
176 #[test]
177 fn no_path_before_file() {
178 let common = CommonDir::try_new(vec!["a.png", "b.png", "c.png"]).unwrap();
179
180 assert_eq!(common.common_root(), Path::new(""));
181
182 let stem = common.path_branches();
183
184 assert_eq!(stem[0], Path::new("a.png"));
185 assert_eq!(stem[1], Path::new("b.png"));
186 assert_eq!(stem[2], Path::new("c.png"));
187 }
188
189 #[test]
190 fn empty_common_dir() {
191 let common = CommonDir::try_new(Vec::<PathBuf>::new());
192 assert!(common.is_err());
193 }
194}