use crate::*;
fn parse_version(version_str: &str) -> Option<Version> {
let parts: Vec<&str> = version_str.split('-').collect();
let version_part: &str = parts.first()?;
let prerelease: Option<String> = parts.get(1).map(|s: &&str| s.to_string());
let nums: Vec<&str> = version_part.split('.').collect();
if nums.len() != 3 {
return None;
}
let major: u64 = nums.first()?.parse().ok()?;
let minor: u64 = nums.get(1)?.parse().ok()?;
let patch: u64 = nums.get(2)?.parse().ok()?;
Some(Version {
major,
minor,
patch,
prerelease,
})
}
fn parse_prerelease(prerelease: &str) -> Option<(&str, u64)> {
let parts: Vec<&str> = prerelease.split('.').collect();
let pre_type: &str = parts.first()?;
let number: u64 = parts
.get(1)
.and_then(|s: &&str| s.parse().ok())
.unwrap_or(0);
Some((pre_type, number))
}
fn get_next_prerelease(current: Option<&String>, target_type: &str) -> String {
match current {
Some(pre) => {
if let Some((pre_type, number)) = parse_prerelease(pre)
&& pre_type == target_type && number > 0 {
return format!("{}.{}", target_type, number + 1);
}
format!("{target_type}.1")
}
None => target_type.to_string(),
}
}
fn version_to_string(version: &Version) -> String {
let base: String = format!("{}.{}.{}", version.major, version.minor, version.patch);
match &version.prerelease {
Some(pre) => format!("{base}-{pre}"),
None => base,
}
}
fn bump_version(version: &Version, bump_type: &BumpVersionType) -> Version {
match bump_type {
BumpVersionType::Patch => Version {
major: version.major,
minor: version.minor,
patch: version.patch + 1,
prerelease: None,
},
BumpVersionType::Minor => Version {
major: version.major,
minor: version.minor + 1,
patch: 0,
prerelease: None,
},
BumpVersionType::Major => Version {
major: version.major + 1,
minor: 0,
patch: 0,
prerelease: None,
},
BumpVersionType::Release => Version {
major: version.major,
minor: version.minor,
patch: version.patch,
prerelease: None,
},
BumpVersionType::Alpha => {
let prerelease: String = get_next_prerelease(version.prerelease.as_ref(), "alpha");
Version {
major: version.major,
minor: version.minor,
patch: version.patch,
prerelease: Some(prerelease),
}
}
BumpVersionType::Beta => {
let prerelease: String = get_next_prerelease(version.prerelease.as_ref(), "beta");
Version {
major: version.major,
minor: version.minor,
patch: version.patch,
prerelease: Some(prerelease),
}
}
BumpVersionType::Rc => {
let prerelease: String = get_next_prerelease(version.prerelease.as_ref(), "rc");
Version {
major: version.major,
minor: version.minor,
patch: version.patch,
prerelease: Some(prerelease),
}
}
}
}
fn find_version_position(line: &str) -> Option<(usize, usize)> {
let trimmed: &str = line.trim();
if !trimmed.starts_with("version") || !trimmed.contains('=') {
return None;
}
let eq_pos: usize = line.find('=')?;
let after_eq: &str = &line[eq_pos + 1..];
let quote_start: usize = after_eq.find('"')?;
let after_first_quote: &str = &after_eq[quote_start + 1..];
let quote_end: usize = after_first_quote.find('"')?;
let version_start: usize = eq_pos + 1 + quote_start + 1;
let version_end: usize = version_start + quote_end;
Some((version_start, version_end))
}
pub(crate) fn execute_bump(
manifest_path: &str,
bump_type: &BumpVersionType,
) -> Result<String, Box<dyn std::error::Error>> {
let path: &Path = Path::new(manifest_path);
let content: String = read_to_string(path)?;
let mut new_version: Option<String> = None;
let mut found_version: bool = false;
let mut updated_content: String = content.clone();
for line in content.lines() {
if found_version {
break;
}
if let Some((version_start, version_end)) = find_version_position(line) {
let version_str: &str = &line[version_start..version_end];
if let Some(version) = parse_version(version_str) {
let bumped: Version = bump_version(&version, bump_type);
let version_string: String = version_to_string(&bumped);
new_version = Some(version_string.clone());
let new_line: String = format!(
"{}{}{}",
&line[..version_start],
version_string,
&line[version_end..]
);
updated_content = updated_content.replacen(line, &new_line, 1);
found_version = true;
}
}
}
if !found_version {
return Err("version field not found in Cargo.toml".into());
}
write(path, updated_content)?;
match new_version {
Some(v) => Ok(v),
None => Err("failed to bump version".into()),
}
}