use std::collections::BTreeMap;
pub fn collect_feature_dependencies(manifest: &str) -> BTreeMap<String, Vec<String>> {
let mut in_features = false;
let mut features = BTreeMap::new();
let mut current: Option<(String, Vec<String>)> = None;
for raw in manifest.lines() {
let line = raw.trim();
if line == "[features]" {
in_features = true;
continue;
}
if in_features && line.starts_with('[') {
break;
}
if !in_features || line.is_empty() {
continue;
}
if let Some((name, deps)) = parse_feature_line(line) {
if let Some((previous, previous_deps)) = current.take() {
features.insert(previous, previous_deps);
}
if line.ends_with(']') {
features.insert(name, deps);
} else {
current = Some((name, deps));
}
continue;
}
if let Some((_, deps)) = current.as_mut() {
deps.extend(parse_array_items(line));
if line.ends_with(']') {
let (name, deps) = current.take().expect("current feature");
features.insert(name, deps);
}
}
}
features
}
fn parse_feature_line(line: &str) -> Option<(String, Vec<String>)> {
let (name, rest) = line.split_once('=')?;
Some((name.trim().to_owned(), parse_array_items(rest)))
}
fn parse_array_items(line: &str) -> Vec<String> {
line.split('"')
.skip(1)
.step_by(2)
.map(str::to_owned)
.collect()
}
pub fn assert_feature_includes(
features: &BTreeMap<String, Vec<String>>,
feature: &str,
expected: &[&str],
) {
let deps = features.get(feature).expect("feature exists");
for dep in expected {
assert!(
deps.iter().any(|candidate| candidate == dep),
"{feature} should include {dep}; got {deps:?}"
);
}
}