diff --git a/Cargo.toml b/Cargo.toml index 3d6db907de..f058e4f666 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -459,17 +459,17 @@ default-features = true ## test experimental [workspace.dependencies.test_experimental_a] -version = "~0.3.0" +version = "~0.5.0" path = "module/test/a" default-features = true [workspace.dependencies.test_experimental_b] -version = "~0.2.0" +version = "~0.3.0" path = "module/test/b" default-features = true [workspace.dependencies.test_experimental_c] -version = "~0.2.0" +version = "~0.3.0" path = "module/test/c" default-features = true diff --git a/module/move/wca/src/ca/grammar/types.rs b/module/move/wca/src/ca/grammar/types.rs index 6a6ac6fe27..16126e0f03 100644 --- a/module/move/wca/src/ca/grammar/types.rs +++ b/module/move/wca/src/ca/grammar/types.rs @@ -1,8 +1,14 @@ pub( crate ) mod private { use crate::*; + use std::fmt:: + { + Display, + Formatter + }; use wtools; use wtools::{ error::Result, err }; + use wtools::Itertools; /// Available types that can be converted to a `Value` /// @@ -87,6 +93,38 @@ pub( crate ) mod private List( Vec< Value > ), } + impl Display for Value + { + fn fmt( &self, f : &mut Formatter< '_ >) -> std::fmt::Result + { + match self + { + Value::String( s ) => + { + write!( f , "{s}" )?; + } + Value::Number( n ) => + { + write!( f, "{n}" )?; + } + Value::Path( p ) => + { + write!( f, "{}", p.display() )?; + } + Value::Bool( b ) => + { + write!( f, "{b}" )?; + } + Value::List( list ) => + { + let list = list.iter().map( | element | element.to_string() ).join( "," ); // qqq : don't hardcode ", " find way to get original separator + write!( f, "{list}" )?; + } + } + Ok( () ) + } + } + macro_rules! value_into_impl { ( $( $value_kind : path => $( $kind : ty => $cast : expr ),+ );+ ) => diff --git a/module/move/wca/tests/inc/commands_aggregator/basic.rs b/module/move/wca/tests/inc/commands_aggregator/basic.rs index 6683eefd2f..2a47a7f4d4 100644 --- a/module/move/wca/tests/inc/commands_aggregator/basic.rs +++ b/module/move/wca/tests/inc/commands_aggregator/basic.rs @@ -242,6 +242,29 @@ tests_impls! a_id!( (), executor.command( dictionary, grammar_command ).unwrap() ); } + + // qqq : make the following test work + // fn subject_with_spaces() + // { + // let query = "SELECT title, links, MIN( published ) FROM Frames"; + // let ca = CommandsAggregator::former() + // .grammar( + // [ + // wca::Command::former() + // .hint( "hint" ) + // .long_hint( "long_hint" ) + // .phrase( "query.execute" ) + // .subject( "SQL query", Type::String, false ) + // .form(), + // ]) + // .executor( + // [ + // ( "query.execute".to_owned(), Routine::new( move |( args, _ )| { assert_eq!( query, args.get_owned::< &str >( 0 ).unwrap() ); Ok( () ) } ) ), + // ]) + // .build(); + + // a_id!( (), ca.perform( vec![ ".query.execute".to_string(), query.into() ] ).unwrap() ); + // } } // @@ -257,4 +280,5 @@ tests_index! string_subject_with_colon, no_prop_subject_with_colon, optional_prop_subject_with_colon, + // subject_with_spaces, } diff --git a/module/move/wca/tests/inc/parser/command.rs b/module/move/wca/tests/inc/parser/command.rs index ec51a8afd9..35929c07ce 100644 --- a/module/move/wca/tests/inc/parser/command.rs +++ b/module/move/wca/tests/inc/parser/command.rs @@ -146,6 +146,56 @@ tests_impls! ); } + // qqq : the parser must be able to accept a list of arguments(std::env::args()) + // fn with_spaces_in_value() + // { + // let parser = Parser::former().form(); + + // a_id! + // ( + // ParsedCommand + // { + // name : "command".into(), + // subjects : vec![ "value with spaces".into() ], + // properties : HashMap::new(), + // }, + // parser.command( vec![ ".command".to_string(), "value with spaces".into() ] ).unwrap() + // ); + + // a_id! + // ( + // ParsedCommand + // { + // name : "command".into(), + // subjects : vec![], + // properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), + // }, + // parser.command( vec![ ".command".to_string(), "prop:value with spaces".into() ] ).unwrap() + // ); + + // a_id! + // ( + // ParsedCommand + // { + // name : "command".into(), + // subjects : vec![], + // properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), + // }, + // parser.command( vec![ ".command".to_string(), "prop:".into(), "value with spaces".into() ] ).unwrap() + // ); + + // a_id! + // ( + // ParsedCommand + // { + // name : "command".into(), + // subjects : vec![], + // properties : HashMap::from_iter([ ( "prop".into(), "value with spaces".into() ) ]), + // }, + // parser.command( vec![ ".command".to_string(), "prop".into(), ":".into(), "value with spaces".into() ] ).unwrap() + // ); + // } + fn not_only_alphanumeric_symbols() { let parser = Parser::former().form(); @@ -387,6 +437,7 @@ tests_index! { basic, with_spaces, + // with_spaces_in_value, not_only_alphanumeric_symbols, same_command_and_prop_delimeter, path_in_subject, diff --git a/module/move/willbe/src/action/publish.rs b/module/move/willbe/src/action/publish.rs index 9eb9162858..94a6e940c6 100644 --- a/module/move/willbe/src/action/publish.rs +++ b/module/move/willbe/src/action/publish.rs @@ -20,6 +20,7 @@ mod private pub workspace_root_dir : Option< AbsolutePath >, /// Represents a collection of packages that are roots of the trees. pub wanted_to_publish : Vec< CrateDir >, + pub plan : Option< package::PublishPlan >, /// Represents a collection of packages and their associated publishing reports. pub packages : Vec<( AbsolutePath, package::PublishReport )> } @@ -30,62 +31,19 @@ mod private { if self.packages.is_empty() { - f.write_fmt( format_args!( "Nothing to publish" ) )?; + write!( f, "Nothing to publish" )?; return Ok( () ); } - write!( f, "Tree(-s):\n" )?; - let name_bump_report = self - .packages - .iter() - .filter_map( |( _, r )| r.bump.as_ref() ) - .map( | b | &b.base ) - .filter_map( | b | b.name.as_ref().and_then( | name | b.old_version.as_ref().and_then( | old | b.new_version.as_ref().map( | new | ( name, ( old, new ) ) ) ) ) ) - .collect::< HashMap< _, _ > >(); - for wanted in &self.wanted_to_publish + if let Some( plan ) = &self.plan { - let list = action::list - ( - action::list::ListOptions::former() - .path_to_manifest( wanted.clone() ) - .format( action::list::ListFormat::Tree ) - .dependency_sources([ action::list::DependencySource::Local ]) - .dependency_categories([ action::list::DependencyCategory::Primary ]) - .form() - ) - .map_err( |( _, _e )| std::fmt::Error )?; - let action::list::ListReport::Tree( list ) = list else { unreachable!() }; - - fn callback( name_bump_report : &HashMap< &String, ( &String, &String) >, mut r : action::list::ListNodeReport ) -> action::list::ListNodeReport - { - if let Some(( old, new )) = name_bump_report.get( &r.name ) - { - r.version = Some( format!( "({old} -> {new})" ) ); - } - r.normal_dependencies = r.normal_dependencies.into_iter().map( | r | callback( name_bump_report, r ) ).collect(); - r.dev_dependencies = r.dev_dependencies.into_iter().map( | r | callback( name_bump_report, r ) ).collect(); - r.build_dependencies = r.build_dependencies.into_iter().map( | r | callback( name_bump_report, r ) ).collect(); - - r - } - let list = list.into_iter().map( | r | callback( &name_bump_report, r ) ).collect(); + write!( f, "Tree{} :\n", if self.wanted_to_publish.len() > 1 { "s" } else { "" } )?; + plan.display_as_tree( f, &self.wanted_to_publish )?; - let list = action::list::ListReport::Tree( list ); - write!( f, "{}\n", list )?; - } - writeln!( f, "The following packages are pending for publication :" )?; - for ( idx, package ) in self.packages.iter().map( |( _, p )| p ).enumerate() - { - if let Some( bump ) = &package.bump - { - match ( &bump.base.name, &bump.base.old_version, &bump.base.new_version ) - { - ( Some( name ), Some( old ), Some( new ) ) => writeln!( f, "[{idx}] {name} ({old} -> {new})" )?, - _ => {} - } - } + writeln!( f, "The following packages are pending for publication :" )?; + plan.display_as_list( f )?; } - write!( f, "\nActions :\n" )?; + writeln!( f, "\nActions :" )?; for ( path, report ) in &self.packages { let report = report.to_string().replace("\n", "\n "); @@ -98,7 +56,7 @@ mod private { path.as_ref() }; - f.write_fmt( format_args!( "Publishing crate by `{}` path\n {report}\n", path.display() ) )?; + write!( f, "Publishing crate by `{}` path\n {report}", path.display() )?; } Ok( () ) @@ -135,14 +93,12 @@ mod private Workspace::with_crate_dir( dir ).err_with( || report.clone() )? }; - report.workspace_root_dir = Some - ( - metadata - .workspace_root() - .err_with( || report.clone() )? - .try_into() - .err_with( || report.clone() )? - ); + let workspace_root_dir : AbsolutePath = metadata + .workspace_root() + .err_with( || report.clone() )? + .try_into() + .err_with( || report.clone() )?; + report.workspace_root_dir = Some( workspace_root_dir.clone() ); let packages = metadata.load().err_with( || report.clone() )?.packages().err_with( || report.clone() )?; let packages_to_publish : Vec< _ > = packages .iter() @@ -184,26 +140,19 @@ mod private let subgraph = graph::remove_not_required_to_publish( &package_map, &tmp, &packages_to_publish, dir.clone() ); let subgraph = subgraph.map( | _, n | n, | _, e | e ); - let queue = graph::toposort( subgraph ).unwrap().into_iter().map( | n | package_map.get( &n ).unwrap() ).collect::< Vec< _ > >(); + let queue = graph::toposort( subgraph ).unwrap().into_iter().map( | n | package_map.get( &n ).unwrap() ).cloned().collect::< Vec< _ > >(); - for package in queue + let plan = package::PublishPlan::former() + .workspace_dir( CrateDir::try_from( workspace_root_dir ).unwrap() ) + .option_base_temp_dir( dir.clone() ) + .dry( dry ) + .packages( queue ) + .form(); + report.plan = Some( plan.clone() ); + for package_report in package::perform_packages_publish( plan ).err_with( || report.clone() )? { - let args = package::PublishSingleOptions::former() - .package( package ) - .force( true ) - .option_base_temp_dir( &dir ) - .dry( dry ) - .form(); - let current_report = package::publish_single( args ) - .map_err - ( - | ( current_report, e ) | - { - report.packages.push(( package.crate_dir().absolute_path(), current_report.clone() )); - ( report.clone(), e.context( "Publish list of packages" ) ) - } - )?; - report.packages.push(( package.crate_dir().absolute_path(), current_report )); + let path : &std::path::Path = package_report.get_info.as_ref().unwrap().current_path.as_ref(); + report.packages.push(( AbsolutePath::try_from( path ).unwrap(), package_report )); } if temp diff --git a/module/move/willbe/src/action/publish_diff.rs b/module/move/willbe/src/action/publish_diff.rs index d8f648aba7..06835dce1d 100644 --- a/module/move/willbe/src/action/publish_diff.rs +++ b/module/move/willbe/src/action/publish_diff.rs @@ -10,11 +10,19 @@ mod private use wtools::error::for_app::Result; use diff::{ DiffReport, crate_diff }; + /// Options for `publish_diff` command + #[ derive( Debug, former::Former ) ] + pub struct PublishDiffOptions + { + path : PathBuf, + keep_archive : Option< PathBuf >, + } + /// Return the differences between a local and remote package versions. #[ cfg_attr( feature = "tracing", tracing::instrument ) ] - pub fn publish_diff( path : PathBuf ) -> Result< DiffReport > + pub fn publish_diff( o : PublishDiffOptions ) -> Result< DiffReport > { - let path = AbsolutePath::try_from( path )?; + let path = AbsolutePath::try_from( o.path )?; let dir = CrateDir::try_from( path )?; let package = package::Package::try_from( dir.clone() )?; @@ -25,6 +33,21 @@ mod private let l = CrateArchive::read( packed_crate::local_path( name, version, dir )? )?; let r = CrateArchive::download_crates_io( name, version ).unwrap(); + if let Some( out_path ) = o.keep_archive + { + _ = std::fs::create_dir_all( &out_path ); + for path in r.list() + { + let local_path = out_path.join( path ); + let folder = local_path.parent().unwrap(); + _ = std::fs::create_dir_all( folder ); + + let content = r.content_bytes( path ).unwrap(); + + std::fs::write( local_path, content )?; + } + } + Ok( crate_diff( &l, &r ) ) } } @@ -33,6 +56,7 @@ mod private crate::mod_interface! { + orphan use PublishDiffOptions; /// Publishes the difference between the local and published versions of a package. orphan use publish_diff; } diff --git a/module/move/willbe/src/command/mod.rs b/module/move/willbe/src/command/mod.rs index 384d1540f4..7cd16ae169 100644 --- a/module/move/willbe/src/command/mod.rs +++ b/module/move/willbe/src/command/mod.rs @@ -42,6 +42,11 @@ pub( crate ) mod private .kind( Type::Path ) .optional( true ) .end() + .property( "keep_archive" ) + .hint( "Save remote package version to the specified path" ) + .kind( Type::Path ) + .optional( true ) + .end() .routine( command::publish_diff ) .end() diff --git a/module/move/willbe/src/command/publish.rs b/module/move/willbe/src/command/publish.rs index ebc1f2b0e5..68f83df6bf 100644 --- a/module/move/willbe/src/command/publish.rs +++ b/module/move/willbe/src/command/publish.rs @@ -13,6 +13,8 @@ mod private pub fn publish( args : Args, properties : Props ) -> Result< () > { + let args_line = format!( "{}", args.get_owned( 0 ).unwrap_or( std::path::PathBuf::from( "" ) ).display() ); + let prop_line = format!( "{}", properties.iter().map( | p | format!( "{}:{}", p.0, p.1.to_string() ) ).collect::< Vec< _ > >().join(" ") ); let patterns : Vec< _ > = args.get_owned( 0 ).unwrap_or_else( || vec![ "./".into() ] ); let dry : bool = properties @@ -31,7 +33,7 @@ mod private if dry && report.packages.iter().find( |( _, p )| p.publish_required ).is_some() { - println!( "To apply plan, call the command `will .publish dry:0`" ) + println!( "To apply plan, call the command `will .publish {} dry:0 {}`", args_line, prop_line ) // qqq : for Petro : for Bohdan : bad. should be exact command with exact parameters } diff --git a/module/move/willbe/src/command/publish_diff.rs b/module/move/willbe/src/command/publish_diff.rs index baedd83605..85f0c29dc8 100644 --- a/module/move/willbe/src/command/publish_diff.rs +++ b/module/move/willbe/src/command/publish_diff.rs @@ -3,9 +3,16 @@ mod private use crate::*; use std::path::PathBuf; - use wca::Args; + use wca::{ Args, Props }; use wtools::error::Result; + use _path::AbsolutePath; + + #[ derive( former::Former ) ] + struct PublishDiffProperties + { + keep_archive : Option< PathBuf >, + } /// Command to display the differences between a local and remote package versions. /// @@ -20,14 +27,38 @@ mod private /// # Errors /// /// Returns an error if there is an issue with the command. - pub fn publish_diff( args : Args ) -> Result< () > + pub fn publish_diff( args : Args, props : Props ) -> Result< () > { let path : PathBuf = args.get_owned( 0 ).unwrap_or( std::env::current_dir()? ); + let PublishDiffProperties { keep_archive } = props.try_into()?; - println!( "{}", action::publish_diff( path )? ); + let mut o = action::PublishDiffOptions::former() + .path( path ); + if let Some( k ) = keep_archive.clone() { o = o.keep_archive( k ); } + let o = o.form(); + + println!( "{}", action::publish_diff( o )? ); + if let Some( keep ) = keep_archive + { + let keep = AbsolutePath::try_from( keep ).unwrap(); + println!( "Remote version of the package was saved at `{}`", keep.as_ref().display() ); + } Ok( () ) } + + impl TryFrom< Props > for PublishDiffProperties + { + type Error = wtools::error::for_app::Error; + fn try_from( value : Props ) -> Result< Self, Self::Error > + { + let mut this = Self::former(); + + this = if let Some( v ) = value.get_owned( "keep_archive" ) { this.keep_archive::< PathBuf >( v ) } else { this }; + + Ok( this.form() ) + } + } } // diff --git a/module/move/willbe/src/command/test.rs b/module/move/willbe/src/command/test.rs index 9f418ed2a9..8146bc8ab2 100644 --- a/module/move/willbe/src/command/test.rs +++ b/module/move/willbe/src/command/test.rs @@ -9,7 +9,6 @@ mod private { Args, Props, - Value, }; use wtools::error::Result; use _path::AbsolutePath; @@ -17,46 +16,8 @@ mod private use former::Former; use channel::Channel; use error_tools::for_app::bail; - use iter_tools::Itertools; use optimization::Optimization; - - trait ToString - { - fn to_string( &self ) -> String; - } - - impl ToString for Value - { - fn to_string( &self ) -> String - { - match self - { - Value::String( s ) => - { - format!( "{s}" ) - } - Value::Number( n ) => - { - format!( "{n}" ) - } - Value::Path( p ) => - { - format!( "{}", p.display() ) - } - Value::Bool( b ) => - { - format!( "{b}" ) - } - Value::List( list ) => - { - let list = list.iter().map( | element | element.to_string() ).join( ", "); // qqq : don't hardcode ", " find way to get original separator - format!( "{list}" ) - } - } - } - } - #[ derive( Former, Debug ) ] struct TestsProperties { @@ -91,7 +52,7 @@ mod private /// run tests in specified crate pub fn test( args : Args, properties : Props ) -> Result< () > { - let args_line = format!( "{}", args.get_owned( 0 ).unwrap_or( "" ) ); + let args_line = format!( "{}", args.get_owned( 0 ).unwrap_or( std::path::PathBuf::from( "" ) ).display() ); let prop_line = format!( "{}", properties.iter().map( | p | format!( "{}:{}", p.0, p.1.to_string() ) ).collect::< Vec< _ > >().join(" ") ); let path : PathBuf = args.get_owned( 0 ).unwrap_or_else( || "./".into() ); let path = AbsolutePath::try_from( path )?; diff --git a/module/move/willbe/src/entity/manifest.rs b/module/move/willbe/src/entity/manifest.rs index 72ef5df144..619a5bab8c 100644 --- a/module/move/willbe/src/entity/manifest.rs +++ b/module/move/willbe/src/entity/manifest.rs @@ -39,7 +39,7 @@ pub( crate ) mod private impl TryFrom< AbsolutePath > for CrateDir { - // qqq : make better errors + // aaa : make better errors // aaa : use `CrateDirError` for it type Error = CrateDirError; @@ -100,7 +100,7 @@ pub( crate ) mod private impl TryFrom< AbsolutePath > for Manifest { - // qqq : make better errors + // aaa : make better errors // aaa : return `ManifestError` type Error = ManifestError; @@ -159,7 +159,7 @@ pub( crate ) mod private Ok( () ) } - // qqq : for Bohdan : don't abuse anyhow + // aaa : for Bohdan : don't abuse anyhow // aaa : return `io` error /// Store manifest. pub fn store( &self ) -> io::Result< () > @@ -200,7 +200,7 @@ pub( crate ) mod private } /// Create and load manifest by specified path - // qqq : for Bohdan : use newtype, add proper errors handing + // aaa : for Bohdan : use newtype, add proper errors handing // aaa : return `ManifestError` pub fn open( path : AbsolutePath ) -> Result< Manifest, ManifestError > { diff --git a/module/move/willbe/src/entity/package.rs b/module/move/willbe/src/entity/package.rs index b9794b4d82..f64dfcb800 100644 --- a/module/move/willbe/src/entity/package.rs +++ b/module/move/willbe/src/entity/package.rs @@ -11,7 +11,6 @@ mod private use std::hash::Hash; use std::path::PathBuf; use cargo_metadata::{ Dependency, DependencyKind }; - use toml_edit::value; use process_tools::process; use manifest::{ Manifest, ManifestError }; @@ -19,7 +18,6 @@ mod private use workspace::Workspace; use _path::AbsolutePath; - use version::BumpReport; use wtools:: { @@ -29,7 +27,7 @@ mod private thiserror, Result, for_lib::Error, - for_app::{ format_err, Error as wError, Context }, + for_app::{ format_err, Context }, } }; use action::readme_health_table_renew::Stability; @@ -37,7 +35,7 @@ mod private use workspace::WorkspacePackage; /// - #[ derive( Debug ) ] + #[ derive( Debug, Clone ) ] pub enum Package { /// `Cargo.toml` file. @@ -72,7 +70,7 @@ mod private impl TryFrom< AbsolutePath > for Package { - // qqq : make better errors + // aaa : make better errors // aaa : return `PackageError` instead of `anohow` message type Error = PackageError; @@ -106,7 +104,7 @@ mod private impl TryFrom< Manifest > for Package { - // qqq : make better errors + // aaa : make better errors // aaa : return `PackageError` instead of `anohow` message type Error = PackageError; @@ -201,16 +199,16 @@ mod private match self { Self::Manifest( manifest ) => - { - let data = manifest.manifest_data.as_ref().ok_or_else( || PackageError::Manifest( ManifestError::EmptyManifestData ) )?; + { + let data = manifest.manifest_data.as_ref().ok_or_else( || PackageError::Manifest( ManifestError::EmptyManifestData ) )?; - // Unwrap safely because of the `Package` type guarantee - Ok( data[ "package" ].get( "metadata" ).and_then( | m | m.get( "stability" ) ).and_then( | s | s.as_str() ).and_then( | s | s.parse::< Stability >().ok() ).unwrap_or( Stability::Experimental) ) - } + // Unwrap safely because of the `Package` type guarantee + Ok( data[ "package" ].get( "metadata" ).and_then( | m | m.get( "stability" ) ).and_then( | s | s.as_str() ).and_then( | s | s.parse::< Stability >().ok() ).unwrap_or( Stability::Experimental) ) + } Self::Metadata( metadata ) => - { - Ok( metadata.metadata()["stability"].as_str().and_then( | s | s.parse::< Stability >().ok() ).unwrap_or( Stability::Experimental) ) - } + { + Ok( metadata.metadata()[ "stability" ].as_str().and_then( | s | s.parse::< Stability >().ok() ).unwrap_or( Stability::Experimental) ) + } } } @@ -220,16 +218,16 @@ mod private match self { Self::Manifest( manifest ) => - { - let data = manifest.manifest_data.as_ref().ok_or_else( || PackageError::Manifest( ManifestError::EmptyManifestData ) )?; + { + let data = manifest.manifest_data.as_ref().ok_or_else( || PackageError::Manifest( ManifestError::EmptyManifestData ) )?; - // Unwrap safely because of the `Package` type guarantee - Ok( data[ "package" ].get( "repository" ).and_then( | r | r.as_str() ).map( | r | r.to_string()) ) - } + // Unwrap safely because of the `Package` type guarantee + Ok( data[ "package" ].get( "repository" ).and_then( | r | r.as_str() ).map( | r | r.to_string()) ) + } Self::Metadata( metadata ) => - { - Ok( metadata.repository().cloned() ) - } + { + Ok( metadata.repository().cloned() ) + } } } @@ -239,15 +237,15 @@ mod private match self { Self::Manifest( manifest ) => - { - let data = manifest.manifest_data.as_ref().ok_or_else( || PackageError::Manifest( ManifestError::EmptyManifestData ) )?; + { + let data = manifest.manifest_data.as_ref().ok_or_else( || PackageError::Manifest( ManifestError::EmptyManifestData ) )?; - Ok( data[ "package" ].get( "metadata" ).and_then( | m | m.get( "discord_url" ) ).and_then( | url | url.as_str() ).map( | r | r.to_string() ) ) - } + Ok( data[ "package" ].get( "metadata" ).and_then( | m | m.get( "discord_url" ) ).and_then( | url | url.as_str() ).map( | r | r.to_string() ) ) + } Self::Metadata( metadata ) => - { - Ok( metadata.metadata()[ "discord_url" ].as_str().map( | url | url.to_string() ) ) - } + { + Ok( metadata.metadata()[ "discord_url" ].as_str().map( | url | url.to_string() ) ) + } } } @@ -276,8 +274,9 @@ mod private Package::Manifest( manifest ) => Ok( manifest.clone() ), Package::Metadata( metadata ) => manifest::open ( - AbsolutePath::try_from( metadata.manifest_path() ).map_err( | _ | PackageError::LocalPath )? ) - .map_err( | _ | PackageError::Metadata ), + AbsolutePath::try_from( metadata.manifest_path() ).map_err( | _ | PackageError::LocalPath )? + ) + .map_err( | _ | PackageError::Metadata ), } } @@ -295,6 +294,339 @@ mod private } } + #[ derive( Debug, Default, Clone ) ] + pub struct ExtendedGitReport + { + pub add : Option< process::Report >, + pub commit : Option< process::Report >, + pub push : Option< process::Report >, + } + + #[ derive( Debug, Clone ) ] + pub struct GitThingsOptions + { + pub git_root : AbsolutePath, + pub items : Vec< AbsolutePath >, + pub message : String, + pub dry : bool, + } + + fn perform_git_operations( o : GitThingsOptions ) -> Result< ExtendedGitReport > + { + let mut report = ExtendedGitReport::default(); + if o.items.is_empty() { return Ok( report ); } + let items = o + .items + .iter() + .map + ( + | item | item.as_ref().strip_prefix( o.git_root.as_ref() ).map( Path::to_string_lossy ) + .with_context( || format!("git_root: {}, item: {}", o.git_root.as_ref().display(), item.as_ref().display() ) ) + ) + .collect::< Result< Vec< _ > > >()?; + let res = git::add( &o.git_root, &items, o.dry ).map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?; + report.add = Some( res ); + let res = git::commit( &o.git_root, &o.message, o.dry ).map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?; + report.commit = Some( res ); + let res = git::push( &o.git_root, o.dry ).map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?; + report.push = Some( res ); + + Ok( report ) + } + + #[ derive( Debug, Clone ) ] + pub struct PackagePublishInstruction + { + pub package_name : String, + pub pack : cargo::PackOptions, + pub version_bump : version::BumpOptions, + // qqq : rename + pub git_things : GitThingsOptions, + pub publish : cargo::PublishOptions, + pub dry : bool, + } + + /// Represents a planner for publishing a single package. + #[ derive( Debug, Former ) ] + #[ perform( fn build() -> PackagePublishInstruction ) ] + pub struct PublishSinglePackagePlanner + { + workspace_dir : CrateDir, + package : Package, + base_temp_dir : Option< PathBuf >, + #[ default( true ) ] + dry : bool, + } + + impl PublishSinglePackagePlanner + { + fn build( self ) -> PackagePublishInstruction + { + let crate_dir = self.package.crate_dir(); + let workspace_root : AbsolutePath = self.workspace_dir.absolute_path(); + let pack = cargo::PackOptions + { + path : crate_dir.as_ref().into(), + temp_path : self.base_temp_dir.clone(), + dry : self.dry, + }; + let old_version : version::Version = self.package.version().as_ref().unwrap().try_into().unwrap(); + let new_version = old_version.clone().bump(); + // bump the package version in dependents (so far, only workspace) + let dependencies = vec![ CrateDir::try_from( workspace_root.clone() ).unwrap() ]; + let version_bump = version::BumpOptions + { + crate_dir : crate_dir.clone(), + old_version : old_version.clone(), + new_version : new_version.clone(), + dependencies : dependencies.clone(), + dry : self.dry, + }; + let git_things = GitThingsOptions + { + git_root : workspace_root, + items : dependencies.iter().chain([ &crate_dir ]).map( | d | d.absolute_path().join( "Cargo.toml" ) ).collect(), + message : format!( "{}-v{}", self.package.name().unwrap(), new_version ), + dry : self.dry, + }; + let publish = cargo::PublishOptions + { + path : crate_dir.as_ref().into(), + temp_path : self.base_temp_dir.clone(), + dry : self.dry, + }; + + PackagePublishInstruction + { + package_name : self.package.name().unwrap(), + pack, + version_bump, + git_things, + publish, + dry : self.dry, + } + } + } + + /// Performs package publishing based on the given arguments. + /// + /// # Arguments + /// + /// * `args` - The package publishing instructions. + /// + /// # Returns + /// + /// * `Result` - The result of the publishing operation, including information about the publish, version bump, and git operations. + pub fn perform_package_publish( instruction : PackagePublishInstruction ) -> Result< PublishReport > + { + let mut report = PublishReport::default(); + let PackagePublishInstruction + { + package_name: _, + mut pack, + mut version_bump, + mut git_things, + mut publish, + dry, + } = instruction; + pack.dry = dry; + version_bump.dry = dry; + git_things.dry = dry; + publish.dry = dry; + + report.get_info = Some( cargo::pack( pack ).map_err( | e | format_err!( "{report}\n{e:#?}" ) )? ); + // qqq : redundant field? + report.publish_required = true; + report.bump = Some( version::version_bump( version_bump ).map_err( | e | format_err!( "{report}\n{e:#?}" ) )? ); + let git = perform_git_operations( git_things ).map_err( |e | format_err!( "{report}\n{e:#?}" ) )?; + report.add = git.add; + report.commit = git.commit; + report.push = git.push; + report.publish = Some( cargo::publish( publish ).map_err( | e | format_err!( "{report}\n{e:#?}" ) )? ); + + Ok( report ) + } + + /// `PublishPlan` manages the overall publication process for multiple packages. + /// It organizes the necessary details required for publishing each individual package. + /// This includes the workspace root directory, any temporary directories used during the process, + /// and the set of specific instructions for publishing each package. + #[ derive( Debug, Former, Clone ) ] + pub struct PublishPlan + { + /// `workspace_dir` - This is the root directory of your workspace, containing all the Rust crates + /// that make up your package. It is used to locate the packages within your workspace that are meant + /// to be published. The value here is represented by `CrateDir` which indicates the directory of the crate. + pub workspace_dir : CrateDir, + + /// `base_temp_dir` - This is used for any temporary operations during the publication process, like + /// building the package or any other processes that might require the storage of transient data. It's + /// optional as not all operations will require temporary storage. The type used is `PathBuf` which allows + /// manipulation of the filesystem paths. + pub base_temp_dir : Option< PathBuf >, + + /// `dry` - A boolean value indicating whether to do a dry run. If set to `true`, the application performs + /// a simulated run without making any actual changes. If set to `false`, the operations are actually executed. + /// This property is optional and defaults to `true`. + #[ default( true ) ] + pub dry : bool, + + /// `plans` - This is a vector containing the instructions for publishing each package. Each item + /// in the `plans` vector indicates a `PackagePublishInstruction` set for a single package. It outlines + /// how to build and where to publish the package amongst other instructions. The `#[setter( false )]` + /// attribute indicates that there is no setter method for the `plans` variable and it can only be modified + /// within the struct. + #[ setter( false ) ] + pub plans : Vec< PackagePublishInstruction >, + } + + impl PublishPlan + { + /// Displays a tree-like structure of crates and their dependencies. + /// + /// # Arguments + /// + /// * `f` - A mutable reference to a `Formatter` used for writing the output. + /// * `roots` - A slice of `CrateDir` representing the root crates to display. + /// + /// # Errors + /// + /// Returns a `std::fmt::Error` if there is an error writing to the formatter. + pub fn display_as_tree( &self, f : &mut Formatter< '_ >, roots : &[ CrateDir ] ) -> std::fmt::Result + { + let name_bump_report = self + .plans + .iter() + .map( | x | ( &x.package_name, ( x.version_bump.old_version.to_string(), x.version_bump.new_version.to_string() ) ) ) + .collect::< HashMap< _, _ > >(); + for wanted in roots + { + let list = action::list + ( + action::list::ListOptions::former() + .path_to_manifest( wanted.clone() ) + .format( action::list::ListFormat::Tree ) + .dependency_sources([ action::list::DependencySource::Local ]) + .dependency_categories([ action::list::DependencyCategory::Primary ]) + .form() + ) + .map_err( |( _, _e )| std::fmt::Error )?; + let action::list::ListReport::Tree( list ) = list else { unreachable!() }; + + fn callback( name_bump_report : &HashMap< &String, ( String, String ) >, mut r : action::list::ListNodeReport ) -> action::list::ListNodeReport + { + if let Some(( old, new )) = name_bump_report.get( &r.name ) + { + r.version = Some( format!( "({old} -> {new})" ) ); + } + r.normal_dependencies = r.normal_dependencies.into_iter().map( | r | callback( name_bump_report, r ) ).collect(); + r.dev_dependencies = r.dev_dependencies.into_iter().map( | r | callback( name_bump_report, r ) ).collect(); + r.build_dependencies = r.build_dependencies.into_iter().map( | r | callback( name_bump_report, r ) ).collect(); + + r + } + let list = list.into_iter().map( | r | callback( &name_bump_report, r ) ).collect(); + + let list = action::list::ListReport::Tree( list ); + writeln!( f, "{}", list )?; + } + + Ok( () ) + } + + /// Format and display the list of packages and their version bumps in a formatted way. + /// + /// # Arguments + /// + /// - `f`: A mutable reference to a `Formatter` where the output will be written to. + /// + /// # Errors + /// + /// Returns a `std::fmt::Error` if there is an error writing to the formatter. + pub fn display_as_list( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result + { + for ( idx, package ) in self.plans.iter().enumerate() + { + let bump = &package.version_bump; + writeln!( f, "[{idx}] {} ({} -> {})", package.package_name, bump.old_version, bump.new_version )?; + } + + Ok( () ) + } + } + + impl PublishPlanFormer + { + pub fn option_base_temp_dir( mut self, path : Option< PathBuf > ) -> Self + { + self.storage.base_temp_dir = path; + self + } + + pub fn package< IntoPackage >( mut self, package : IntoPackage ) -> Self + where + IntoPackage : Into< Package >, + { + let mut plan = PublishSinglePackagePlanner::former(); + if let Some( workspace ) = &self.storage.workspace_dir + { + plan = plan.workspace_dir( workspace.clone() ); + } + if let Some( base_temp_dir ) = &self.storage.base_temp_dir + { + plan = plan.base_temp_dir( base_temp_dir.clone() ); + } + if let Some( dry ) = self.storage.dry + { + plan = plan.dry( dry ); + } + let plan = plan + .package( package ) + .perform(); + let mut plans = self.storage.plans.unwrap_or_default(); + plans.push( plan ); + + self.storage.plans = Some( plans ); + + self + } + + pub fn packages< IntoPackageIter, IntoPackage >( mut self, packages : IntoPackageIter ) -> Self + where + IntoPackageIter : IntoIterator< Item = IntoPackage >, + IntoPackage : Into< Package >, + { + for package in packages + { + self = self.package( package ); + } + + self + } + } + + + /// Perform publishing of multiple packages based on the provided publish plan. + /// + /// # Arguments + /// + /// * `plan` - The publish plan with details of packages to be published. + /// + /// # Returns + /// + /// Returns a `Result` containing a vector of `PublishReport` if successful, else an error. + pub fn perform_packages_publish( plan : PublishPlan ) -> Result< Vec< PublishReport > > + { + let mut report = vec![]; + for package in plan.plans + { + let res = perform_package_publish( package ).map_err( | e | format_err!( "{report:#?}\n{e:#?}" ) )?; + report.push( res ); + } + + Ok( report ) + } + /// Holds information about the publishing process. #[ derive( Debug, Default, Clone ) ] pub struct PublishReport @@ -304,7 +636,7 @@ mod private /// Indicates whether publishing is required for the package. pub publish_required : bool, /// Bumps the version of the package. - pub bump : Option< ExtendedBumpReport >, + pub bump : Option< version::ExtendedBumpReport >, /// Report of adding changes to the Git repository. pub add : Option< process::Report >, /// Report of committing changes to the Git repository. @@ -336,7 +668,7 @@ mod private return Ok( () ) } let info = get_info.as_ref().unwrap(); - f.write_fmt( format_args!( "{}", info ) )?; + write!( f, "{}", info )?; if !publish_required { @@ -346,196 +678,29 @@ mod private if let Some( bump ) = bump { - f.write_fmt( format_args!( "{}", bump ) )?; + writeln!( f, "{}", bump )?; } if let Some( add ) = add { - f.write_fmt( format_args!( "{add}" ) )?; + write!( f, "{add}" )?; } if let Some( commit ) = commit { - f.write_fmt( format_args!( "{commit}" ) )?; + write!( f, "{commit}" )?; } if let Some( push ) = push { - f.write_fmt( format_args!( "{push}" ) )?; + write!( f, "{push}" )?; } if let Some( publish ) = publish { - f.write_fmt( format_args!( "{publish}" ) )?; - } - - Ok( () ) - } - } - - /// Report about a changing version. - #[ derive( Debug, Default, Clone ) ] - pub struct ExtendedBumpReport - { - /// Report base. - pub base : BumpReport, - /// Files that should(already) changed for bump. - pub changed_files : Vec< AbsolutePath > - } - - impl std::fmt::Display for ExtendedBumpReport - { - fn fmt( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result - { - let Self { base, changed_files } = self; - if self.changed_files.is_empty() - { - f.write_str( "Files were not changed during bumping the version" )?; - return Ok( () ) + write!( f, "{publish}" )?; } - let files = changed_files.iter().map( | f | f.as_ref().display() ).join( ",\n " ); - f.write_fmt( format_args!( "{base}\n changed files :\n {files}\n" ) )?; - Ok( () ) } } - /// Option for publish single - #[ derive( Debug, Former ) ] - pub struct PublishSingleOptions< 'a > - { - package : &'a Package, - force : bool, - base_temp_dir : &'a Option< PathBuf >, - dry : bool, - } - - impl < 'a >PublishSingleOptionsFormer< 'a > - { - pub fn option_base_temp_dir( mut self, value : impl Into< &'a Option< PathBuf > > ) -> Self - { - self.storage.base_temp_dir = Some( value.into() ); - self - } - } - - /// Publishes a single package without publishing its dependencies. - /// - /// This function is designed to publish a single package. It does not publish any of the package's dependencies. - /// - /// Args : - /// - /// - package - a package that will be published - /// - dry - a flag that indicates whether to apply the changes or not - /// - true - do not publish, but only show what steps should be taken - /// - false - publishes the package - /// - /// Returns : - /// Returns a result containing a report indicating the result of the operation. - pub fn publish_single< 'a >( args : PublishSingleOptions< 'a > ) -> Result< PublishReport, ( PublishReport, wError ) > - { - let mut report = PublishReport::default(); - if args.package.local_is().map_err( | err | ( report.clone(), format_err!( err ) ) )? - { - return Ok( report ); - } - - let package_dir = &args.package.crate_dir(); - let temp_dir = args.base_temp_dir.as_ref().map - ( - | p | - { - let path = p.join( package_dir.as_ref().file_name().unwrap() ); - std::fs::create_dir_all( &path ).unwrap(); - path - } - ); - - let pack_args = cargo::PackOptions::former() - .path( package_dir.absolute_path().as_ref().to_path_buf() ) - .option_temp_path( temp_dir.clone() ) - .dry( args.dry ) - .form(); - let output = cargo::pack( pack_args ).context( "Take information about package" ).map_err( | e | ( report.clone(), e ) )?; - if output.err.contains( "not yet committed") - { - return Err(( report, format_err!( "Some changes wasn't committed. Please, commit or stash that changes and try again." ) )); - } - report.get_info = Some( output ); - - if args.force || publish_need( &args.package, temp_dir.clone() ).map_err( | err | ( report.clone(), format_err!( err ) ) )? - { - report.publish_required = true; - - let mut files_changed_for_bump = vec![]; - let mut manifest = args.package.manifest().map_err( | err | ( report.clone(), format_err!( err ) ) )?; - // bump a version in the package manifest - let bump_report = version::bump( &mut manifest, args.dry ).context( "Try to bump package version" ).map_err( | e | ( report.clone(), e ) )?; - files_changed_for_bump.push( args.package.manifest_path() ); - let new_version = bump_report.new_version.clone().unwrap(); - - let package_name = args.package.name().map_err( | err | ( report.clone(), format_err!( err ) ) )?; - - // bump the package version in dependents (so far, only workspace) - let workspace_manifest_dir : AbsolutePath = Workspace::with_crate_dir( args.package.crate_dir() ).map_err( | err | ( report.clone(), err ) )?.workspace_root().map_err( | err | ( report.clone(), format_err!( err ) ) )?.try_into().unwrap(); - let workspace_manifest_path = workspace_manifest_dir.join( "Cargo.toml" ); - - // qqq : should be refactored - if !args.dry - { - let mut workspace_manifest = manifest::open( workspace_manifest_path.clone() ).map_err( | e | ( report.clone(), format_err!( e ) ) )?; - let workspace_manifest_data = workspace_manifest.manifest_data.as_mut().ok_or_else( || ( report.clone(), format_err!( PackageError::Manifest( ManifestError::EmptyManifestData ) ) ) )?; - workspace_manifest_data - .get_mut( "workspace" ) - .and_then( | workspace | workspace.get_mut( "dependencies" ) ) - .and_then( | dependencies | dependencies.get_mut( &package_name ) ) - .map - ( - | dependency | - { - if let Some( previous_version ) = dependency.get( "version" ).and_then( | v | v.as_str() ).map( | v | v.to_string() ) - { - if previous_version.starts_with('~') - { - dependency[ "version" ] = value( format!( "~{new_version}" ) ); - } - else - { - dependency[ "version" ] = value( new_version.clone() ); - } - } - } - ) - .unwrap(); - workspace_manifest.store().map_err( | err | ( report.clone(), err.into() ) )?; - } - - files_changed_for_bump.push( workspace_manifest_path ); - let files_changed_for_bump : Vec< _ > = files_changed_for_bump.into_iter().unique().collect(); - let objects_to_add : Vec< _ > = files_changed_for_bump.iter().map( | f | f.as_ref().strip_prefix( &workspace_manifest_dir ).unwrap().to_string_lossy() ).collect(); - - report.bump = Some( ExtendedBumpReport { base : bump_report, changed_files : files_changed_for_bump.clone() } ); - - let commit_message = format!( "{package_name}-v{new_version}" ); - let res = git::add( workspace_manifest_dir, objects_to_add, args.dry ).map_err( | e | ( report.clone(), e ) )?; - report.add = Some( res ); - let res = git::commit( package_dir, commit_message, args.dry ).map_err( | e | ( report.clone(), e ) )?; - report.commit = Some( res ); - let res = git::push( package_dir, args.dry ).map_err( | e | ( report.clone(), e ) )?; - report.push = Some( res ); - - let res = cargo::publish - ( - cargo::PublishOptions::former() - .path( package_dir.absolute_path().as_ref().to_path_buf() ) - .option_temp_path( temp_dir ) - .dry( args.dry ) - .form() - ) - .map_err( | e | ( report.clone(), e ) )?; - report.publish = Some( res ); - } - - Ok( report ) - } - /// Sorting variants for dependencies. #[ derive( Debug, Copy, Clone ) ] pub enum DependenciesSort @@ -727,7 +892,7 @@ mod private .map( | p | p.join( format!( "package/{0}-{1}.crate", name, version ) ) ) .unwrap_or( packed_crate::local_path( &name, &version, package.crate_dir() ).map_err( | _ | PackageError::LocalPath )? ); - // qqq : for Bohdan : bad, properly handle errors + // aaa : for Bohdan : bad, properly handle errors // aaa : return result instead of panic let local_package = CrateArchive::read( local_package_path ).map_err( | _ | PackageError::ReadArchive )?; let remote_package = match CrateArchive::download_crates_io( name, version ) @@ -767,9 +932,12 @@ mod private crate::mod_interface! { + protected use PublishSinglePackagePlanner; + protected use PublishPlan; + protected use perform_package_publish; + protected use perform_packages_publish; + protected use PublishReport; - protected use publish_single; - protected use PublishSingleOptions; protected use Package; protected use PackageError; diff --git a/module/move/willbe/src/entity/version.rs b/module/move/willbe/src/entity/version.rs index 8f55939f34..50f851ce7d 100644 --- a/module/move/willbe/src/entity/version.rs +++ b/module/move/willbe/src/entity/version.rs @@ -8,14 +8,18 @@ mod private fmt, str::FromStr, }; + use std::fmt::Formatter; use toml_edit::value; use semver::Version as SemVersion; use wtools::error::for_app::Result; use manifest::Manifest; + use _path::AbsolutePath; + use package::Package; + use wtools::{ error::anyhow::format_err, iter::Itertools }; /// Wrapper for a SemVer structure - #[ derive( Debug, Clone, Eq, PartialEq ) ] + #[ derive( Debug, Clone, Eq, PartialEq, Ord, PartialOrd ) ] pub struct Version( SemVersion ); impl FromStr for Version @@ -28,6 +32,26 @@ mod private } } + impl TryFrom< &str > for Version + { + type Error = semver::Error; + + fn try_from( value : &str ) -> Result< Self, Self::Error > + { + FromStr::from_str( value ) + } + } + + impl TryFrom< &String > for Version + { + type Error = semver::Error; + + fn try_from( value : &String ) -> Result< Self, Self::Error > + { + Self::try_from( value.as_str() ) + } + } + impl fmt::Display for Version { fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result @@ -144,6 +168,138 @@ mod private Ok( report ) } + + // qqq : we have to replace the implementation above with the implementation below, don't we? + + /// `BumpOptions` manages the details necessary for the version bump process for crates. + /// This includes the directory of the crate whose version is being bumped, the old and new version numbers, + /// and the set of dependencies of that crate. + #[ derive( Debug, Clone ) ] + pub struct BumpOptions + { + /// `crate_dir` - The directory of the crate which you want to bump the version of. This value is + /// represented by `CrateDir` which indicates the directory of the crate. + pub crate_dir : CrateDir, + + /// `old_version` - The version of the crate before the bump. It's represented by `Version` which + /// denotes the old version number of the crate. + pub old_version : Version, + + /// `new_version` - The version number to assign to the crate after the bump. It's also represented + /// by `Version` which denotes the new version number of the crate. + pub new_version : Version, + + /// `dependencies` - This is a vector containing the directories of all the dependencies of the crate. + /// Each item in the `dependencies` vector indicates a `CrateDir` directory of a single dependency. + pub dependencies : Vec< CrateDir >, + + /// `dry` - A boolean indicating whether to do a "dry run". If set to `true`, a simulated run is performed + /// without making actual changes. If set to `false`, the operations are actually executed. This is + /// useful for validating the process of bumping up the version or for testing and debugging. + pub dry : bool, + } + + /// Report about a changing version. + #[ derive( Debug, Default, Clone ) ] + pub struct ExtendedBumpReport + { + /// Pacakge name. + pub name : Option< String >, + /// Package old version. + pub old_version : Option< String >, + /// Package new version. + pub new_version : Option< String >, + /// Files that should(already) changed for bump. + pub changed_files : Vec< AbsolutePath > + } + + impl std::fmt::Display for ExtendedBumpReport + { + fn fmt( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result + { + let Self { name, old_version, new_version, changed_files } = self; + if self.changed_files.is_empty() + { + write!( f, "Files were not changed during bumping the version" )?; + return Ok( () ) + } + + let files = changed_files.iter().map( | f | f.as_ref().display() ).join( ",\n " ); + match ( name, old_version, new_version ) + { + ( Some( name ), Some( old_version ), Some( new_version ) ) + => writeln!( f, "`{name}` bumped from {old_version} to {new_version}\n changed files :\n {files}" ), + _ => writeln!( f, "Bump failed" ) + }?; + + Ok( () ) + } + } + + + /// Bumps the version of a package and its dependencies. + /// + /// # Arguments + /// + /// * `args` - The options for version bumping. + /// + /// # Returns + /// + /// Returns a result containing the extended bump report if successful. + /// + pub fn version_bump( o : BumpOptions ) -> Result< ExtendedBumpReport > + { + let mut report = ExtendedBumpReport::default(); + let package_path = o.crate_dir.absolute_path().join( "Cargo.toml" ); + let package = Package::try_from( package_path.clone() ).map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?; + let name = package.name().map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?; + report.name = Some( name.clone() ); + let package_version = package.version().map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?; + let current_version = version::Version::try_from( package_version.as_str() ).map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?; + if current_version > o.new_version + { + return Err( format_err!( "{report:?}\nThe current version of the package is higher than need to be set\n\tpackage: {name}\n\tcurrent_version: {current_version}\n\tnew_version: {}", o.new_version ) ); + } + report.old_version = Some( o.old_version.to_string() ); + report.new_version = Some( o.new_version.to_string() ); + + let mut package_manifest = package.manifest().map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?; + if !o.dry + { + let data = package_manifest.manifest_data.as_mut().unwrap(); + data[ "package" ][ "version" ] = value( &o.new_version.to_string() ); + package_manifest.store()?; + } + report.changed_files = vec![ package_path ]; + let new_version = &o.new_version.to_string(); + for dep in &o.dependencies + { + let manifest_path = dep.absolute_path().join( "Cargo.toml" ); + let mut manifest = manifest::open( manifest_path.clone() ).map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?; + let data = manifest.manifest_data.as_mut().unwrap(); + let item = if let Some( item ) = data.get_mut( "package" ) { item } + else if let Some( item ) = data.get_mut( "workspace" ) { item } + else { return Err( format_err!( "{report:?}\nThe manifest nor the package and nor the workspace" ) ); }; + if let Some( dependency ) = item.get_mut( "dependencies" ).and_then( | ds | ds.get_mut( &name ) ) + { + if let Some( previous_version ) = dependency.get( "version" ).and_then( | v | v.as_str() ).map( | v | v.to_string() ) + { + if previous_version.starts_with('~') + { + dependency[ "version" ] = value( format!( "~{new_version}" ) ); + } + else + { + dependency[ "version" ] = value( new_version.clone() ); + } + } + } + if !o.dry { manifest.store().map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?; } + report.changed_files.push( manifest_path ); + } + + Ok( report ) + } } // @@ -158,4 +314,11 @@ crate::mod_interface! /// Bump version. protected use bump; + + /// Options for version bumping. + protected use BumpOptions; + /// Report about a changing version with list of files that was changed. + protected use ExtendedBumpReport; + /// Bumps the version of a package and its dependencies. + protected use version_bump; } diff --git a/module/move/willbe/src/tool/_path.rs b/module/move/willbe/src/tool/_path.rs index 57a80ced88..cd094111f8 100644 --- a/module/move/willbe/src/tool/_path.rs +++ b/module/move/willbe/src/tool/_path.rs @@ -9,6 +9,28 @@ pub( crate ) mod private #[ derive( Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash ) ] pub struct AbsolutePath( PathBuf ); + impl TryFrom< &str > for AbsolutePath + { + type Error = std::io::Error; + + fn try_from( value : &str ) -> Result< Self, Self::Error > + { + let value = PathBuf::from( value ); + Ok( Self( canonicalize( value )? ) ) + } + } + + impl TryFrom< String > for AbsolutePath + { + type Error = std::io::Error; + + fn try_from( value : String ) -> Result< Self, Self::Error > + { + let value = PathBuf::from( value ); + Ok( Self( canonicalize( value )? ) ) + } + } + impl TryFrom< PathBuf > for AbsolutePath { type Error = std::io::Error; @@ -74,7 +96,7 @@ pub( crate ) mod private Self::try_from( self.0.join( path ) ).unwrap() } } - + // qqq : for Petro : for Bohdan : bad. move out /// Check if path has a glob. diff --git a/module/move/willbe/src/tool/cargo.rs b/module/move/willbe/src/tool/cargo.rs index ce587ff5ed..83e376f59b 100644 --- a/module/move/willbe/src/tool/cargo.rs +++ b/module/move/willbe/src/tool/cargo.rs @@ -10,12 +10,12 @@ mod private use wtools::error::Result; /// Represents pack options - #[ derive( Debug, Former ) ] + #[ derive( Debug, Former, Clone ) ] pub struct PackOptions { - path : PathBuf, - temp_path : Option< PathBuf >, - dry : bool, + pub( crate ) path : PathBuf, + pub( crate ) temp_path : Option< PathBuf >, + pub( crate ) dry : bool, } impl PackOptionsFormer @@ -84,9 +84,9 @@ mod private #[ derive( Debug, Former, Clone, Default ) ] pub struct PublishOptions { - path : PathBuf, - temp_path : Option< PathBuf >, - dry : bool, + pub( crate ) path : PathBuf, + pub( crate ) temp_path : Option< PathBuf >, + pub( crate ) dry : bool, } impl PublishOptionsFormer diff --git a/module/move/willbe/src/tool/graph.rs b/module/move/willbe/src/tool/graph.rs index 5c74ce9eb0..4e13a84fbc 100644 --- a/module/move/willbe/src/tool/graph.rs +++ b/module/move/willbe/src/tool/graph.rs @@ -97,7 +97,7 @@ pub( crate ) mod private .collect::< Vec< _ > >() ), Err( index ) => Err( GraphError::Cycle( ( *graph.index( index.node_id() ) ).clone() ) ), - // qqq : for Bohdan : bad, make proper error handling + // aaa : for Bohdan : bad, make proper error handling // aaa : now returns `GraphError` } } diff --git a/module/move/willbe/tests/inc/entity/publish_need.rs b/module/move/willbe/tests/inc/entity/publish_need.rs index fa0829b24c..59f4a97828 100644 --- a/module/move/willbe/tests/inc/entity/publish_need.rs +++ b/module/move/willbe/tests/inc/entity/publish_need.rs @@ -38,7 +38,7 @@ fn package< P : AsRef< Path > >( path : P ) -> Package fn no_changes() { // Arrange - // qqq : for Bohdan : make helper function returning package_path. reuse it for all relevant tests + // aaa : for Bohdan : make helper function returning package_path. reuse it for all relevant tests // aaa : use `package_path` function let package_path = package_path( "c" ); diff --git a/module/move/willbe/tests/inc/mod.rs b/module/move/willbe/tests/inc/mod.rs index b941422388..b7a5680237 100644 --- a/module/move/willbe/tests/inc/mod.rs +++ b/module/move/willbe/tests/inc/mod.rs @@ -1,9 +1,10 @@ -use super::*; - -mod action; -mod command; -mod entity; -mod tool; -mod helpers; - -// qqq : for Petro : for Bohdan : sort out test files to be consistent with src files +use super::*; + +mod action; +mod package; +mod command; +mod entity; +mod tool; +mod helpers; + +// qqq : for Petro : for Bohdan : sort out test files to be consistent with src files diff --git a/module/move/willbe/tests/inc/package.rs b/module/move/willbe/tests/inc/package.rs new file mode 100644 index 0000000000..08b2f36f93 --- /dev/null +++ b/module/move/willbe/tests/inc/package.rs @@ -0,0 +1,172 @@ +use super::*; +use the_module:: +{ + Workspace, + _path::AbsolutePath, + package::PublishPlan, +}; +use willbe::package::perform_packages_publish; + +// #[ test ] +// fn plan_publish_many_packages() +// { +// let workspace = Workspace::from_current_path().unwrap(); +// let package = workspace.package_find_by_manifest( /* AbsolutePath::try_from( "../wca/Cargo.toml" ).unwrap() */ ).unwrap().to_owned(); +// let mega_plan = PublishPlan::former() +// .workspace( workspace ) +// .base_temp_dir( "temp" ) +// .packages([ package ]) +// .form(); +// dbg!( &mega_plan.plans ); +// // [module\move\willbe\tests\inc\package.rs:21:3] &mega_plan.plans = [ +// // PackagePublishInstruction { +// // pack: PackOptions { +// // path: ".../wTools/module/move/wca", +// // temp_path: Some( +// // "temp", +// // ), +// // dry: true, +// // }, +// // version_bump: BumpOptions { +// // crate_dir: CrateDir( +// // AbsolutePath( +// // ".../wTools/module/move/wca", +// // ), +// // ), +// // old_version: Version( +// // Version { +// // major: 0, +// // minor: 14, +// // patch: 0, +// // }, +// // ), +// // new_version: Version( +// // Version { +// // major: 0, +// // minor: 15, +// // patch: 0, +// // }, +// // ), +// // dependencies: [ +// // CrateDir( +// // AbsolutePath( +// // ".../wTools", +// // ), +// // ), +// // ], +// // dry: true, +// // }, +// // git_things: GitThingsOptions { +// // git_root: AbsolutePath( +// // ".../wTools", +// // ), +// // items: [ +// // AbsolutePath( +// // ".../wTools/Cargo.toml", +// // ), +// // AbsolutePath( +// // ".../wTools/module/move/wca/Cargo.toml", +// // ), +// // ], +// // message: "wca-v0.15.0", +// // dry: true, +// // }, +// // publish: PublishOptions { +// // path: ".../wTools/module/move/wca", +// // temp_path: Some( +// // "temp", +// // ), +// // dry: true, +// // }, +// // dry: true, +// // }, +// // ] +// let mega_plan = perform_packages_publish( mega_plan ); +// dbg!( mega_plan ); +// // [module\move\willbe\tests\inc\package.rs:89:3] mega_plan = Ok( +// // [ +// // PublishReport { +// // get_info: Some( +// // Report { +// // command: "cargo package --target-dir temp", +// // current_path: ".../wTools/module/move/wca", +// // out: "", +// // err: "", +// // error: Ok( +// // (), +// // ), +// // }, +// // ), +// // publish_required: true, +// // bump: Some( +// // ExtendedBumpReport { +// // base: BumpReport { +// // name: Some( +// // "wca", +// // ), +// // old_version: Some( +// // "0.14.0", +// // ), +// // new_version: Some( +// // "0.15.0", +// // ), +// // }, +// // changed_files: [ +// // AbsolutePath( +// // ".../wTools/module/move/wca/Cargo.toml", +// // ), +// // AbsolutePath( +// // ".../wTools/Cargo.toml", +// // ), +// // ], +// // }, +// // ), +// // add: Some( +// // Report { +// // command: "git add Cargo.toml module/move/wca/Cargo.toml", +// // current_path: ".../wTools", +// // out: "", +// // err: "", +// // error: Ok( +// // (), +// // ), +// // }, +// // ), +// // commit: Some( +// // Report { +// // command: "git commit -m wca-v0.15.0", +// // current_path: ".../wTools", +// // out: "", +// // err: "", +// // error: Ok( +// // (), +// // ), +// // }, +// // ), +// // push: Some( +// // Report { +// // command: "git push", +// // current_path: ".../wTools", +// // out: "", +// // err: "", +// // error: Ok( +// // (), +// // ), +// // }, +// // ), +// // publish: Some( +// // Report { +// // command: "cargo publish --target-dir temp", +// // current_path: ".../wTools/module/move/wca", +// // out: "", +// // err: "", +// // error: Ok( +// // (), +// // ), +// // }, +// // ), +// // }, +// // ], +// // ) +// panic!() +// } diff --git a/module/test/a/Cargo.toml b/module/test/a/Cargo.toml index 81dfa753e8..19f5f8e546 100644 --- a/module/test/a/Cargo.toml +++ b/module/test/a/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test_experimental_a" -version = "0.3.0" +version = "0.5.0" edition = "2021" license = "MIT" description = """ diff --git a/module/test/b/Cargo.toml b/module/test/b/Cargo.toml index 6b3808e6d0..57d5bb293a 100644 --- a/module/test/b/Cargo.toml +++ b/module/test/b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test_experimental_b" -version = "0.2.0" +version = "0.3.0" edition = "2021" license = "MIT" description = """ diff --git a/module/test/c/Cargo.toml b/module/test/c/Cargo.toml index 584207f2d5..8b5d415955 100644 --- a/module/test/c/Cargo.toml +++ b/module/test/c/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "test_experimental_c" -version = "0.2.0" +version = "0.3.0" edition = "2021" license = "MIT" description = """