//! Graph manipulation code //! //! I had hoped to use petgraph, but it is optimized for efficiency over //! usability, and I got lost in a maze of indices. use anyhow::{anyhow, Result}; use std::collections::{HashMap, HashSet}; use std::hash::Hash; /// Given a hashmap representing a binary relationship, compute its transitive closure. fn transitive_closure( inp: &HashMap>, ) -> HashMap> { let mut out = inp.clone(); let max_iters = inp.len(); for _ in 0..max_iters { let mut new_out = out.clone(); for (k, vs) in out.iter() { let new_vs = new_out .get_mut(k) .expect("That key should have been there when I cloned it..."); for v in vs.iter() { if let Some(vv) = out.get(v) { new_vs.extend(vv.iter().cloned()); } } } if out == new_out { break; } else { out = new_out; } } out } /// Given a hashmap representing a binary relationship, invert it. fn invert( inp: &HashMap>, ) -> HashMap> { let mut out: HashMap> = HashMap::new(); for (k, vs) in inp.iter() { for v in vs { out.entry(v.clone()) .or_insert_with(Default::default) .insert(k.clone()); } } out } /// A representation of which features depend on what. #[derive(Clone, Debug, Default)] pub struct FeatureGraph { /// List of all features. all_features: HashSet, /// Adjacency map: for each feature F, `depends_on[F]` includes G if F /// directly depends on G. depends_on: HashMap>, /// Inverse adjacency map. depended_on_by: HashMap>, /// Transitive closure of the adjacency map. reachable_from: HashMap>, } impl FeatureGraph { /// Convert a toml `[features]` section into a [`FeatureGraph`]. pub fn from_features_table(features: &toml_edit::Table) -> Result { let mut depends_on = HashMap::new(); for (k, vs) in features.iter() { let mut d = HashSet::new(); for v in vs .as_array() .ok_or_else(|| anyhow!("features.{} was not an array", k))? { let v = v .as_str() .ok_or_else(|| anyhow!("features.{} contained a non-string", k))?; d.insert(v.to_string()); } depends_on.insert(k.to_string(), d); } let all_features = depends_on.keys().cloned().collect(); let reachable_from = transitive_closure(&depends_on); let depended_on_by = invert(&depends_on); Ok(Self { all_features, depends_on, depended_on_by, reachable_from, }) } pub fn all_features(&self) -> impl Iterator + '_ { self.depends_on.keys().cloned() } pub fn contains_feature(&self, feature: &str) -> bool { self.all_features.contains(feature) } pub fn contains_edge(&self, from: &str, to: &str) -> bool { match self.depends_on.get(from) { Some(fs) => fs.contains(to), None => false, } } pub fn all_reachable_from(&self, feature: &str) -> impl Iterator + '_ { match self.reachable_from.get(feature) { Some(set) => itertools::Either::Left(set.iter().cloned()), None => itertools::Either::Right(std::iter::empty()), } } /// Return all the features that `feature` depends on. Return an empty iterator if /// it has no dependencies, or is not in this map. pub fn edges_from(&self, feature: &str) -> impl Iterator + '_ { match self.depends_on.get(feature) { Some(set) => itertools::Either::Left(set.iter().cloned()), None => itertools::Either::Right(std::iter::empty()), } } /// Return all the features that depend on `feature` directly. Return an empty iterator if /// it has no dependencies, or is not in this map. pub fn edges_to(&self, feature: &str) -> impl Iterator + '_ { match self.depended_on_by.get(feature) { Some(set) => itertools::Either::Left(set.iter().cloned()), None => itertools::Either::Right(std::iter::empty()), } } }