diff --git a/module/move/willbe/src/action/features.rs b/module/move/willbe/src/action/features.rs new file mode 100644 index 0000000000..47b9e98f44 --- /dev/null +++ b/module/move/willbe/src/action/features.rs @@ -0,0 +1,97 @@ +mod private +{ + use crate::*; + + use std:: + { + collections::{ BTreeMap, HashMap }, + fmt + }; + + use _path::AbsolutePath; + use former::Former; + use error_tools::{ for_app::Context, Result }; + use workspace::Workspace; + + /// Options available for the .features command + #[ derive( Debug, Former ) ] + pub struct FeaturesOptions + { + manifest_dir : AbsolutePath, + with_features_deps : bool, + } + + /// Represents a report about features available in the package + #[ derive( Debug, Default ) ] + pub struct FeaturesReport + { + /// Flag to turn off/on displaying feature dependencies - "feature: [deps...]" + pub with_features_deps : bool, + + /// A key-value pair structure representing available features. + /// + /// Key: name of the package (useful for workspaces, where multiple packages can be found). + /// + /// Value: Another key-value pair representing a feature and its dependencies + pub inner : HashMap< String, BTreeMap< String, Vec< String > > >, + } + + impl fmt::Display for FeaturesReport + { + fn fmt( &self, f : &mut fmt::Formatter< '_ >) -> Result< (), fmt::Error > + { + self.inner.iter().try_for_each + ( | ( package, features ) | + { + writeln!(f, "Package {}:", package)?; + features.iter().try_for_each + ( | ( feature, dependencies ) | + { + let feature = match self.with_features_deps + { + false => format!( "\t{feature}" ), + true + => + { + let deps = dependencies.join( ", " ); + format!( "\t{feature}: [{deps}]" ) + } + }; + writeln!( f, "{feature}" ) + } + ) + } + ) + } + } + + /// List features + pub fn features( FeaturesOptions { manifest_dir, with_features_deps } : FeaturesOptions ) -> Result< FeaturesReport > + { + let workspace = Workspace::with_crate_dir( CrateDir::try_from( manifest_dir.clone() )? ).context( "Failed to find workspace" )?; + let packages = workspace.packages()?.into_iter().filter + ( | package | + package.manifest_path().as_str().starts_with( manifest_dir.as_ref().as_os_str().to_str().unwrap() ) + ).collect::< Vec< _ > >(); + let mut report = FeaturesReport + { + with_features_deps, + ..Default::default() + }; + packages.iter().for_each + ( | package | + { + let features = package.features(); + report.inner.insert(package.name().to_owned(), features.to_owned()); + } + ); + Ok( report ) + } +} + +crate::mod_interface! +{ + orphan use features; + orphan use FeaturesOptions; + orphan use FeaturesReport; +} diff --git a/module/move/willbe/src/action/mod.rs b/module/move/willbe/src/action/mod.rs index 03d817fc44..0c66bb41ce 100644 --- a/module/move/willbe/src/action/mod.rs +++ b/module/move/willbe/src/action/mod.rs @@ -22,4 +22,6 @@ crate::mod_interface! layer cicd_renew; /// Workspace new. layer workspace_renew; + /// List features. + layer features; } diff --git a/module/move/willbe/src/action/readme_modules_headers_renew.rs b/module/move/willbe/src/action/readme_modules_headers_renew.rs index e3acf34272..ca3299079e 100644 --- a/module/move/willbe/src/action/readme_modules_headers_renew.rs +++ b/module/move/willbe/src/action/readme_modules_headers_renew.rs @@ -73,7 +73,9 @@ mod private let repo_url = url::extract_repo_url( &self.repository_url ).and_then( | r | url::git_info_extract( &r ).ok() ).ok_or_else::< Error, _ >( || err!( "Fail to parse repository url" ) )?; let example = if let Some( name ) = find_example_file( self.module_path.as_path(), &self.module_name ) { + // qqq : for Bohdan : Hardcoded Strings, would be better to use `PathBuf` to avoid separator mismatch on Windows and Unix let p = name.strip_prefix( workspace_path ).unwrap().get( 1.. ).unwrap().replace( "\\","%2F" ); + let name = name.replace( "/", "\\" ); let name = name.split( "\\" ).last().unwrap().split( "." ).next().unwrap(); format!( " [![Open in Gitpod](https://raster.shields.io/static/v1?label=try&message=online&color=eee&logo=gitpod&logoColor=eee)](https://gitpod.io/#RUN_PATH=.,SAMPLE_FILE={p},RUN_POSTFIX=--example%20{}/https://github.com/{})", name, repo_url ) } diff --git a/module/move/willbe/src/command/features.rs b/module/move/willbe/src/command/features.rs new file mode 100644 index 0000000000..ad69897935 --- /dev/null +++ b/module/move/willbe/src/command/features.rs @@ -0,0 +1,40 @@ +mod private +{ + use crate::*; + + use action::features::FeaturesOptions; + use std::path::PathBuf; + use _path::AbsolutePath; + use wca::VerifiedCommand; + use wtools::error::Result; + + /// + /// List features of a package. + /// + + pub fn features( o : VerifiedCommand ) -> Result< () > + { + let path : PathBuf = o.args.get_owned( 0 ).unwrap_or_else( || "./".into() ); + let path = AbsolutePath::try_from( path )?; + let with_features_deps = o.props.get_owned( "with_features_deps" ).unwrap_or( false ); + let options = FeaturesOptions::former() + .manifest_dir( path ) + .with_features_deps( with_features_deps ) + .form(); + let report = action::features( options ); + match report + { + Ok(success) => println!("{success}"), + Err(failure) => eprintln!("{failure}"), + } + Ok( () ) + } + +} + +crate::mod_interface! +{ + /// List features. + orphan use features; +} + diff --git a/module/move/willbe/src/command/mod.rs b/module/move/willbe/src/command/mod.rs index d3d5e5950b..8e08457686 100644 --- a/module/move/willbe/src/command/mod.rs +++ b/module/move/willbe/src/command/mod.rs @@ -256,6 +256,22 @@ with_gitpod: If set to 1, a column with a link to Gitpod will be added. Clicking .long_hint( "For use this command you need to specify:\n\n[package]\nname = \"test_module\"\nrepository = \"https://github.com/Username/ProjectName/tree/master/module/test_module\"\n...\n[package.metadata]\nstability = \"stable\" (Optional)\ndiscord_url = \"https://discord.gg/1234567890\" (Optional)\n\nin module's Cargo.toml." ) .routine( command::readme_modules_headers_renew ) .end() + + .command( "features" ) + .hint( "Lists features of the package" ) + .long_hint( "Lists features of the package located in a folder.\nWill list either separate package features or features for every package of a workspace") + .subject() + .hint( "Provide path to the package that you want to check.\n\t The path should point to a directory that contains a `Cargo.toml` file." ) + .kind( Type::Path ) + .optional( true ) + .end() + .property("with_features_deps") + .hint( "Display dependencies of features of the package" ) + .kind( Type::Bool ) + .optional( true ) + .end() + .routine( command::features ) + .end() } } @@ -286,5 +302,6 @@ crate::mod_interface! layer main_header; /// Generate headers layer readme_modules_headers_renew; - + /// List features + layer features; } diff --git a/module/move/willbe/src/entity/features.rs b/module/move/willbe/src/entity/features.rs index 81c1452180..54f38b2d22 100644 --- a/module/move/willbe/src/entity/features.rs +++ b/module/move/willbe/src/entity/features.rs @@ -142,7 +142,7 @@ mod private estimate } - + } crate::mod_interface! diff --git a/module/move/willbe/src/entity/workspace.rs b/module/move/willbe/src/entity/workspace.rs index 26a586e6d9..b477aa3c97 100644 --- a/module/move/willbe/src/entity/workspace.rs +++ b/module/move/willbe/src/entity/workspace.rs @@ -108,7 +108,6 @@ mod private { &self.inner.features } - } /// A dependency of the main crate diff --git a/module/move/willbe/tests/asset/three_packages_with_features/Cargo.toml b/module/move/willbe/tests/asset/three_packages_with_features/Cargo.toml new file mode 100644 index 0000000000..49f36c395b --- /dev/null +++ b/module/move/willbe/tests/asset/three_packages_with_features/Cargo.toml @@ -0,0 +1,8 @@ +[workspace] +resolver = "2" +members = [ + "*", +] + +[workspace.metadata] +discord_url = "https://discord.gg/123456789" diff --git a/module/move/willbe/tests/asset/three_packages_with_features/b/Cargo.toml b/module/move/willbe/tests/asset/three_packages_with_features/b/Cargo.toml new file mode 100644 index 0000000000..b9c97a9443 --- /dev/null +++ b/module/move/willbe/tests/asset/three_packages_with_features/b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "_chain_of_packages_b" +version = "0.1.0" +edition = "2021" +repository = "https://github.com/Username/test/b" + +[package.metadata] +stability = "stable" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +_chain_of_packages_c = { path = "../c", optional = true } + +[features] +enabled = [] +default = ["boo"] +boo = ["_chain_of_packages_c"] diff --git a/module/move/willbe/tests/asset/three_packages_with_features/b/Readme.md b/module/move/willbe/tests/asset/three_packages_with_features/b/Readme.md new file mode 100644 index 0000000000..8c938fa512 --- /dev/null +++ b/module/move/willbe/tests/asset/three_packages_with_features/b/Readme.md @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/module/move/willbe/tests/asset/three_packages_with_features/b/src/lib.rs b/module/move/willbe/tests/asset/three_packages_with_features/b/src/lib.rs new file mode 100644 index 0000000000..e9b1860dae --- /dev/null +++ b/module/move/willbe/tests/asset/three_packages_with_features/b/src/lib.rs @@ -0,0 +1,17 @@ +pub fn add( left : usize, right : usize ) -> usize +{ + left + right +} + +#[ cfg( test ) ] +mod tests +{ + use super::*; + + #[ test ] + fn it_works() + { + let result = add( 2, 2 ); + assert_eq!( result, 4 ); + } +} diff --git a/module/move/willbe/tests/asset/three_packages_with_features/c/Cargo.toml b/module/move/willbe/tests/asset/three_packages_with_features/c/Cargo.toml new file mode 100644 index 0000000000..0bcd46b4e3 --- /dev/null +++ b/module/move/willbe/tests/asset/three_packages_with_features/c/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "_chain_of_packages_c" +version = "0.1.0" +edition = "2021" +repository = "https://github.com/Username/test/c" + +[package.metadata] +discord_url = "https://discord.gg/m3YfbXpUUY" +stability = "stable" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[features] +enabled = [] +default = ["foo"] +foo = [] \ No newline at end of file diff --git a/module/move/willbe/tests/asset/three_packages_with_features/c/Readme.md b/module/move/willbe/tests/asset/three_packages_with_features/c/Readme.md new file mode 100644 index 0000000000..8c938fa512 --- /dev/null +++ b/module/move/willbe/tests/asset/three_packages_with_features/c/Readme.md @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/module/move/willbe/tests/asset/three_packages_with_features/c/src/lib.rs b/module/move/willbe/tests/asset/three_packages_with_features/c/src/lib.rs new file mode 100644 index 0000000000..e9b1860dae --- /dev/null +++ b/module/move/willbe/tests/asset/three_packages_with_features/c/src/lib.rs @@ -0,0 +1,17 @@ +pub fn add( left : usize, right : usize ) -> usize +{ + left + right +} + +#[ cfg( test ) ] +mod tests +{ + use super::*; + + #[ test ] + fn it_works() + { + let result = add( 2, 2 ); + assert_eq!( result, 4 ); + } +} diff --git a/module/move/willbe/tests/asset/three_packages_with_features/d/Cargo.toml b/module/move/willbe/tests/asset/three_packages_with_features/d/Cargo.toml new file mode 100644 index 0000000000..a6e5f08b8f --- /dev/null +++ b/module/move/willbe/tests/asset/three_packages_with_features/d/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "_chain_of_packages_d" +version = "0.1.0" +edition = "2021" +repository = "https://github.com/Username/test/c" + +[package.metadata] +stability = "stable" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[features] +enabled = [] diff --git a/module/move/willbe/tests/asset/three_packages_with_features/d/Readme.md b/module/move/willbe/tests/asset/three_packages_with_features/d/Readme.md new file mode 100644 index 0000000000..8c938fa512 --- /dev/null +++ b/module/move/willbe/tests/asset/three_packages_with_features/d/Readme.md @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/module/move/willbe/tests/asset/three_packages_with_features/d/src/lib.rs b/module/move/willbe/tests/asset/three_packages_with_features/d/src/lib.rs new file mode 100644 index 0000000000..e9b1860dae --- /dev/null +++ b/module/move/willbe/tests/asset/three_packages_with_features/d/src/lib.rs @@ -0,0 +1,17 @@ +pub fn add( left : usize, right : usize ) -> usize +{ + left + right +} + +#[ cfg( test ) ] +mod tests +{ + use super::*; + + #[ test ] + fn it_works() + { + let result = add( 2, 2 ); + assert_eq!( result, 4 ); + } +} diff --git a/module/move/willbe/tests/inc/action/features.rs b/module/move/willbe/tests/inc/action/features.rs new file mode 100644 index 0000000000..c27e99c3b4 --- /dev/null +++ b/module/move/willbe/tests/inc/action/features.rs @@ -0,0 +1,183 @@ +use super::*; +use assert_fs::prelude::*; + +fn arrange( source : &str ) -> assert_fs::TempDir +{ + let root_path = std::path::Path::new( env!( "CARGO_MANIFEST_DIR" ) ); + let assets_relative_path = std::path::Path::new( ASSET_PATH ); + let assets_path = root_path.join( assets_relative_path ); + + let temp = assert_fs::TempDir::new().unwrap(); + temp.copy_from( assets_path.join( source ), &[ "**" ] ).unwrap(); + + temp +} + +#[ test ] +fn package_no_features() +{ + // Arrange + let temp = arrange( "three_packages/b" ); + let options = willbe::action::features::FeaturesOptions::former() + .manifest_dir( willbe::_path::AbsolutePath::try_from( temp.path().to_owned() ).unwrap() ) + .form(); + + // Act + let report = willbe::action::features( options ).unwrap().to_string(); + + // Assert + assert!( report.contains( +"\ +Package _chain_of_packages_b:\ +" ) ); +} + +#[ test ] +fn package_features() +{ + // Arrange + let temp = arrange( "three_packages_with_features/b" ); + let options = willbe::action::features::FeaturesOptions::former() + .manifest_dir( willbe::_path::AbsolutePath::try_from( temp.path().to_owned() ).unwrap() ) + .form(); + + // Act + let report = willbe::action::features( options ).unwrap().to_string(); + + // Assert + assert!( report.contains( +"\ +Package _chain_of_packages_b: +\t_chain_of_packages_c +\tboo +\tdefault +\tenabled\ +" ) ); +} + +#[ test ] +fn package_features_with_features_deps() +{ + let temp = arrange( "three_packages_with_features/b" ); + let options = willbe::action::features::FeaturesOptions::former() + .manifest_dir( willbe::_path::AbsolutePath::try_from( temp.path().to_owned() ).unwrap() ) + .with_features_deps( true ) + .form(); + + // Act + let report = willbe::action::features( options ).unwrap().to_string(); + + // Assert + assert!( report.contains( +"\ +Package _chain_of_packages_b: +\t_chain_of_packages_c: [dep:_chain_of_packages_c] +\tboo: [_chain_of_packages_c] +\tdefault: [boo] +\tenabled: []\ +" ) ); +} + +#[ test ] +fn workspace_no_features() +{ + // Arrange + let temp = arrange( "three_packages" ); + let options = willbe::action::features::FeaturesOptions::former() + .manifest_dir( willbe::_path::AbsolutePath::try_from( temp.path().to_owned() ).unwrap() ) + .form(); + + // Act + let report = willbe::action::features( options ).unwrap().to_string(); + + // Assert + assert!( report.contains( +"\ +Package _chain_of_packages_b:\ +" ) ); + + assert!( report.contains( +"\ +Package _chain_of_packages_c:\ +" ) ); + + assert!( report.contains( +"\ +Package _chain_of_packages_d:\ +" ) ); +} + +#[ test ] +fn workspace_features() +{ + // Arrange + let temp = arrange( "three_packages_with_features" ); + let options = willbe::action::features::FeaturesOptions::former() + .manifest_dir( willbe::_path::AbsolutePath::try_from( temp.path().to_owned() ).unwrap() ) + .form(); + + // Act + let report = willbe::action::features( options ).unwrap().to_string(); + + // Assert + assert!( report.contains( +"\ +Package _chain_of_packages_b: +\t_chain_of_packages_c +\tboo +\tdefault +\tenabled\ +" ) ); + + assert!( report.contains( +"\ +Package _chain_of_packages_c: +\tdefault +\tenabled +\tfoo\ +" ) ); + + assert!( report.contains( +"\ +Package _chain_of_packages_d: +\tenabled\ +" ) ); +} + +#[ test ] +fn workspace_features_with_features_deps() +{ + // Arrange + let temp = arrange( "three_packages_with_features" ); + let options = willbe::action::features::FeaturesOptions::former() + .manifest_dir( willbe::_path::AbsolutePath::try_from( temp.path().to_owned() ).unwrap() ) + .with_features_deps( true ) + .form(); + + // Act + let report = willbe::action::features( options ).unwrap().to_string(); + + // Assert + assert!( report.contains( +"\ +Package _chain_of_packages_b: +\t_chain_of_packages_c: [dep:_chain_of_packages_c] +\tboo: [_chain_of_packages_c] +\tdefault: [boo] +\tenabled: []\ +" ) ); + + assert!( report.contains( +"\ +Package _chain_of_packages_c: +\tdefault: [foo] +\tenabled: [] +\tfoo: []\ +" ) ); + + assert!( report.contains( +"\ +Package _chain_of_packages_d: +\tenabled: []\ +" ) ); +} diff --git a/module/move/willbe/tests/inc/action/mod.rs b/module/move/willbe/tests/inc/action/mod.rs index 66c800260f..f5f1b151f5 100644 --- a/module/move/willbe/tests/inc/action/mod.rs +++ b/module/move/willbe/tests/inc/action/mod.rs @@ -1,5 +1,6 @@ use super::*; +pub mod features; pub mod list; pub mod readme_health_table_renew; pub mod readme_modules_headers_renew; diff --git a/module/move/willbe/tests/inc/entity/features.rs b/module/move/willbe/tests/inc/entity/features.rs index dd4db5bff8..0eaf8e7c75 100644 --- a/module/move/willbe/tests/inc/entity/features.rs +++ b/module/move/willbe/tests/inc/entity/features.rs @@ -265,4 +265,4 @@ fn estimate() assert_eq!( estimate_with( 5, 2, false, true, &[], 0 ), 17 ); assert_eq!( estimate_with( 5, 2, false, false, &[ "feature1".to_string(), "feature2".to_string() ], 2 ), 20 ); assert_eq!( estimate_with( 5, 2, true, true, &[ "feature1".to_string(), "feature2".to_string() ], 2 ), 22 ); -} \ No newline at end of file +}