#[ allow( clippy ::std_instead_of_alloc, clippy ::std_instead_of_core ) ]
mod private
{
use crate :: *;
use std ::
{
fmt,
str ::FromStr,
};
use std ::fmt ::Formatter;
use toml_edit ::value;
use semver ::Version as SemVersion;
use error ::untyped ::Result;
use crate ::entity ::manifest ::Manifest;
use crate ::entity ::package ::Package;
use { error ::untyped ::format_err, iter ::Itertools };
#[ derive( Debug, Clone, Eq, PartialEq, Ord, PartialOrd ) ]
pub struct Version( SemVersion );
impl FromStr for Version
{
type Err = semver ::Error;
fn from_str( s: &str ) -> std ::result ::Result< Self, Self ::Err >
{
std ::result ::Result ::Ok( Self( SemVersion ::from_str( s )? ) )
}
}
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
{
write!( f, "{}", self.0 )
}
}
impl Version
{
#[ must_use ]
pub fn bump( self ) -> Self
{
let mut ver = self.0;
if ver.minor != 0 || ver.major != 0
{
ver.minor += 1;
ver.patch = 0;
}
else
{
ver.patch += 1;
}
Self( ver )
}
}
#[ derive( Debug, Default, Clone ) ]
pub struct BumpReport
{
pub name: Option< String >,
pub old_version: Option< String >,
pub new_version: Option< String >,
}
impl fmt ::Display for BumpReport
{
fn fmt( &self, f: &mut fmt ::Formatter< '_ > ) -> fmt ::Result
{
let Self { name, old_version, new_version } = self;
match ( name, old_version, new_version )
{
( Some( name ), Some( old_version ), Some( new_version ) )
=> f.write_fmt( format_args!( "`{name}` bumped from {old_version} to {new_version}" ) ),
_ => f.write_fmt( format_args!( "Bump failed" ) )
}
}
}
#[ derive( Debug, Clone ) ]
pub struct BumpOptions
{
pub crate_dir: CrateDir,
pub old_version: Version,
pub new_version: Version,
pub dependencies: Vec< CrateDir >,
pub dry: bool,
}
#[ derive( Debug, Default, Clone ) ]
pub struct ExtendedBumpReport
{
pub name: Option< String >,
pub old_version: Option< String >,
pub new_version: Option< String >,
pub changed_files: Vec< ManifestFile >
}
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 std ::fmt ::Result ::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" )
}?;
std ::fmt ::Result ::Ok( () )
}
}
pub fn bump( o: BumpOptions ) -> Result< ExtendedBumpReport >
{
let mut report = ExtendedBumpReport ::default();
let manifest_file = o.crate_dir.manifest_file();
let package = Package ::try_from( manifest_file.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.into() );
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 = &mut package_manifest.data;
data[ "package" ][ "version" ] = value( o.new_version.to_string() );
package_manifest.store()?;
}
report.changed_files = vec![ manifest_file ];
let new_version = &o.new_version.to_string();
for dep in &o.dependencies
{
let manifest_file = dep.clone().manifest_file();
let mut manifest = Manifest ::try_from( manifest_file.clone() ).map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?;
let data = &mut manifest.data;
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" ) ); };
for section in [ "dependencies", "dev-dependencies", "build-dependencies" ]
{
if let Some( dependency ) = item.get_mut( section ).and_then( | ds | ds.get_mut( name ) )
{
if let Some( previous_version ) = dependency.get( "version" ).and_then( | v | v.as_str() ).map( std ::string ::ToString ::to_string )
{
let (operator, _) = if let Some( stripped ) = previous_version.strip_prefix('~')
{
("~", stripped)
}
else if let Some( stripped ) = previous_version.strip_prefix('^')
{
("^", stripped)
}
else if let Some( stripped ) = previous_version.strip_prefix('=')
{
("=", stripped)
}
else
{
("", previous_version.as_str())
};
dependency[ "version" ] = value( format!( "{operator}{new_version}" ) );
}
}
}
if !o.dry
{ manifest.store().map_err( | e | format_err!( "{report:?}\n{e:#?}" ) )?; }
report.changed_files.push( manifest_file );
}
Ok( report )
}
pub fn revert( report: &ExtendedBumpReport ) -> error ::untyped ::Result< () > {
let Some( name ) = report.name.as_ref() else { return Ok( () ) };
let Some( old_version ) = report.old_version.as_ref() else { return Ok( () ) };
let Some( new_version ) = report.new_version.as_ref() else { return Ok( () ) };
let dependencies = | item_maybe_with_dependencies: &mut toml_edit ::Item |
{
for section in [ "dependencies", "dev-dependencies", "build-dependencies" ]
{
if let Some( dependency ) = item_maybe_with_dependencies.get_mut( section ).and_then( | ds | ds.get_mut( name ) )
{
if let Some( current_version ) = dependency.get( "version" ).and_then( | v | v.as_str() ).map( std ::string ::ToString ::to_string )
{
let version = &mut dependency[ "version" ];
let (operator, version_without_prefix) = if let Some( v ) = current_version.strip_prefix( '~' )
{
("~", v)
}
else if let Some( v ) = current_version.strip_prefix( '^' )
{
("^", v)
}
else if let Some( v ) = current_version.strip_prefix( '=' )
{
("=", v)
}
else
{
("", current_version.as_str())
};
if version_without_prefix != new_version
{
return Err( format_err!
(
"The current version of the package does not match the expected one. Expected: `{new_version}` Current: `{}`",
version.as_str().unwrap_or_default()
));
}
*version = value( format!( "{operator}{old_version}" ) );
}
}
}
Ok( () )
};
for path in &report.changed_files
{
let mut manifest = Manifest ::try_from( path.clone() )?;
let data = manifest.data();
if let Some( workspace ) = data.get_mut( "workspace" )
{
dependencies( workspace )?;
}
if let Some( package ) = data.get_mut( "package" )
{
if package.get_mut( "name" ).unwrap().as_str().unwrap() == name
{
let version = &mut package[ "version" ];
if version.as_str().unwrap() != new_version
{
return Err( format_err!
(
"The current version of the package does not match the expected one. Expected: `{new_version}` Current: `{}`",
version.as_str().unwrap_or_default()
));
}
*version = value( old_version.clone() );
}
else
{
dependencies( package )?;
}
}
manifest.store()?;
}
Ok( () )
}
pub fn manifest_bump( manifest: &mut Manifest, dry: bool ) -> Result< BumpReport, manifest ::ManifestError >
{
let mut report = BumpReport ::default();
let version=
{
let data = &manifest.data;
if !manifest.package_is()
{
return Err( manifest ::ManifestError ::NotAPackage );
}
let package = data.get( "package" ).unwrap();
let version = package.get( "version" );
if version.is_none()
{
return Err( manifest ::ManifestError ::CannotFindValue( "version".into() ) );
}
let version = version.unwrap().as_str().unwrap();
report.name = Some( package[ "name" ].as_str().unwrap().to_string() );
report.old_version = Some( version.to_string() );
Version ::from_str( version ).map_err( | e | manifest ::ManifestError ::InvalidValue( e.to_string() ) )?
};
let new_version = version.bump().to_string();
report.new_version = Some( new_version.clone() );
if !dry
{
let data = &mut manifest.data;
data[ "package" ][ "version" ] = value( &new_version );
manifest.store()?;
}
Result ::Ok( report )
}
}
crate ::mod_interface!
{
exposed use Version;
own use BumpReport;
own use BumpOptions;
own use ExtendedBumpReport;
own use manifest_bump;
own use bump;
own use revert;
}