///|
/// A semantic version triple.
pub(all) struct Semver {
major : Int
minor : Int
patch : Int
} derive(Eq, Debug)
///|
pub extend Semver with Eq::{equal, not_equal}
///|
pub extend Semver with @moonbitlang/core/debug.Debug::{to_repr}
///|
/// Which segment a bump increments.
pub(all) enum Bump {
Patch
Minor
Major
}
///|
/// Extract the first `major.minor.patch` triple found in `text`. Lenient by
/// design: any prefix is skipped ("v1.4.0", "bun 1.4.0 (official)"), the scan
/// stops at the first character that is not a digit or a dot, and missing
/// segments read as zero — so pre-release suffixes ("0.3.0-beta.1") compare
/// as their release triple. Text with no digits yields all zeros, which
/// callers can treat as "unknown version".
pub fn parse(text : String) -> Semver {
let buf = StringBuilder()
let mut started = false
for ch in text {
if !started {
if ch is ('0'..='9') {
started = true
buf.write_char(ch)
}
} else if ch is ('0'..='9') || ch == '.' {
buf.write_char(ch)
} else {
break
}
}
let parts = buf.to_string().split(".").to_array()
Semver::{
major: segment(parts, 0),
minor: segment(parts, 1),
patch: segment(parts, 2),
}
}
///|
fn segment(parts : Array[StringView], index : Int) -> Int {
if index < parts.length() {
digits(parts[index].to_owned())
} else {
0
}
}
///|
/// Numeric prefix of one version segment; non-digits stop the scan.
fn digits(text : String) -> Int {
let mut n = 0
for ch in text {
if !(ch is ('0'..='9')) {
break
}
n = n * 10 + ch.to_int() - 48
}
n
}
///|
/// Segment-by-segment ordering: -1, 0, or 1.
pub fn compare(a : Semver, b : Semver) -> Int {
if a.major != b.major {
return if a.major < b.major { -1 } else { 1 }
}
if a.minor != b.minor {
return if a.minor < b.minor { -1 } else { 1 }
}
if a.patch != b.patch {
return if a.patch < b.patch { -1 } else { 1 }
}
0
}
///|
/// Increment one segment, zeroing the ones below it.
pub fn bump(version : Semver, kind : Bump) -> Semver {
match kind {
Major => Semver::{ major: version.major + 1, minor: 0, patch: 0, }
Minor =>
Semver::{ major: version.major, minor: version.minor + 1, patch: 0, }
Patch =>
Semver::{
major: version.major,
minor: version.minor,
patch: version.patch + 1,
}
}
}
///|
/// Render back to `major.minor.patch`.
pub fn Semver::to_string(self : Semver) -> String {
"\{self.major}.\{self.minor}.\{self.patch}"
}