use crate::facts::Ecosystem;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DepNode {
pub name: String,
pub version: String,
pub ecosystem: Ecosystem,
pub direct: bool,
}
#[derive(Debug, Clone, Default)]
pub struct DepGraph {
nodes: Vec<DepNode>,
edges: Vec<Vec<usize>>,
}
impl DepGraph {
pub fn new() -> Self {
DepGraph::default()
}
pub fn add_node(&mut self, node: DepNode) -> usize {
let idx = self.nodes.len();
self.nodes.push(node);
self.edges.push(Vec::new());
idx
}
pub fn add_edge(&mut self, from: usize, to: usize) {
if from < self.edges.len() && to < self.nodes.len() && from != to {
let e = &mut self.edges[from];
if !e.contains(&to) {
e.push(to);
}
}
}
pub fn nodes(&self) -> &[DepNode] {
&self.nodes
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn direct_count(&self) -> usize {
self.nodes.iter().filter(|n| n.direct).count()
}
fn reverse(&self) -> Vec<Vec<usize>> {
let mut rev = vec![Vec::new(); self.nodes.len()];
for (from, tos) in self.edges.iter().enumerate() {
for &to in tos {
rev[to].push(from);
}
}
rev
}
pub fn blast_radii(&self) -> Vec<usize> {
let rev = self.reverse();
let n = self.nodes.len();
let mut out = vec![0usize; n];
for start in 0..n {
let mut seen = vec![false; n];
seen[start] = true;
let mut stack = vec![start];
let mut count = 0;
while let Some(u) = stack.pop() {
for &p in &rev[u] {
if !seen[p] {
seen[p] = true;
count += 1;
stack.push(p);
}
}
}
out[start] = count;
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
fn node(name: &str, direct: bool) -> DepNode {
DepNode {
name: name.into(),
version: "1.0.0".into(),
ecosystem: Ecosystem::Cargo,
direct,
}
}
#[test]
fn blast_radius_counts_transitive_dependents() {
let mut g = DepGraph::new();
let app = g.add_node(node("app", true));
let lib = g.add_node(node("lib", false));
let leaf = g.add_node(node("leaf", false));
g.add_edge(app, lib);
g.add_edge(lib, leaf);
g.add_edge(app, leaf);
let radii = g.blast_radii();
assert_eq!(radii[app], 0, "nothing depends on the app");
assert_eq!(radii[lib], 1, "only app depends on lib");
assert_eq!(radii[leaf], 2, "both app and lib depend on leaf");
}
#[test]
fn duplicate_and_self_edges_are_ignored() {
let mut g = DepGraph::new();
let a = g.add_node(node("a", true));
let b = g.add_node(node("b", false));
g.add_edge(a, b);
g.add_edge(a, b); g.add_edge(a, a); assert_eq!(g.blast_radii()[b], 1);
}
}