diff --git a/module/core/process_tools/src/process.rs b/module/core/process_tools/src/process.rs index 98027c9f7a..f41e3bf120 100644 --- a/module/core/process_tools/src/process.rs +++ b/module/core/process_tools/src/process.rs @@ -176,6 +176,22 @@ pub( crate ) mod private report.out = out; + let err = String::from_utf8( output.stderr ) + .context( "Found invalid UTF-8" ) + .map_err( | e | + { + report.error = Err( e.into() ); + Err::< (), () >( () ) + }); + + if err.is_err() + { + return Err( report ); + } + let err = err.unwrap(); + + report.err = err; + if output.status.success() { Ok( report ) diff --git a/module/move/willbe/src/action/mod.rs b/module/move/willbe/src/action/mod.rs index 08c634878f..03d817fc44 100644 --- a/module/move/willbe/src/action/mod.rs +++ b/module/move/willbe/src/action/mod.rs @@ -8,6 +8,8 @@ crate::mod_interface! layer main_header; /// Publish packages. layer publish; + /// Return the differences between a local and remote package versions. + layer publish_diff; /// Generates health table in main Readme.md file of workspace. // aaa : for Petro : give high quality explanations // aaa : add more details to description diff --git a/module/move/willbe/src/action/publish_diff.rs b/module/move/willbe/src/action/publish_diff.rs new file mode 100644 index 0000000000..d8f648aba7 --- /dev/null +++ b/module/move/willbe/src/action/publish_diff.rs @@ -0,0 +1,38 @@ +/// Internal namespace. +mod private +{ + use crate::*; + + use std::path::PathBuf; + use crates_tools::CrateArchive; + + use _path::AbsolutePath; + use wtools::error::for_app::Result; + use diff::{ DiffReport, crate_diff }; + + /// Return the differences between a local and remote package versions. + #[ cfg_attr( feature = "tracing", tracing::instrument ) ] + pub fn publish_diff( path : PathBuf ) -> Result< DiffReport > + { + let path = AbsolutePath::try_from( path )?; + let dir = CrateDir::try_from( path )?; + + let package = package::Package::try_from( dir.clone() )?; + let name = &package.name()?; + let version = &package.version()?; + + _ = cargo::pack( cargo::PackOptions::former().path( dir.as_ref() ).dry( false ).form() )?; + let l = CrateArchive::read( packed_crate::local_path( name, version, dir )? )?; + let r = CrateArchive::download_crates_io( name, version ).unwrap(); + + Ok( crate_diff( &l, &r ) ) + } +} + +// + +crate::mod_interface! +{ + /// 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 08cf29ba2a..384d1540f4 100644 --- a/module/move/willbe/src/command/mod.rs +++ b/module/move/willbe/src/command/mod.rs @@ -34,6 +34,17 @@ pub( crate ) mod private .routine( command::publish ) .end() + .command( "publish.diff" ) + .hint( "Display the differences between a local and remote package versions." ) + .long_hint( "Following this command, you will immediately get a comparison between the local and remote packages. It looks at each file, identifying those added, removed, or modified. A full report will then be generated where you can quickly and easily see the differences." ) + .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() + .routine( command::publish_diff ) + .end() + .command( "list" ) .hint( "list packages from a directory" ) .long_hint( "generates a list of packages based on the provided directory path. The directory must contain a `Cargo.toml` file." ) @@ -237,6 +248,8 @@ crate::mod_interface! layer list; /// Publish packages. layer publish; + /// Used to compare local and published versions of a specific package. + layer publish_diff; /// Generates health table in main Readme.md file of workspace. // aaa : for Petro : what a table?? // aaa : add more details to documentation diff --git a/module/move/willbe/src/command/publish_diff.rs b/module/move/willbe/src/command/publish_diff.rs new file mode 100644 index 0000000000..baedd83605 --- /dev/null +++ b/module/move/willbe/src/command/publish_diff.rs @@ -0,0 +1,39 @@ +mod private +{ + use crate::*; + + use std::path::PathBuf; + use wca::Args; + + use wtools::error::Result; + + /// Command to display the differences between a local and remote package versions. + /// + /// # Arguments + /// + /// * `args` - Command line arguments. + /// + /// # Returns + /// + /// Returns a `Result` indicating success or failure. + /// + /// # Errors + /// + /// Returns an error if there is an issue with the command. + pub fn publish_diff( args : Args ) -> Result< () > + { + let path : PathBuf = args.get_owned( 0 ).unwrap_or( std::env::current_dir()? ); + + println!( "{}", action::publish_diff( path )? ); + + Ok( () ) + } +} + +// + +crate::mod_interface! +{ + /// Publishes the difference between the local and published versions of a package. + orphan use publish_diff; +} diff --git a/module/move/willbe/src/entity/diff.rs b/module/move/willbe/src/entity/diff.rs new file mode 100644 index 0000000000..27c39543ff --- /dev/null +++ b/module/move/willbe/src/entity/diff.rs @@ -0,0 +1,113 @@ +mod private +{ + use crate::*; + + use std:: + { + collections::HashSet, + fmt::Formatter, + path::PathBuf, + }; + use colored::Colorize; + use crates_tools::CrateArchive; + + use wtools::iter::Itertools; + + /// The `Diff` enum is designed to represent differences between two versions + /// of some kind of item identified. + #[ derive( Debug ) ] + pub enum Diff< T > + { + /// This variant represents items that are identical or same in both versions. + Same( T ), + /// This variant represents items that exists in both versions but have been modified. + Modified( T ), + /// This variant represents items that were added. + Add( T ), + /// This variant represents items that were removed. + Rem( T ), + } + + /// The `DiffReport` struct represents a diff report containing a list of `Diff` objects. + #[ derive( Debug, Default ) ] + pub struct DiffReport( Vec< Diff< PathBuf > > ); + + impl std::fmt::Display for DiffReport + { + fn fmt( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result + { + for diff in self.0.iter() + .sorted_by_key( | d | match d { Diff::Modified( df ) | Diff::Same( df ) | Diff::Rem( df ) | Diff::Add( df ) => df } ) + { + match diff + { + Diff::Same( t ) => writeln!( f, "{}", t.display() )?, + Diff::Modified( t ) => writeln!( f, "~ {}", t.to_string_lossy().yellow() )?, + Diff::Add( t ) => writeln!( f, "+ {}", t.to_string_lossy().green() )?, + Diff::Rem( t ) => writeln!( f, "- {}", t.to_string_lossy().red() )?, + }; + } + + Ok( () ) + } + } + + /// Compare two crate archives and create a difference report. + /// + /// # Arguments + /// + /// * `left` - A reference to the left crate archive. + /// * `right` - A reference to the right crate archive. + /// + /// # Returns + /// + /// A `DiffReport` struct representing the difference between the two crate archives. + pub fn crate_diff( left : &CrateArchive, right : &CrateArchive ) -> DiffReport + { + let mut report = DiffReport::default(); + + let local_package_files : HashSet< _ > = left.list().into_iter().collect(); + let remote_package_files : HashSet< _ > = right.list().into_iter().collect(); + + let local_only = local_package_files.difference( &remote_package_files ); + let remote_only = remote_package_files.difference( &local_package_files ); + let both = local_package_files.intersection( &remote_package_files ); + + for &path in local_only + { + report.0.push( Diff::Add( path.to_path_buf() ) ); + } + + for &path in remote_only + { + report.0.push( Diff::Rem( path.to_path_buf() ) ); + } + + for &path in both + { + // unwraps are safe because the paths to the files was compared previously + let local = left.content_bytes( path ).unwrap(); + let remote = right.content_bytes( path ).unwrap(); + + if local == remote + { + report.0.push( Diff::Same( path.to_path_buf() ) ); + } + else + { + report.0.push( Diff::Modified( path.to_path_buf() ) ); + } + } + + report + } +} + +// + +crate::mod_interface! +{ + protected use Diff; + protected use DiffReport; + protected use crate_diff; +} \ No newline at end of file diff --git a/module/move/willbe/src/entity/mod.rs b/module/move/willbe/src/entity/mod.rs index 6073c0fa94..5a0be78e1f 100644 --- a/module/move/willbe/src/entity/mod.rs +++ b/module/move/willbe/src/entity/mod.rs @@ -1,6 +1,10 @@ crate::mod_interface! { + /// Compare two crate archives and create a difference report. + layer diff; + orphan use super::diff; + /// Operation with features layer features; orphan use super::features; diff --git a/module/move/willbe/src/entity/package.rs b/module/move/willbe/src/entity/package.rs index f5d904fab4..b9794b4d82 100644 --- a/module/move/willbe/src/entity/package.rs +++ b/module/move/willbe/src/entity/package.rs @@ -88,6 +88,22 @@ mod private } } + impl TryFrom< CrateDir > for Package + { + type Error = PackageError; + + fn try_from( value : CrateDir ) -> Result< Self, Self::Error > + { + let manifest = manifest::open( value.absolute_path().join( "Cargo.toml" ) )?; + if !manifest.package_is()? + { + return Err( PackageError::NotAPackage ); + } + + Ok( Self::Manifest( manifest ) ) + } + } + impl TryFrom< Manifest > for Package { // qqq : make better errors @@ -744,7 +760,6 @@ mod private Ok( !is_same ) } - } //