pub fn major_version_from_string(version_str: &str) -> String {
try_major_version_from_string(version_str)
.unwrap_or_else(|| panic!("invalid semver string: {version_str:?}"))
}
pub fn try_major_version_from_string(version_str: &str) -> Option<String> {
let (major, minor, patch) = parse_triplet(version_str)?;
Some(major_version(version_str, major, minor, patch))
}
fn major_version(
version_str: &str,
major: u64,
minor: u64,
patch: u64,
) -> String {
if major > 0 {
major.to_string()
} else if minor > 0 {
format!("{major}.{minor}")
} else if patch > 0 {
format!("{major}.{minor}.{patch}")
} else {
version_str.to_string()
}
}
fn parse_triplet(version_str: &str) -> Option<(u64, u64, u64)> {
let core = version_str.split_once('+').map_or(version_str, |(v, _)| v);
let core = core.split_once('-').map_or(core, |(v, _)| v);
let mut fields = core.split('.');
let major = parse_numeric_field(fields.next()?)?;
let minor = parse_numeric_field(fields.next()?)?;
let patch = parse_numeric_field(fields.next()?)?;
if fields.next().is_some() {
return None;
}
Some((major, minor, patch))
}
fn parse_numeric_field(field: &str) -> Option<u64> {
if field.is_empty()
|| (field.len() > 1 && field.starts_with('0'))
|| !field.bytes().all(|b| b.is_ascii_digit())
{
return None;
}
field.parse().ok()
}