From badefd682659a4de7c07afa016f6e9804c48548d Mon Sep 17 00:00:00 2001 From: tobinio Date: Sat, 4 May 2024 12:24:38 +0200 Subject: [PATCH 1/6] correctly handle keeping sub features --- CHANGELOG.md | 3 +- Cargo.toml | 2 +- Features.toml | 4 +- src/prune.rs | 267 ++++++++++++++++++++++++++++---------------------- 4 files changed, 154 insertions(+), 122 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 127a230..e8e3b8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ * use `color-eyre` instead of `anyhow` * handle unused workspace dependencies * allow `default` to be a sub_feature -* sort dependencies and packages alphabetically if no filter is set +* sort dependencies and packages alphabetically if no filter is set +* when running `cargo features prune` correctly handle sub features ## 0.7.1 diff --git a/Cargo.toml b/Cargo.toml index 2b2eb76..4a0b780 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ include = ["src/**/*", "LICENSE", "README.md"] color-eyre = "0.6.3" cargo-platform = "0.1.8" cargo_metadata = "0.18.1" -clap = { version = "4.5.4", features = ["color", "derive", "error-context", "help", "suggestions", "usage"], default-features = false } +clap = { version = "4.5.4", features = ["derive"] } clap_complete = "4.5.2" console = { version = "0.15.8", default-features = false } ctrlc = "3.4.4" diff --git a/Features.toml b/Features.toml index 0e6b229..692ad98 100644 --- a/Features.toml +++ b/Features.toml @@ -1,2 +1,2 @@ -clap = ["color", "error-context", "help", "suggestions", "usage"] -color-eyre = ["capture-spantrace","track-caller", "color-spantrace", "tracing-error"] \ No newline at end of file +clap = ["default"] +color-eyre = ["default"] \ No newline at end of file diff --git a/src/prune.rs b/src/prune.rs index 639c071..c27bfaf 100644 --- a/src/prune.rs +++ b/src/prune.rs @@ -7,166 +7,197 @@ use std::io::Write; use std::ops::Not; use std::path::Path; +use crate::project::dependency::Dependency; use crate::project::document::Document; use crate::save::save_dependency; use color_eyre::eyre::eyre; use std::process::{Command, Stdio}; use toml::Table; +//todo test with renamed dependencies!!! + pub fn prune(mut document: Document, is_dry_run: bool) -> Result<()> { let mut term = Term::stdout(); - let ignored_features = get_ignored_features("./")?; + let mut enabled_features = get_enabled_features(&document); - for name in document.get_packages_names().iter() { - if document.is_workspace() { - writeln!(term, "{}", name)?; - prune_package( - &mut document, - is_dry_run, - &mut term, - name, - 2, - &ignored_features, - )?; - } else { - prune_package( - &mut document, - is_dry_run, - &mut term, - name, - 0, - &ignored_features, - )?; + let base_ignored_features = get_ignored_features("./")?; + remove_ignored_features(&document, &base_ignored_features, &mut enabled_features)?; + + prune_features(&mut document, is_dry_run, &mut term, enabled_features)?; + + Ok(()) +} + +fn get_enabled_features(document: &Document) -> HashMap>> { + let mut data = HashMap::new(); + + for package in document.get_packages() { + let mut package_data = HashMap::new(); + + for dependency in package.get_deps() { + let enabled_features = dependency + .features + .iter() + .filter(|(_name, data)| data.is_toggleable() && data.is_enabled()) + .map(|(name, _data)| name) + .cloned() + .collect::>(); + + if enabled_features.is_empty().not() { + package_data.insert(dependency.name.clone(), enabled_features); + } + } + + if package_data.is_empty().not() { + data.insert(package.name.clone(), package_data); } } + data +} + +fn remove_ignored_features( + document: &Document, + base_ignored: &HashMap>, + enabled_features: &mut HashMap>>, +) -> Result<()> { + for (package_name, dependencies) in enabled_features { + let package = document.get_package(package_name)?; + + let ignored_features = + get_ignored_features(package.manifest_path.trim_end_matches("/Cargo.toml"))?; + + for (dependency_name, features) in dependencies { + let dependency = package.get_dep(dependency_name)?; + + if dependency.can_use_default() { + features.push("default".to_string()); + } + + for feature in ignored_features.get(dependency_name).unwrap_or(&vec![]) { + remove_feature(feature, features, dependency); + } + for feature in base_ignored.get(dependency_name).unwrap_or(&vec![]) { + remove_feature(feature, features, dependency); + } + + if let Some(index) = features.iter().position(|name| name == "default") { + features.remove(index); + } + } + } + + //todo remove empty packages / dependencies Ok(()) } -fn prune_package( +fn remove_feature(feature: &String, features: &mut Vec, dependency: &Dependency) { + let index = features.iter().position(|name| name == feature); + + let Some(index) = index else { + return; + }; + + features.remove(index); + + if let Some(feature) = dependency.get_feature(&feature) { + for sub_feature in &feature.sub_features { + remove_feature(&sub_feature.name, features, dependency); + } + } +} + +fn prune_features( document: &mut Document, is_dry_run: bool, term: &mut Term, - package_name: &str, - inset: usize, - base_ignored: &HashMap>, + features: HashMap>>, ) -> Result<()> { - let deps = document - .get_package(package_name)? - .get_deps() - .iter() - .map(|dep| dep.get_name()) - .collect::>(); - - let ignored_features = get_ignored_features( - document - .get_package(package_name)? - .manifest_path - .trim_end_matches("/Cargo.toml"), - )?; - - for name in deps.iter() { - let dependency = document.get_package_mut(package_name)?.get_dep_mut(name)?; - - let enabled_features = dependency - .features - .iter() - .filter(|(_name, data)| data.is_toggleable() && data.is_enabled()) - .filter(|(feature_name, _data)| { - !ignored_features - .get(name) - .unwrap_or(&vec![]) - .contains(feature_name) - }) - .filter(|(feature_name, _data)| { - !base_ignored - .get(name) - .unwrap_or(&vec![]) - .contains(feature_name) - }) - .map(|(name, _)| name) - .cloned() - .collect::>(); - - if enabled_features.is_empty() { - continue; - } - - term.clear_line()?; - writeln!(term, "{:inset$}{} [0/0]", "", name)?; + let inset = if features.len() == 1 { 0 } else { 2 }; - let mut to_be_disabled = vec![]; + for (package_name, dependencies) in features { + if document.is_workspace() { + writeln!(term, "{}", package_name)?; + } - for (id, feature) in enabled_features.iter().enumerate() { + for (dependency_name, features) in dependencies { term.clear_line()?; - writeln!(term, "{:inset$} โ”” {}", "", feature)?; - - document - .get_package_mut(package_name)? - .get_dep_mut(name)? - .disable_feature(feature)?; + writeln!(term, "{:inset$}{} [0/0]", "", dependency_name)?; - save_dependency(document, package_name, name)?; + let mut to_be_disabled = vec![]; - if check()? { - to_be_disabled.push(feature.to_string()); - } + for (id, feature) in features.iter().enumerate() { + term.clear_line()?; + writeln!(term, "{:inset$} โ”” {}", "", feature)?; - //reset to start - for feature in &enabled_features { document - .get_package_mut(package_name)? - .get_dep_mut(name)? - .enable_feature(feature)?; + .get_package_mut(&package_name)? + .get_dep_mut(&dependency_name)? + .disable_feature(feature)?; + + save_dependency(document, &package_name, &dependency_name)?; + + if check()? { + to_be_disabled.push(feature.to_string()); + } + + //reset to start + for feature in &features { + document + .get_package_mut(&package_name)? + .get_dep_mut(&dependency_name)? + .enable_feature(feature)?; + } + + save_dependency(document, &package_name, &dependency_name)?; + + term.move_cursor_up(2)?; + term.clear_line()?; + writeln!( + term, + "{:inset$}{} [{}/{}]", + "", + dependency_name, + id + 1, + features.len() + )?; } - save_dependency(document, package_name, name)?; + let mut disabled_count = style(to_be_disabled.len()); - term.move_cursor_up(2)?; + if to_be_disabled.is_empty().not() { + disabled_count = disabled_count.red(); + } + + term.move_cursor_up(1)?; term.clear_line()?; writeln!( term, "{:inset$}{} [{}/{}]", "", - name, - id + 1, - enabled_features.len() + dependency_name, + disabled_count, + features.len() )?; - } - - let mut disabled_count = style(to_be_disabled.len()); - if to_be_disabled.is_empty().not() { - disabled_count = disabled_count.red(); - } + if is_dry_run { + continue; + } - term.move_cursor_up(1)?; - term.clear_line()?; - writeln!( - term, - "{:inset$}{} [{}/{}]", - "", - name, - disabled_count, - enabled_features.len() - )?; - - if is_dry_run { - continue; - } + if to_be_disabled.is_empty().not() { + for feature in to_be_disabled { + document + .get_package_mut(&package_name)? + .get_dep_mut(&dependency_name)? + .disable_feature(&feature)?; + } - if to_be_disabled.is_empty().not() { - for feature in to_be_disabled { - document - .get_package_mut(package_name)? - .get_dep_mut(name)? - .disable_feature(&feature)?; + save_dependency(document, &package_name, &dependency_name)?; } - - save_dependency(document, package_name, name)?; } } + Ok(()) } -- 2.51.2 From 9e1ebf60f71c1f690493c01fae03b5f3022f6132 Mon Sep 17 00:00:00 2001 From: tobinio Date: Sat, 4 May 2024 13:51:26 +0200 Subject: [PATCH 2/6] feat(#28): move Features.toml into Cargo.toml --- CHANGELOG.md | 3 ++ Cargo.toml | 4 ++ Features.toml | 2 - README.md | 34 ++++++++----- src/parsing/dependency.rs | 3 +- src/project/dependency/util.rs | 86 -------------------------------- src/project/document.rs | 7 --- src/prune.rs | 48 ++++++++++++------ src/save.rs | 4 +- src/util.rs | 89 +++++++++++++++++++++++++++++++++- 10 files changed, 154 insertions(+), 126 deletions(-) delete mode 100644 Features.toml diff --git a/CHANGELOG.md b/CHANGELOG.md index e8e3b8f..dc617e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ ## unreleased + +* #### BREAKING - move Features.toml into Cargo.toml see [README.md](README.md#prune) + * use `color-eyre` instead of `anyhow` * handle unused workspace dependencies * allow `default` to be a sub_feature diff --git a/Cargo.toml b/Cargo.toml index 4a0b780..c76084a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,3 +30,7 @@ toml_edit = { version = "0.22.12", default-features = false, features = ["displa [[bin]] name = "cargo-features" path = "src/main.rs" + +[cargo-features-manager.keep] +clap = ["default"] +color-eyre = ["default"] diff --git a/Features.toml b/Features.toml deleted file mode 100644 index 692ad98..0000000 --- a/Features.toml +++ /dev/null @@ -1,2 +0,0 @@ -clap = ["default"] -color-eyre = ["default"] \ No newline at end of file diff --git a/README.md b/README.md index 03d46fe..5967482 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,15 @@ A TUI-like cli tool to manage the features of your rust-projects dependencies. You can view all available features and easily toggle (enable & disable) them with one button click. All of your changes will directly be reflected in your Cargo.toml file. +--- + ## install `cargo install cargo-features-manager` -## usage +--- -### viewer +## usage To start the tool run `cargo features` in your project root dir. @@ -27,7 +29,7 @@ Selecting a dependency will open the feature-selector: When using `cargo features -d ` it will directly open the corresponding feature-selector. -#### navigation +### navigation โ†‘ to move up @@ -37,7 +39,7 @@ When using `cargo features -d ` it will directly open the corre ESC | โ† to move back -#### dependency selector +### dependency selector Dependency which do not have any features are marked grey.
Dev-Dependency are marked with ๐Ÿงช.
@@ -46,7 +48,7 @@ Workspace-Dependency are marked with ๐Ÿ—ƒ๏ธ๏ธ. ![dependencySelector](resources/dependencySelector.png) -#### feature selector +### feature selector All default features are marked Green. @@ -68,22 +70,32 @@ Features marked with ๐Ÿ—ƒ๏ธ๏ธ are enabled by the workspace dependency and can ![workspaceFeatures](resources/workspaceFeatures.png) -#### search mode +### search mode At any point you can start typing like normal. This will start using your input as a search query. -### prune +--- + +## prune You can run prune with `cargo features prune` this will disable all features which are not required to compile. -#### always keep +### always keep -If your project requires a features to be enabled which does not make the compile fail. you can create a file -called `Features.toml` in there you can define features which will not get disabled. +If your project requires features to be enabled which do not make the compile fail. You can add a section to +your `Cargo.toml` named `cargo-features-manager.keep` in there you can define which features will be kept. ```toml -clap = ["help"] +# for individial packages +[cargo-features-manager.keep] +clap = ["default"] +color-eyre = ["capture-spantrace", "track-caller"] + +# for the whole workspace +[workspace.cargo-features-manager.keep] +clap = ["default"] +color-eyre = ["capture-spantrace", "track-caller"] ``` \ No newline at end of file diff --git a/src/parsing/dependency.rs b/src/parsing/dependency.rs index 07a0783..f099941 100644 --- a/src/parsing/dependency.rs +++ b/src/parsing/dependency.rs @@ -1,7 +1,8 @@ use crate::parsing::package::get_package_from_version; use crate::project::dependency::feature::{EnabledState, FeatureData, SubFeature, SubFeatureType}; -use crate::project::dependency::util::{get_item_from_doc, get_path}; +use crate::project::dependency::util::get_path; use crate::project::dependency::{Dependency, DependencyType}; +use crate::util::get_item_from_doc; use cargo_metadata::PackageId; use color_eyre::eyre::{eyre, ContextCompat}; use itertools::Itertools; diff --git a/src/project/dependency/util.rs b/src/project/dependency/util.rs index 047f809..558e95c 100644 --- a/src/project/dependency/util.rs +++ b/src/project/dependency/util.rs @@ -1,7 +1,5 @@ use crate::project::dependency::DependencyType; use cargo_platform::Platform; -use color_eyre::eyre::{bail, eyre, ContextCompat}; -use std::str::FromStr; pub fn get_path(kind: &DependencyType, target: &Option) -> String { let path = match kind { @@ -21,87 +19,3 @@ pub fn get_path(kind: &DependencyType, target: &Option) -> String { path.to_string() } - -pub fn get_mut_item_from_doc<'a>( - path: &str, - document: &'a mut toml_edit::DocumentMut, -) -> color_eyre::Result<&'a mut toml_edit::Item> { - let mut item = document.as_item_mut(); - - let mut is_target = false; - - 'outer: for key in path.split('.') { - if is_target { - is_target = false; - - let target = Platform::from_str(key.trim_start_matches('\'').trim_end_matches('\''))?; - - let table = item - .as_table_like_mut() - .context(eyre!("could not find - {} - no table", path))?; - - for (key, next_item) in table.iter_mut() { - let platform = - Platform::from_str(key.trim_start_matches('\'').trim_end_matches('\''))?; - - if platform.eq(&target) { - item = next_item; - continue 'outer; - } - } - - bail!("could not find - {} - no table", path) - } - - item = item - .get_mut(key) - .context(eyre!("could not find - {}", path))?; - - if key == "target" { - is_target = true; - } - } - - Ok(item) -} - -pub fn get_item_from_doc<'a>( - path: &str, - document: &'a toml_edit::DocumentMut, -) -> color_eyre::Result<&'a toml_edit::Item> { - let mut item = document.as_item(); - - let mut is_target = false; - - 'outer: for key in path.split('.') { - if is_target { - is_target = false; - - let target = Platform::from_str(key.trim_start_matches('\'').trim_end_matches('\''))?; - - let table = item - .as_table() - .context(eyre!("could not find - {} - no table", path))?; - - for (key, next_item) in table.iter() { - let platform = - Platform::from_str(key.trim_start_matches('\'').trim_end_matches('\''))?; - - if platform.eq(&target) { - item = next_item; - continue 'outer; - } - } - - bail!("could not find - {} - no table", path) - } - - item = item.get(key).context(eyre!("could not find - {}", path))?; - - if key == "target" { - is_target = true; - } - } - - Ok(item) -} diff --git a/src/project/document.rs b/src/project/document.rs index f6b8e7e..fafd155 100644 --- a/src/project/document.rs +++ b/src/project/document.rs @@ -102,13 +102,6 @@ impl Document { &self.packages } - pub fn get_packages_names(&self) -> Vec { - self.packages - .iter() - .map(|package| package.name.to_string()) - .collect() - } - pub fn get_package_by_id(&self, package_id: usize) -> Result<&Package> { self.packages .get(package_id) diff --git a/src/prune.rs b/src/prune.rs index c27bfaf..1b34aed 100644 --- a/src/prune.rs +++ b/src/prune.rs @@ -10,18 +10,17 @@ use std::path::Path; use crate::project::dependency::Dependency; use crate::project::document::Document; use crate::save::save_dependency; -use color_eyre::eyre::eyre; +use crate::util::{get_item_from_doc, toml_document_from_path}; +use color_eyre::eyre::{eyre, ContextCompat}; use std::process::{Command, Stdio}; -use toml::Table; - -//todo test with renamed dependencies!!! pub fn prune(mut document: Document, is_dry_run: bool) -> Result<()> { let mut term = Term::stdout(); let mut enabled_features = get_enabled_features(&document); - let base_ignored_features = get_ignored_features("./")?; + let base_ignored_features = + get_ignored_features("./", "workspace.cargo-features-manager.keep")?; remove_ignored_features(&document, &base_ignored_features, &mut enabled_features)?; prune_features(&mut document, is_dry_run, &mut term, enabled_features)?; @@ -45,7 +44,7 @@ fn get_enabled_features(document: &Document) -> HashMap>(); if enabled_features.is_empty().not() { - package_data.insert(dependency.name.clone(), enabled_features); + package_data.insert(dependency.get_name().clone(), enabled_features); } } @@ -53,6 +52,7 @@ fn get_enabled_features(document: &Document) -> HashMap Result { Ok(code == 0) } -fn get_ignored_features>(base_path: P) -> Result>> { - let result = fs::read_to_string(base_path.as_ref().join("Features.toml")); +fn get_ignored_features>( + file_path: P, + item_path: &str, +) -> Result>> { + let result = toml_document_from_path(file_path.as_ref().join("Cargo.toml")); match result { - Ok(file) => { - let table = file.parse::()?; + Ok(document) => { + let item = get_item_from_doc(&item_path, &document); + + let Ok(item) = item else { + return Ok(HashMap::new()); + }; + + let table = item.as_table_like().context(format!( + "could not parse {} in {:?}", + item_path, + file_path.as_ref() + ))?; let mut map = HashMap::new(); - for (key, value) in table { + for (key, value) in table.iter() { map.insert( - key, + key.to_string(), value .as_array() - .ok_or(eyre!("Invalid Features.toml format"))? + .ok_or(eyre!("Invalid format to keep features"))? .iter() - .to_owned() .filter_map(|value| value.as_str()) .map(|value| value.to_string()) .collect(), ); } + Ok(map) } Err(_) => Ok(HashMap::new()), diff --git a/src/save.rs b/src/save.rs index 43fccff..5fd2d4b 100644 --- a/src/save.rs +++ b/src/save.rs @@ -1,6 +1,6 @@ -use crate::project::dependency::util::{get_mut_item_from_doc, get_path}; +use crate::project::dependency::util::get_path; use crate::project::document::Document; -use crate::util::toml_document_from_path; +use crate::util::{get_mut_item_from_doc, toml_document_from_path}; use color_eyre::eyre::{ContextCompat, Error}; use std::fs; use toml_edit::{Array, Formatted, InlineTable, Item, Value}; diff --git a/src/util.rs b/src/util.rs index cfce633..5031d7a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,8 @@ -use color_eyre::eyre::eyre; +use color_eyre::eyre::{bail, ContextCompat, eyre}; use std::fs; use std::path::Path; +use std::str::FromStr; +use cargo_platform::Platform; pub fn toml_document_from_path>( dir_path: P, @@ -10,3 +12,88 @@ pub fn toml_document_from_path>( Ok(file_content.parse()?) } + + +pub fn get_mut_item_from_doc<'a>( + path: &str, + document: &'a mut toml_edit::DocumentMut, +) -> color_eyre::Result<&'a mut toml_edit::Item> { + let mut item = document.as_item_mut(); + + let mut is_target = false; + + 'outer: for key in path.split('.') { + if is_target { + is_target = false; + + let target = Platform::from_str(key.trim_start_matches('\'').trim_end_matches('\''))?; + + let table = item + .as_table_like_mut() + .context(eyre!("could not find - {} - no table", path))?; + + for (key, next_item) in table.iter_mut() { + let platform = + Platform::from_str(key.trim_start_matches('\'').trim_end_matches('\''))?; + + if platform.eq(&target) { + item = next_item; + continue 'outer; + } + } + + bail!("could not find - {} - no table", path) + } + + item = item + .get_mut(key) + .context(eyre!("could not find - {}", path))?; + + if key == "target" { + is_target = true; + } + } + + Ok(item) +} + +pub fn get_item_from_doc<'a>( + path: &str, + document: &'a toml_edit::DocumentMut, +) -> color_eyre::Result<&'a toml_edit::Item> { + let mut item = document.as_item(); + + let mut is_target = false; + + 'outer: for key in path.split('.') { + if is_target { + is_target = false; + + let target = Platform::from_str(key.trim_start_matches('\'').trim_end_matches('\''))?; + + let table = item + .as_table() + .context(eyre!("could not find - {} - no table", path))?; + + for (key, next_item) in table.iter() { + let platform = + Platform::from_str(key.trim_start_matches('\'').trim_end_matches('\''))?; + + if platform.eq(&target) { + item = next_item; + continue 'outer; + } + } + + bail!("could not find - {} - no table", path) + } + + item = item.get(key).context(eyre!("could not find - {}", path))?; + + if key == "target" { + is_target = true; + } + } + + Ok(item) +} \ No newline at end of file -- 2.51.2 From 2bd8251e76a2641d364bb5977bd020046cadc20a Mon Sep 17 00:00:00 2001 From: tobinio Date: Sat, 4 May 2024 15:23:27 +0200 Subject: [PATCH 3/6] feat(#29): improve progress display for prune --- CHANGELOG.md | 1 + src/prune.rs | 87 ++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc617e4..c4534da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * allow `default` to be a sub_feature * sort dependencies and packages alphabetically if no filter is set * when running `cargo features prune` correctly handle sub features +* improved progress display while running `cargo features prune` ## 0.7.1 diff --git a/src/prune.rs b/src/prune.rs index 1b34aed..56c9e85 100644 --- a/src/prune.rs +++ b/src/prune.rs @@ -1,6 +1,5 @@ use color_eyre::Result; use std::collections::HashMap; -use std::fs; use console::{style, Term}; use std::io::Write; @@ -28,7 +27,9 @@ pub fn prune(mut document: Document, is_dry_run: bool) -> Result<()> { Ok(()) } -fn get_enabled_features(document: &Document) -> HashMap>> { +type FeaturesToTest = HashMap>>; + +fn get_enabled_features(document: &Document) -> FeaturesToTest { let mut data = HashMap::new(); for package in document.get_packages() { @@ -59,7 +60,7 @@ fn get_enabled_features(document: &Document) -> HashMap>, - enabled_features: &mut HashMap>>, + enabled_features: &mut FeaturesToTest, ) -> Result<()> { for (package_name, dependencies) in enabled_features { let package = document.get_package(package_name)?; @@ -114,24 +115,60 @@ fn prune_features( document: &mut Document, is_dry_run: bool, term: &mut Term, - features: HashMap>>, + features: FeaturesToTest, ) -> Result<()> { - let inset = if features.len() == 1 { 0 } else { 2 }; + let feature_count = features + .values() + .flat_map(|dependencies| dependencies.values()) + .flatten() + .count(); + + let mut checked_features_count = 0; + + writeln!( + term, + "workspace [{}/{}]", + checked_features_count, feature_count + )?; + + let mut offset_to_top = 1; + + let package_inset = if features.len() == 1 { 0 } else { 2 }; + let dependency_inset = if features.len() == 1 { 2 } else { 4 }; for (package_name, dependencies) in features { + let package_feature_count = dependencies.values().flatten().count(); + let mut package_checked_features_count = 0; + let mut package_offset_to_top = 1; + if document.is_workspace() { - writeln!(term, "{}", package_name)?; + term.clear_line()?; + writeln!(term, "")?; + writeln!( + term, + "{:package_inset$}{} [{}/{}]", + "", package_name, package_checked_features_count, package_feature_count + )?; + offset_to_top += 2; } for (dependency_name, features) in dependencies { - term.clear_line()?; - writeln!(term, "{:inset$}{} [0/0]", "", dependency_name)?; - let mut to_be_disabled = vec![]; for (id, feature) in features.iter().enumerate() { term.clear_line()?; - writeln!(term, "{:inset$} โ”” {}", "", feature)?; + writeln!( + term, + "{:dependency_inset$}{} [{}/{}]", + "", + dependency_name, + id + 1, + features.len() + )?; + term.clear_line()?; + writeln!(term, "{:dependency_inset$} โ”” {}", "", feature)?; + + term.move_cursor_up(2)?; document .get_package_mut(&package_name)? @@ -155,29 +192,41 @@ fn prune_features( save_dependency(document, &package_name, &dependency_name)?; - term.move_cursor_up(2)?; - term.clear_line()?; + checked_features_count += 1; + package_checked_features_count += 1; + + term.move_cursor_up(offset_to_top)?; writeln!( term, - "{:inset$}{} [{}/{}]", - "", - dependency_name, - id + 1, - features.len() + "workspace [{}/{}]", + checked_features_count, feature_count )?; + term.move_cursor_down(offset_to_top - 1)?; + + if document.is_workspace() { + term.move_cursor_up(package_offset_to_top)?; + writeln!( + term, + "{:package_inset$}{} [{}/{}]", + "", package_name, package_checked_features_count, package_feature_count + )?; + term.move_cursor_down(package_offset_to_top - 1)?; + } } + offset_to_top += 1; + package_offset_to_top += 1; + let mut disabled_count = style(to_be_disabled.len()); if to_be_disabled.is_empty().not() { disabled_count = disabled_count.red(); } - term.move_cursor_up(1)?; term.clear_line()?; writeln!( term, - "{:inset$}{} [{}/{}]", + "{:dependency_inset$}{} [{}/{}]", "", dependency_name, disabled_count, -- 2.51.2 From c5f0641487b0ae617632419fc25e23ae5ca8fd7b Mon Sep 17 00:00:00 2001 From: tobinio Date: Sat, 4 May 2024 15:55:17 +0200 Subject: [PATCH 4/6] skip features that are known to be unnecessary --- src/prune.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/src/prune.rs b/src/prune.rs index 56c9e85..2f9e21b 100644 --- a/src/prune.rs +++ b/src/prune.rs @@ -10,7 +10,9 @@ use crate::project::dependency::Dependency; use crate::project::document::Document; use crate::save::save_dependency; use crate::util::{get_item_from_doc, toml_document_from_path}; +use clap::builder::Str; use color_eyre::eyre::{eyre, ContextCompat}; +use itertools::Itertools; use std::process::{Command, Stdio}; pub fn prune(mut document: Document, is_dry_run: bool) -> Result<()> { @@ -136,7 +138,10 @@ fn prune_features( let package_inset = if features.len() == 1 { 0 } else { 2 }; let dependency_inset = if features.len() == 1 { 2 } else { 4 }; - for (package_name, dependencies) in features { + for (package_name, dependencies) in features + .into_iter() + .sorted_by(|(name_a, _), (name_b, _)| name_a.cmp(name_b)) + { let package_feature_count = dependencies.values().flatten().count(); let mut package_checked_features_count = 0; let mut package_offset_to_top = 1; @@ -152,7 +157,10 @@ fn prune_features( offset_to_top += 2; } - for (dependency_name, features) in dependencies { + for (dependency_name, features) in dependencies + .into_iter() + .sorted_by(|(name_a, _), (name_b, _)| name_a.cmp(name_b)) + { let mut to_be_disabled = vec![]; for (id, feature) in features.iter().enumerate() { @@ -162,7 +170,7 @@ fn prune_features( "{:dependency_inset$}{} [{}/{}]", "", dependency_name, - id + 1, + id, features.len() )?; term.clear_line()?; @@ -177,9 +185,14 @@ fn prune_features( save_dependency(document, &package_name, &dependency_name)?; - if check()? { - //todo disable parent features and dont test them - to_be_disabled.push(feature.to_string()); + if !to_be_disabled.contains(feature) && check()? { + set_features_to_be_disabled( + document + .get_package(&package_name)? + .get_dep(&dependency_name)?, + feature.to_string(), + &mut to_be_disabled, + ); } //reset to start @@ -217,7 +230,12 @@ fn prune_features( offset_to_top += 1; package_offset_to_top += 1; - let mut disabled_count = style(to_be_disabled.len()); + let mut disabled_count = style( + features + .iter() + .filter(|feature| to_be_disabled.contains(feature)) + .count(), + ); if to_be_disabled.is_empty().not() { disabled_count = disabled_count.red(); @@ -253,6 +271,30 @@ fn prune_features( Ok(()) } +fn set_features_to_be_disabled( + dependency: &Dependency, + feature: String, + to_be_disabled: &mut Vec, +) { + if to_be_disabled.contains(&feature) { + return; + } + + to_be_disabled.push(feature.clone()); + + dependency + .features + .iter() + .filter(|(_, data)| { + data.sub_features + .iter() + .any(|sub_feature| sub_feature.name == feature) + }) + .for_each(|(name, _)| { + set_features_to_be_disabled(dependency, name.to_string(), to_be_disabled); + }); +} + fn check() -> Result { if !build()? { return Ok(false); -- 2.51.2 From 13cfafce5db8c7890aaad4d2b243f592c350b07b Mon Sep 17 00:00:00 2001 From: tobinio Date: Sat, 4 May 2024 16:00:15 +0200 Subject: [PATCH 5/6] skip empty dependencies and packages while pruning --- src/prune.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/prune.rs b/src/prune.rs index 2f9e21b..bdc31b5 100644 --- a/src/prune.rs +++ b/src/prune.rs @@ -10,7 +10,6 @@ use crate::project::dependency::Dependency; use crate::project::document::Document; use crate::save::save_dependency; use crate::util::{get_item_from_doc, toml_document_from_path}; -use clap::builder::Str; use color_eyre::eyre::{eyre, ContextCompat}; use itertools::Itertools; use std::process::{Command, Stdio}; @@ -92,8 +91,6 @@ fn remove_ignored_features( } } - //todo remove empty packages / dependencies - Ok(()) } @@ -142,6 +139,10 @@ fn prune_features( .into_iter() .sorted_by(|(name_a, _), (name_b, _)| name_a.cmp(name_b)) { + if dependencies.is_empty() { + continue; + } + let package_feature_count = dependencies.values().flatten().count(); let mut package_checked_features_count = 0; let mut package_offset_to_top = 1; @@ -161,6 +162,10 @@ fn prune_features( .into_iter() .sorted_by(|(name_a, _), (name_b, _)| name_a.cmp(name_b)) { + if features.is_empty() { + continue; + } + let mut to_be_disabled = vec![]; for (id, feature) in features.iter().enumerate() { -- 2.51.2 From 2751adc51d9bdea5f3fe4cc5d7aaef41e348e58f Mon Sep 17 00:00:00 2001 From: tobinio Date: Sat, 4 May 2024 16:02:44 +0200 Subject: [PATCH 6/6] clippy & fmt --- src/prune.rs | 6 +++--- src/util.rs | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/prune.rs b/src/prune.rs index bdc31b5..c98a8e5 100644 --- a/src/prune.rs +++ b/src/prune.rs @@ -103,7 +103,7 @@ fn remove_feature(feature: &String, features: &mut Vec, dependency: &Dep features.remove(index); - if let Some(feature) = dependency.get_feature(&feature) { + if let Some(feature) = dependency.get_feature(feature) { for sub_feature in &feature.sub_features { remove_feature(&sub_feature.name, features, dependency); } @@ -149,7 +149,7 @@ fn prune_features( if document.is_workspace() { term.clear_line()?; - writeln!(term, "")?; + writeln!(term)?; writeln!( term, "{:package_inset$}{} [{}/{}]", @@ -345,7 +345,7 @@ fn get_ignored_features>( match result { Ok(document) => { - let item = get_item_from_doc(&item_path, &document); + let item = get_item_from_doc(item_path, &document); let Ok(item) = item else { return Ok(HashMap::new()); diff --git a/src/util.rs b/src/util.rs index 5031d7a..4920ba1 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,8 +1,8 @@ -use color_eyre::eyre::{bail, ContextCompat, eyre}; +use cargo_platform::Platform; +use color_eyre::eyre::{bail, eyre, ContextCompat}; use std::fs; use std::path::Path; use std::str::FromStr; -use cargo_platform::Platform; pub fn toml_document_from_path>( dir_path: P, @@ -13,7 +13,6 @@ pub fn toml_document_from_path>( Ok(file_content.parse()?) } - pub fn get_mut_item_from_doc<'a>( path: &str, document: &'a mut toml_edit::DocumentMut, @@ -96,4 +95,4 @@ pub fn get_item_from_doc<'a>( } Ok(item) -} \ No newline at end of file +} -- 2.51.2