From 5237eed46c78b4a81c056a69a67916e669cbbe28 Mon Sep 17 00:00:00 2001 From: databasedav <31483365+databasedav@users.noreply.github.com> Date: Mon, 11 Nov 2024 00:29:28 -0800 Subject: [PATCH] collect defaults recursively (#28) fixes #19 --- lib.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 11 deletions(-) diff --git a/lib.rs b/lib.rs index cd12f1b..9afbf8a 100644 --- a/lib.rs +++ b/lib.rs @@ -154,7 +154,7 @@ extern crate proc_macro; use proc_macro::{TokenStream, TokenTree}; use std::borrow::Cow; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; use std::fmt::Write; use std::path::Path; @@ -371,6 +371,35 @@ foo = bar )); } +fn dependents( + feature_dependencies: &HashMap>, + feature: &str, + collected: &mut HashSet, +) { + if collected.contains(feature) { + return; + } + collected.insert(feature.to_string()); + if let Some(dependencies) = feature_dependencies.get(feature) { + for dependency in dependencies { + dependents(feature_dependencies, dependency, collected); + } + } +} + +fn parse_feature_deps<'a>( + s: &'a str, + dep: &str, +) -> Result + 'a, String> { + Ok(s.trim() + .strip_prefix('[') + .and_then(|r| r.strip_suffix(']')) + .ok_or_else(|| format!("Parse error while parsing dependency {}", dep))? + .split(',') + .map(|d| d.trim().trim_matches(|c| c == '"' || c == '\'').trim().to_string()) + .filter(|d: &String| !d.is_empty())) +} + fn process_toml(cargo_toml: &str, args: &Args) -> Result { // Get all lines between the "[features]" and the next block let mut lines = cargo_toml @@ -385,6 +414,7 @@ fn process_toml(cargo_toml: &str, args: &Args) -> Result { let mut features = vec![]; let mut default_features = HashSet::new(); let mut current_table = ""; + let mut dependencies = HashMap::new(); while let Some(line) = lines.next() { if let Some(x) = line.strip_prefix("#!") { if !x.is_empty() && !x.starts_with(' ') { @@ -408,6 +438,7 @@ fn process_toml(cargo_toml: &str, args: &Args) -> Result { .map(|(t, _)| t.trim()) .ok_or_else(|| format!("Parse error while parsing line: {}", line))?; if !current_comment.is_empty() { + #[allow(clippy::unnecessary_lazy_evaluations)] let dep = current_table .rsplit_once('.') .and_then(|(table, dep)| table.trim().ends_with("dependencies").then(|| dep)) @@ -422,16 +453,17 @@ fn process_toml(cargo_toml: &str, args: &Args) -> Result { let dep = dep.trim().trim_matches('"'); let rest = get_balanced(rest, &mut lines) .map_err(|e| format!("Parse error while parsing value {}: {}", dep, e))?; - if current_table == "features" && dep == "default" { - let defaults = rest - .trim() - .strip_prefix('[') - .and_then(|r| r.strip_suffix(']')) - .ok_or_else(|| format!("Parse error while parsing dependency {}", dep))? - .split(',') - .map(|d| d.trim().trim_matches(|c| c == '"' || c == '\'').trim().to_string()) - .filter(|d| !d.is_empty()); - default_features.extend(defaults); + if current_table == "features" { + if dep == "default" { + default_features.extend(parse_feature_deps(&rest, dep)?); + } else { + for d in parse_feature_deps(&rest, dep)? { + dependencies + .entry(dep.to_string()) + .or_insert_with(Vec::new) + .push(d.clone()); + } + } } if !current_comment.is_empty() { if current_table.ends_with("dependencies") { @@ -456,6 +488,12 @@ fn process_toml(cargo_toml: &str, args: &Args) -> Result { } } } + let df = default_features.iter().cloned().collect::>(); + for feature in df { + let mut resolved = HashSet::new(); + dependents(&dependencies, &feature, &mut resolved); + default_features.extend(resolved.into_iter()); + } if !current_comment.is_empty() { return Err("Found comment not associated with a feature".into()); } @@ -983,4 +1021,20 @@ default = ["teßt."] "* teßt. *(enabled by default)* — This is a test\n* dep — A dep\n" ); } + + #[test] + fn recursive_default() { + let toml = r#" +[features] +default=["qqq"] + +## Qqq +qqq=["www"] + +## Www +www=[] + "#; + let parsed = process_toml(toml, &Args::default()).unwrap(); + assert_eq!(parsed, "* **`qqq`** *(enabled by default)* — Qqq\n* **`www`** *(enabled by default)* — Www\n"); + } }