use crate::fs::AbsPath;
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
pub struct DepManager {
out_edge_counts: HashMap<AbsPath, usize>, in_edges: HashMap<AbsPath, HashSet<AbsPath>>, finished: HashSet<AbsPath>, }
impl DepManager {
pub fn new() -> Self {
Self {
out_edge_counts: HashMap::new(),
in_edges: HashMap::new(),
finished: HashSet::new(),
}
}
pub fn add_dependency(&mut self, depender: &AbsPath, dependencies: &[AbsPath]) -> bool {
if dependencies.is_empty() {
return false;
}
let mut added = false;
let dependency_count = self.out_edge_counts.entry(depender.clone()).or_insert(0);
for dependency in dependencies {
if self.finished.contains(dependency) {
continue;
}
let dependers = self.in_edges.entry(dependency.clone()).or_default();
if dependers.insert(depender.clone()) {
*dependency_count += 1;
}
added = true;
}
added
}
pub fn notify_finish(&mut self, finished: &AbsPath) -> HashSet<AbsPath> {
self.finished.insert(finished.clone());
let mut output = HashSet::new();
let in_edges = match self.in_edges.remove(finished) {
Some(in_edges) => in_edges,
None => return output,
};
for depender in in_edges {
let count = self.out_edge_counts.get_mut(&depender).unwrap();
if *count <= 1 {
self.out_edge_counts.remove(&depender);
output.insert(depender);
} else {
*count -= 1;
}
}
output
}
pub fn take_remaining(self) -> HashMap<AbsPath, HashSet<AbsPath>> {
let mut out_edges = HashMap::new();
for (k, v) in self.in_edges {
for depender in v {
out_edges
.entry(depender)
.or_insert_with(HashSet::new)
.insert(k.clone());
}
}
out_edges
}
}
pub fn print_dep_map(map: &HashMap<AbsPath, HashSet<AbsPath>>) -> String {
let mut out = String::new();
for (k, v) in map {
let _ = writeln!(out, "{k}");
for (i, dep) in v.iter().enumerate() {
let _ = if i == v.len() - 1 {
writeln!(out, "â•°â•´{dep}")
} else {
writeln!(out, "├╴{dep}")
};
}
out.push('\n')
}
out
}
#[cfg(test)]
mod ut {
use std::path::PathBuf;
use super::*;
#[test]
fn test_empty() {
let mut dm = DepManager::new();
let finished = AbsPath::new(PathBuf::from("/a"));
let free = dm.notify_finish(&finished);
assert_eq!(free, HashSet::new());
}
#[test]
fn test_insert_empty() {
let mut dm = DepManager::new();
let finished = AbsPath::new(PathBuf::from("/a"));
assert!(!dm.add_dependency(&finished, &[]));
let free = dm.notify_finish(&finished);
assert_eq!(free, HashSet::new());
}
#[test]
fn test_one() {
let mut dm = DepManager::new();
let a = AbsPath::new(PathBuf::from("/a"));
let b = AbsPath::new(PathBuf::from("/b"));
assert!(dm.add_dependency(&a, &[b.clone()]));
let free = dm.notify_finish(&b);
assert_eq!(free, [a].into_iter().collect());
}
#[test]
fn test_one_no_depender() {
let mut dm = DepManager::new();
let a = AbsPath::new(PathBuf::from("/a"));
let b = AbsPath::new(PathBuf::from("/b"));
let c = AbsPath::new(PathBuf::from("/c"));
assert!(dm.add_dependency(&a, &[b.clone()]));
let free = dm.notify_finish(&c);
assert_eq!(free, HashSet::new());
let a_deps = [b].into_iter().collect::<HashSet<_>>();
assert_eq!(
dm.take_remaining(),
[(a, a_deps)].into_iter().collect::<HashMap<_, _>>()
);
}
#[test]
fn test_one_depends_on_two() {
let mut dm = DepManager::new();
let a = AbsPath::new(PathBuf::from("/a"));
let b = AbsPath::new(PathBuf::from("/b"));
let c = AbsPath::new(PathBuf::from("/c"));
assert!(dm.add_dependency(&a, &[b.clone(), c.clone()]));
let free = dm.notify_finish(&b);
assert_eq!(free, HashSet::new());
let free = dm.notify_finish(&c);
assert_eq!(free, [a].into_iter().collect());
}
#[test]
fn test_diamond() {
let mut dm = DepManager::new();
let a = AbsPath::new(PathBuf::from("/a"));
let b = AbsPath::new(PathBuf::from("/b"));
let c = AbsPath::new(PathBuf::from("/c"));
let d = AbsPath::new(PathBuf::from("/d"));
assert!(dm.add_dependency(&a, &[b.clone(), c.clone()]));
assert!(dm.add_dependency(&b, &[d.clone()]));
assert!(dm.add_dependency(&c, &[d.clone()]));
let free = dm.notify_finish(&d);
assert_eq!(free, [b.clone(), c.clone()].into_iter().collect());
let free = dm.notify_finish(&c);
assert_eq!(free, HashSet::new());
let free = dm.notify_finish(&b);
assert_eq!(free, [a].into_iter().collect());
}
#[test]
fn test_circle() {
let mut dm = DepManager::new();
let a = AbsPath::new(PathBuf::from("/a"));
let b = AbsPath::new(PathBuf::from("/b"));
let c = AbsPath::new(PathBuf::from("/c"));
assert!(dm.add_dependency(&a, &[b.clone(), c.clone()]));
assert!(dm.add_dependency(&b, &[a.clone()]));
let free = dm.notify_finish(&c);
assert_eq!(free, HashSet::new());
let a_deps = [b.clone()].into_iter().collect::<HashSet<_>>();
let b_deps = [a.clone()].into_iter().collect::<HashSet<_>>();
assert_eq!(
dm.take_remaining(),
[(a, a_deps), (b, b_deps)]
.into_iter()
.collect::<HashMap<_, _>>()
);
}
#[test]
fn test_do_not_add_done() {
let mut dm = DepManager::new();
let a = AbsPath::new(PathBuf::from("/a"));
let b = AbsPath::new(PathBuf::from("/b"));
let free = dm.notify_finish(&b);
assert_eq!(free, HashSet::new());
assert!(!dm.add_dependency(&a, &[b.clone()]));
assert!(!dm.add_dependency(&a, &[b.clone(), b.clone()]));
let free = dm.notify_finish(&b);
assert_eq!(free, HashSet::new());
assert_eq!(dm.take_remaining(), HashMap::new());
}
}