use miette::{IntoDiagnostic, LabeledSpan, bail, ensure, miette};
use semver::{BuildMetadata, Prerelease, Version};
use std::str::FromStr;
use tracing::instrument;
use crate::{Action, Result, current_span, error::VersionError};
pub trait Bumpable {
fn bump(
&mut self,
action: Action,
pre_release: Option<Prerelease>,
build: Option<BuildMetadata>,
force_version: bool,
) -> Result<Version>;
fn try_bump_pre(&mut self, force: bool) -> Result<Version>;
fn try_bump_patch(&mut self) -> Result<Version>;
fn try_bump_minor(&mut self, force: bool) -> Result<Version>;
fn try_bump_major(&mut self, force: bool) -> Result<Version>;
}
impl Bumpable for Version {
#[instrument(fields(from, to), skip(self))]
fn bump(
&mut self,
action: Action,
pre_release: Option<Prerelease>,
build: Option<BuildMetadata>,
force_version: bool,
) -> Result<Version> {
use crate::cli::Action as BumpVer;
let span = current_span!();
let old_version = self.clone();
span.record("from", self.to_string());
tracing::trace!("Bumping version");
match action {
BumpVer::Patch => self.try_bump_patch()?,
BumpVer::Minor => self.try_bump_minor(force_version)?,
BumpVer::Major => self.try_bump_major(force_version)?,
BumpVer::Pre => self.try_bump_pre(force_version)?,
_ => bail!("Invalid Action: {}", action),
};
if action != Action::Pre {
if let Some(pre) = pre_release {
self.pre = pre
}
};
if let Some(build) = build {
self.build = build
}
if !force_version {
ensure!(
self.clone() > old_version,
"New version is not large than old version"
);
}
let ver_str = &self.clone().to_string();
span.record("to", ver_str);
tracing::debug!("Version bumped to: {}", ver_str);
Ok(self.clone())
}
fn try_bump_patch(&mut self) -> Result<Version> {
let old_version = self.clone();
let version = self;
if version.pre.is_empty() {
version.patch += 1;
} else {
version.pre = semver::Prerelease::EMPTY
}
ensure!(
&old_version < version,
"Patch bump error: old={}, new={}",
old_version.to_string(),
version.clone().to_string()
);
Ok(version.clone())
}
fn try_bump_minor(&mut self, force: bool) -> Result<Version> {
let old_version = self.clone();
let version = self;
if !version.pre.is_empty() && !force {
Err(VersionError::prerelease_not_empty(
&old_version,
Action::Minor,
))?;
}
version.pre = Prerelease::EMPTY;
version.minor += 1;
version.patch = 0;
ensure!(&old_version < version, "Failed to bump: Minor");
Ok(version.clone())
}
fn try_bump_major(&mut self, force: bool) -> Result<Version> {
let old_version = self.clone();
let version = self;
if !version.pre.is_empty() && !force {
Err(VersionError::prerelease_not_empty(
&old_version,
Action::Minor,
))?;
}
version.pre = Prerelease::EMPTY;
version.major += 1;
version.minor = 0;
version.patch = 0;
ensure!(&old_version < version, "Failed to bump: Major");
Ok(version.clone())
}
#[instrument(skip(self), fields(from, to))]
fn try_bump_pre(&mut self, force: bool) -> Result<Version> {
let span = current_span!();
span.record("from", self.to_string());
tracing::trace!("Bumping pre");
let old_version = self.clone();
if self.pre == Prerelease::EMPTY {
bail!(
help = "Set the pre-release either with the pre-release flag `--pre` or using the `set` action.",
"Prerelease not set."
);
}
if let Some((id, num)) = self.pre.to_string().split_once('.') {
let num = match u64::from_str(num) {
Ok(n) => n + 1,
Err(e) => {
let source_code = self.pre.to_string();
let split_idx = source_code.find('.').expect("Already checked");
let mut span = (
split_idx,
source_code.chars().count() as isize - 1isize - split_idx as isize,
);
if span.1 < 0 {
span.1 = 0isize
}
let mut span = (span.0, span.1 as usize);
span.0 += self.to_string().find('-').unwrap() + 2 + 11;
let labels = vec![LabeledSpan::new(
Some("Invalid incrementable segment".into()),
span.0,
span.1,
)];
let err = miette!(
labels = labels,
help = "Must only be a number after the '.' in prerelease segment.",
"When converting to int: {e}"
)
.with_source_code(format!(r#"version = "{}""#, self.to_string()));
return Err(err);
}
};
let pre = Prerelease::new(&format!("{id}.{num}")).into_diagnostic()?;
self.pre = pre
} else {
bail!("Prerelease not split by '.': {}", self.pre.to_string());
};
ensure!(self.clone() > old_version, "PreRelease bump failed.");
span.record("to", self.to_string());
tracing::debug!("Prerelease bumped.");
Ok(self.clone())
}
}
pub trait Setable {
fn set_version(&mut self, new_version: Version) -> Result<Version>;
fn set_prerelease(&mut self, new_prerelease: Prerelease) -> Result<Version>;
}
impl Setable for Version {
#[instrument(skip_all, fields(from, to))]
fn set_version(&mut self, new_version: Version) -> Result<Version> {
let span = current_span!();
span.record("from", self.to_string());
span.record("to", new_version.to_string());
tracing::debug!("Setting version");
self.build = new_version.build.clone();
self.pre = new_version.pre.clone();
self.patch = new_version.patch;
self.minor = new_version.minor;
self.major = new_version.major;
miette::ensure!(self.clone() == new_version, "Failed to set version");
Ok(self.clone())
}
#[instrument(skip_all, fields(from, to))]
fn set_prerelease(&mut self, new_prerelease: Prerelease) -> Result<Version> {
let span = current_span!();
span.record("from", self.to_string());
tracing::trace!("set_prerelease");
self.pre = new_prerelease;
span.record("to", self.to_string());
tracing::debug!("Set new prerelease");
Ok(self.clone())
}
}
#[allow(dead_code)]
pub trait Incrimentable {
fn increment(&mut self);
fn increment_by(&mut self, n: isize);
}