fixup_features/
graph.rs
1#![allow(renamed_and_removed_lints)] #![allow(unknown_lints)] #![warn(missing_docs)]
10#![warn(noop_method_call)]
11#![warn(unreachable_pub)]
12#![warn(clippy::all)]
13#![deny(clippy::await_holding_lock)]
14#![deny(clippy::cargo_common_metadata)]
15#![deny(clippy::cast_lossless)]
16#![deny(clippy::checked_conversions)]
17#![warn(clippy::cognitive_complexity)]
18#![deny(clippy::debug_assert_with_mut_call)]
19#![deny(clippy::exhaustive_enums)]
20#![deny(clippy::exhaustive_structs)]
21#![deny(clippy::expl_impl_clone_on_copy)]
22#![deny(clippy::fallible_impl_from)]
23#![deny(clippy::implicit_clone)]
24#![deny(clippy::large_stack_arrays)]
25#![warn(clippy::manual_ok_or)]
26#![deny(clippy::missing_docs_in_private_items)]
27#![warn(clippy::needless_borrow)]
28#![warn(clippy::needless_pass_by_value)]
29#![warn(clippy::option_option)]
30#![deny(clippy::print_stderr)]
31#![deny(clippy::print_stdout)]
32#![warn(clippy::rc_buffer)]
33#![deny(clippy::ref_option_ref)]
34#![warn(clippy::semicolon_if_nothing_returned)]
35#![warn(clippy::trait_duplication_in_bounds)]
36#![deny(clippy::unchecked_duration_subtraction)]
37#![deny(clippy::unnecessary_wraps)]
38#![warn(clippy::unseparated_literal_suffix)]
39#![deny(clippy::unwrap_used)]
40#![deny(clippy::mod_module_files)]
41#![allow(clippy::let_unit_value)] #![allow(clippy::uninlined_format_args)]
43#![allow(clippy::significant_drop_in_scrutinee)] #![allow(clippy::result_large_err)] #![allow(clippy::needless_raw_string_hashes)] #![allow(clippy::needless_lifetimes)] #![allow(clippy::bool_assert_comparison)]
51#![allow(clippy::clone_on_copy)]
52#![allow(clippy::dbg_macro)]
53#![allow(clippy::mixed_attributes_style)]
54#![allow(clippy::print_stderr)]
55#![allow(clippy::print_stdout)]
56#![allow(clippy::single_char_pattern)]
57#![allow(clippy::unwrap_used)]
58#![allow(clippy::unchecked_duration_subtraction)]
59#![allow(clippy::useless_vec)]
60#![allow(clippy::needless_pass_by_value)]
61#![allow(clippy::missing_docs_in_private_items)]
64#![allow(unreachable_pub)]
65
66use anyhow::{anyhow, Result};
67use std::collections::{HashMap, HashSet};
68use std::hash::Hash;
69
70fn transitive_closure<K: Hash + Eq + PartialEq + Clone>(
72 inp: &HashMap<K, HashSet<K>>,
73) -> HashMap<K, HashSet<K>> {
74 let mut out = inp.clone();
75 let max_iters = inp.len();
76 for _ in 0..max_iters {
77 let mut new_out = out.clone();
78 for (k, vs) in out.iter() {
79 let new_vs = new_out
80 .get_mut(k)
81 .expect("That key should have been there when I cloned it...");
82 for v in vs.iter() {
83 if let Some(vv) = out.get(v) {
84 new_vs.extend(vv.iter().cloned());
85 }
86 }
87 }
88 if out == new_out {
89 break;
90 } else {
91 out = new_out;
92 }
93 }
94 out
95}
96
97fn invert<K: Hash + Eq + PartialEq + Clone>(
99 inp: &HashMap<K, HashSet<K>>,
100) -> HashMap<K, HashSet<K>> {
101 let mut out: HashMap<K, HashSet<K>> = HashMap::new();
102 for (k, vs) in inp.iter() {
103 for v in vs {
104 out.entry(v.clone()).or_default().insert(k.clone());
105 }
106 }
107 out
108}
109
110#[derive(Clone, Debug, Default)]
112pub struct FeatureGraph {
113 all_features: HashSet<String>,
115 depends_on: HashMap<String, HashSet<String>>,
118 depended_on_by: HashMap<String, HashSet<String>>,
120 reachable_from: HashMap<String, HashSet<String>>,
122}
123
124impl FeatureGraph {
125 pub fn from_features_table(features: &toml_edit::Table) -> Result<Self> {
127 let mut depends_on = HashMap::new();
128 for (k, vs) in features.iter() {
129 let mut d = HashSet::new();
130 for v in vs
131 .as_array()
132 .ok_or_else(|| anyhow!("features.{} was not an array", k))?
133 {
134 let v = v
135 .as_str()
136 .ok_or_else(|| anyhow!("features.{} contained a non-string", k))?;
137 d.insert(v.to_string());
138 }
139 depends_on.insert(k.to_string(), d);
140 }
141 let all_features = depends_on.keys().cloned().collect();
142 let reachable_from = transitive_closure(&depends_on);
143 let depended_on_by = invert(&depends_on);
144 Ok(Self {
145 all_features,
146 depends_on,
147 depended_on_by,
148 reachable_from,
149 })
150 }
151
152 pub fn all_features(&self) -> impl Iterator<Item = String> + '_ {
153 self.depends_on.keys().cloned()
154 }
155
156 pub fn contains_feature(&self, feature: &str) -> bool {
157 self.all_features.contains(feature)
158 }
159
160 pub fn contains_edge(&self, from: &str, to: &str) -> bool {
161 match self.depends_on.get(from) {
162 Some(fs) => fs.contains(to),
163 None => false,
164 }
165 }
166
167 pub fn all_reachable_from(&self, feature: &str) -> impl Iterator<Item = String> + '_ {
168 match self.reachable_from.get(feature) {
169 Some(set) => itertools::Either::Left(set.iter().cloned()),
170 None => itertools::Either::Right(std::iter::empty()),
171 }
172 }
173
174 pub fn edges_from(&self, feature: &str) -> impl Iterator<Item = String> + '_ {
177 match self.depends_on.get(feature) {
178 Some(set) => itertools::Either::Left(set.iter().cloned()),
179 None => itertools::Either::Right(std::iter::empty()),
180 }
181 }
182
183 pub fn edges_to(&self, feature: &str) -> impl Iterator<Item = String> + '_ {
186 match self.depended_on_by.get(feature) {
187 Some(set) => itertools::Either::Left(set.iter().cloned()),
188 None => itertools::Either::Right(std::iter::empty()),
189 }
190 }
191}