mol_core/
package.rs

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}