use std::collections::HashSet;
use std::io::{self, BufRead};
use std::fmt::Write;
pub fn list_enabled() -> Vec<String> {
list_enabled_with_path("Cargo.toml")
}
pub fn list_enabled_with_path(cargo_toml_path: &str) -> Vec<String> {
let all_features = list_all(cargo_toml_path).unwrap();
list_enabled_among(&all_features)
}
pub fn list_enabled_as_string(const_name: &str) -> String {
list_enabled_as_string_with_path(const_name, "Cargo.toml")
}
pub fn list_enabled_as_string_with_path(const_name: &str, cargo_toml_path: &str) -> String {
let enabled_features = list_enabled_with_path(cargo_toml_path);
let mut buf = String::new();
writeln!(buf, "pub const {const_name}: &[&str] = &[").unwrap();
for feature in enabled_features {
writeln!(buf, r#""{feature}","#).unwrap();
}
writeln!(buf, "];").unwrap();
buf
}
pub fn list_all<S: AsRef<str>>(cargo_toml_path: S) -> Result<HashSet<String>, io::Error> {
let file = std::fs::File::open(cargo_toml_path.as_ref())?;
let reader = io::BufReader::new(file);
let lines: Result<Vec<String>, io::Error> = reader.lines().collect();
let lines = lines?;
Ok(parse_feature_keys_from_lines(lines))
}
fn parse_feature_keys_from_lines<I>(lines: I) -> HashSet<String>
where
I: IntoIterator<Item = String>,
{
let mut in_features = false;
let mut features = HashSet::new();
for line in lines {
let stripped = line.split('#').next().unwrap_or("").trim();
if stripped.starts_with('[') {
in_features = stripped == "[features]";
continue;
}
if in_features && !stripped.is_empty() {
if let Some((key, _)) = stripped.split_once('=') {
let key = key.trim().trim_matches('"');
if !key.is_empty() {
features.insert(key.to_string());
}
}
}
}
features
}
#[cfg(feature = "test")]
pub fn test_parse_feature_keys_from_lines<I>(lines: I) -> HashSet<String>
where
I: IntoIterator<Item = String>,
{
parse_feature_keys_from_lines(lines)
}
fn list_enabled_among(all_features: &std::collections::HashSet<String>) -> Vec<String> {
let normalize = |s: &str| s.to_lowercase().replace('_', "-");
let mut enabled: Vec<String> = std::env::vars()
.filter_map(|(k, _)| {
if let Some(name) = k.strip_prefix("CARGO_FEATURE_") {
let norm_name = normalize(name);
if let Some(matched) = all_features
.iter()
.find(|feat| normalize(feat) == norm_name)
{
return Some(matched.clone());
}
}
None
})
.collect();
enabled.sort();
if let Some(pos) = enabled.iter().position(|f| f == "default") {
let default_feature = enabled.remove(pos);
enabled.insert(0, default_feature);
}
enabled
}
#[cfg(feature = "test")]
pub fn test_list_enabled_among(all_features: &std::collections::HashSet<String>) -> Vec<String> {
list_enabled_among(all_features)
}