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(mismatched_lifetime_syntaxes)] #![allow(clippy::bool_assert_comparison)]
52#![allow(clippy::clone_on_copy)]
53#![allow(clippy::dbg_macro)]
54#![allow(clippy::mixed_attributes_style)]
55#![allow(clippy::print_stderr)]
56#![allow(clippy::print_stdout)]
57#![allow(clippy::single_char_pattern)]
58#![allow(clippy::unwrap_used)]
59#![allow(clippy::unchecked_duration_subtraction)]
60#![allow(clippy::useless_vec)]
61#![allow(clippy::needless_pass_by_value)]
62#![allow(clippy::missing_docs_in_private_items)]
65#![allow(unreachable_pub)]
66
67use anyhow::{anyhow, Result};
68use std::collections::{HashMap, HashSet};
69use std::hash::Hash;
70
71fn transitive_closure<K: Hash + Eq + PartialEq + Clone>(
73 inp: &HashMap<K, HashSet<K>>,
74) -> HashMap<K, HashSet<K>> {
75 let mut out = inp.clone();
76 let max_iters = inp.len();
77 for _ in 0..max_iters {
78 let mut new_out = out.clone();
79 for (k, vs) in out.iter() {
80 let new_vs = new_out
81 .get_mut(k)
82 .expect("That key should have been there when I cloned it...");
83 for v in vs.iter() {
84 if let Some(vv) = out.get(v) {
85 new_vs.extend(vv.iter().cloned());
86 }
87 }
88 }
89 if out == new_out {
90 break;
91 } else {
92 out = new_out;
93 }
94 }
95 out
96}
97
98fn invert<K: Hash + Eq + PartialEq + Clone>(
100 inp: &HashMap<K, HashSet<K>>,
101) -> HashMap<K, HashSet<K>> {
102 let mut out: HashMap<K, HashSet<K>> = HashMap::new();
103 for (k, vs) in inp.iter() {
104 for v in vs {
105 out.entry(v.clone()).or_default().insert(k.clone());
106 }
107 }
108 out
109}
110
111#[derive(Clone, Debug, Default)]
113pub struct FeatureGraph {
114 all_features: HashSet<String>,
116 depends_on: HashMap<String, HashSet<String>>,
119 depended_on_by: HashMap<String, HashSet<String>>,
121 reachable_from: HashMap<String, HashSet<String>>,
123}
124
125impl FeatureGraph {
126 pub fn from_features_table(features: &toml_edit::Table) -> Result<Self> {
128 let mut depends_on = HashMap::new();
129 for (k, vs) in features.iter() {
130 let mut d = HashSet::new();
131 for v in vs
132 .as_array()
133 .ok_or_else(|| anyhow!("features.{} was not an array", k))?
134 {
135 let v = v
136 .as_str()
137 .ok_or_else(|| anyhow!("features.{} contained a non-string", k))?;
138 d.insert(v.to_string());
139 }
140 depends_on.insert(k.to_string(), d);
141 }
142 let all_features = depends_on.keys().cloned().collect();
143 let reachable_from = transitive_closure(&depends_on);
144 let depended_on_by = invert(&depends_on);
145 Ok(Self {
146 all_features,
147 depends_on,
148 depended_on_by,
149 reachable_from,
150 })
151 }
152
153 pub fn all_features(&self) -> impl Iterator<Item = String> + '_ {
154 self.depends_on.keys().cloned()
155 }
156
157 pub fn contains_feature(&self, feature: &str) -> bool {
158 self.all_features.contains(feature)
159 }
160
161 pub fn contains_edge(&self, from: &str, to: &str) -> bool {
162 match self.depends_on.get(from) {
163 Some(fs) => fs.contains(to),
164 None => false,
165 }
166 }
167
168 pub fn all_reachable_from(&self, feature: &str) -> impl Iterator<Item = String> + '_ {
169 match self.reachable_from.get(feature) {
170 Some(set) => itertools::Either::Left(set.iter().cloned()),
171 None => itertools::Either::Right(std::iter::empty()),
172 }
173 }
174
175 pub fn edges_from(&self, feature: &str) -> impl Iterator<Item = String> + '_ {
178 match self.depends_on.get(feature) {
179 Some(set) => itertools::Either::Left(set.iter().cloned()),
180 None => itertools::Either::Right(std::iter::empty()),
181 }
182 }
183
184 pub fn edges_to(&self, feature: &str) -> impl Iterator<Item = String> + '_ {
187 match self.depended_on_by.get(feature) {
188 Some(set) => itertools::Either::Left(set.iter().cloned()),
189 None => itertools::Either::Right(std::iter::empty()),
190 }
191 }
192}