1use std::collections::HashMap;
2use std::path::PathBuf;
3
4use itertools::Itertools;
5
6use crate::version::{Version, Versioned};
7
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct Package<T: Versioned> {
10 pub path: PathBuf,
11 pub name: String,
12 pub version: Version<T>,
13 pub dependencies: Vec<(String, String)>,
14}
15
16pub trait AsPackageGraph<T: Versioned> {
17 fn as_package_graph(&self) -> PackageGraph<'_, T>;
18}
19
20#[derive(Debug, PartialEq)]
21pub struct PackageGraph<'a, T: Versioned> {
22 edges: Vec<(&'a str, &'a Package<T>)>,
23 nodes: Vec<&'a Package<T>>,
24}
25
26impl<'a, T> PackageGraph<'a, T>
27where
28 T: Versioned,
29{
30 pub fn child_changes(&self, name: &'a str) -> Vec<&'a Package<T>> {
31 self
32 .edges
33 .iter()
34 .filter(|(p_name, _)| &name == p_name)
35 .map(|(_, package)| *package)
36 .collect()
37 }
38
39 fn stagger_scores(&self, scores: &mut HashMap<&'a str, isize>) {
40 for (edge, target) in &self.edges {
41 if let Some(value) = scores.get(&*target.name).copied() {
42 if let Some(score) = scores.get_mut(&*edge) {
43 *score += value;
44 }
45 }
46 }
47 }
48
49 pub fn update_order(&self) -> Vec<&'a Package<T>> {
50 let name_map: HashMap<&str, &'a Package<T>> = self
51 .nodes
52 .iter()
53 .map(|package| (package.name.as_str(), *package))
54 .collect();
55
56 let base = self
57 .nodes
58 .iter()
59 .map(|package| (package.name.as_str(), 0))
60 .collect();
61
62 let mut scores: HashMap<&'a str, isize> =
63 self.edges.iter().fold(base, |mut acc, (parent, package)| {
64 let value = acc
65 .get(&*package.name)
66 .map(|val| val + 1)
67 .unwrap_or_else(|| 0);
68
69 if let Some(score) = acc.get_mut(parent) {
70 *score += value
71 } else {
72 acc.insert(parent, value);
73 }
74
75 acc
76 });
77
78 for _ in 0..self.nodes.len() {
79 self.stagger_scores(&mut scores);
80 }
81
82 scores
83 .into_iter()
84 .sorted_by_key(|(_, score)| -*score)
85 .filter(|(name, _)| name_map.contains_key(name))
86 .map(|(name, _)| name_map[name])
87 .collect()
88 }
89}
90
91impl<T> AsPackageGraph<T> for Vec<Package<T>>
92where
93 T: Versioned,
94{
95 fn as_package_graph(&self) -> PackageGraph<'_, T> {
96 let nodes: Vec<&Package<T>> = self.iter().collect();
97 let edges: Vec<(&str, &Package<T>)> = self.iter().fold(vec![], |mut acc, package| {
98 acc.extend(
99 package
100 .dependencies
101 .iter()
102 .map(|(dep, _)| (dep.as_str(), package)),
103 );
104 acc
105 });
106
107 PackageGraph { edges, nodes }
108 }
109}
110
111#[cfg(test)]
112mod tests {
113
114 use super::*;
115 use crate::semantic::Semantic;
116
117 #[test]
118 fn as_package_graph() {
119 let packages: Vec<Package<Semantic>> = vec![
120 Package {
121 path: "".into(),
122 name: "foo".to_owned(),
123 version: "1.0.0".into(),
124 dependencies: vec![],
125 },
126 Package {
127 path: "".into(),
128 name: "bar".to_owned(),
129 version: "1.0.0".into(),
130 dependencies: vec![("foo".to_owned(), "1".to_owned())],
131 },
132 Package {
133 path: "".into(),
134 name: "baz".to_owned(),
135 version: "1.0.0".into(),
136 dependencies: vec![("foo".to_owned(), "1".to_owned())],
137 },
138 ];
139
140 let graph = packages.as_package_graph();
141
142 assert_eq!(
143 graph,
144 PackageGraph {
145 nodes: vec![&packages[0], &packages[1], &packages[2]],
146 edges: vec![("foo", &packages[1]), ("foo", &packages[2])]
147 }
148 );
149 }
150
151 #[test]
152 fn update_order() {
153 let packages: Vec<Package<Semantic>> = vec![
154 Package {
155 path: "".into(),
156 name: "foo".to_owned(),
157 version: "1.0.0".into(),
158 dependencies: vec![],
159 },
160 Package {
161 path: "".into(),
162 name: "bar".to_owned(),
163 version: "1.0.0".into(),
164 dependencies: vec![("foo".to_owned(), "1".to_owned())],
165 },
166 Package {
167 path: "".into(),
168 name: "baz".to_owned(),
169 version: "1.0.0".into(),
170 dependencies: vec![("foo".to_owned(), "1".to_owned())],
171 },
172 ];
173
174 let graph = packages.as_package_graph();
175
176 let update_order = graph.update_order();
177
178 assert_eq!(update_order[0], &packages[0]);
179 }
180
181 #[test]
182 fn update_order_deep() {
183 let packages: Vec<Package<Semantic>> = vec![
184 Package {
185 path: "".into(),
186 name: "pre_foo".to_owned(),
187 version: "1.0.0".into(),
188 dependencies: vec![],
189 },
190 Package {
191 path: "".into(),
192 name: "foo".to_owned(),
193 version: "1.0.0".into(),
194 dependencies: vec![("pre_foo".to_owned(), "1".to_owned())],
195 },
196 Package {
197 path: "".into(),
198 name: "bar".to_owned(),
199 version: "1.0.0".into(),
200 dependencies: vec![("foo".to_owned(), "1".to_owned())],
201 },
202 Package {
203 path: "".into(),
204 name: "baz".to_owned(),
205 version: "1.0.0".into(),
206 dependencies: vec![("foo".to_owned(), "1".to_owned())],
207 },
208 ];
209
210 let graph = packages.as_package_graph();
211
212 let update_order = graph.update_order();
213
214 let packages_ref: Vec<&Package<Semantic>> = packages.iter().collect();
215 assert_eq!(update_order[..2], packages_ref[..2]);
216 }
217
218 #[test]
219 fn child_changes() {
220 let packages = vec![
221 Package {
222 path: "".into(),
223 name: "foo".to_owned(),
224 version: "1.0.0".into(),
225 dependencies: vec![],
226 },
227 Package {
228 path: "".into(),
229 name: "bar".to_owned(),
230 version: "1.0.0".into(),
231 dependencies: vec![("foo".to_owned(), "1".to_owned())],
232 },
233 Package {
234 path: "".into(),
235 name: "baz".to_owned(),
236 version: "1.0.0".into(),
237 dependencies: vec![("foo".to_owned(), "1".to_owned())],
238 },
239 ];
240
241 let graph = packages.as_package_graph();
242
243 let child_changes = graph.child_changes("foo");
244
245 assert_eq!(
246 child_changes,
247 packages[1..].iter().collect::<Vec<&Package<Semantic>>>()
248 );
249 }
250}