///|
/// Semantic Versioning (SemVer 2.0.0).
pub struct SemVer {
major : Int
minor : Int
patch : Int
pre : Array[PreId]
build : Array[String]
} derive(Debug, Eq, ToJson)
///|
/// Pre-release identifier.
pub enum PreId {
Num(Int)
Str(String)
} derive(Debug, Eq, ToJson)
///|
/// Errors raised while parsing a SemVer string.
pub suberror SemVerError {
InvalidFormat(String)
InvalidNumber(String)
LeadingZero(String)
InvalidIdentifier(String)
} derive(Debug, Eq)
///|
pub fn SemVer::new(major : Int, minor : Int, patch : Int) -> SemVer {
SemVer::{ major, minor, patch, pre: [], build: [] }
}
///|
pub fn SemVer::with_prerelease(
major : Int,
minor : Int,
patch : Int,
label : String,
number : Int,
) -> SemVer {
SemVer::{
major,
minor,
patch,
pre: [PreId::Str(label), PreId::Num(number)],
build: [],
}
}
///|
pub fn SemVer::strip_build(self : SemVer) -> SemVer {
SemVer::{
major: self.major,
minor: self.minor,
patch: self.patch,
pre: self.pre,
build: [],
}
}
///|
pub fn SemVer::to_string(self : SemVer) -> String {
let base = "\{self.major}.\{self.minor}.\{self.patch}"
let pre = if self.pre.is_empty() {
""
} else {
"-" + self.pre.map(pre_id_to_string).join(".")
}
let build = if self.build.is_empty() {
""
} else {
"+" + self.build.join(".")
}
base + pre + build
}
///|
pub fn SemVer::is_prerelease(self : SemVer) -> Bool {
!self.pre.is_empty()
}
///|
/// Compare versions by SemVer precedence rules (build metadata ignored).
pub fn SemVer::compare(self : SemVer, other : SemVer) -> Int {
let major_cmp = compare_int(self.major, other.major)
if major_cmp != 0 {
major_cmp
} else {
let minor_cmp = compare_int(self.minor, other.minor)
if minor_cmp != 0 {
minor_cmp
} else {
let patch_cmp = compare_int(self.patch, other.patch)
if patch_cmp != 0 {
patch_cmp
} else {
compare_prerelease(self.pre, other.pre)
}
}
}
}
///|
fn compare_int(a : Int, b : Int) -> Int {
if a < b {
-1
} else if a > b {
1
} else {
0
}
}
///|
fn compare_string(a : String, b : String) -> Int {
let a_chars = a.iter().to_array()
let b_chars = b.iter().to_array()
let len = if a_chars.length() < b_chars.length() {
a_chars.length()
} else {
b_chars.length()
}
for i = 0, result = 0; i < len && result == 0; {
let cmp = compare_char(a_chars[i], b_chars[i])
continue i + 1, cmp
} nobreak {
if result != 0 {
result
} else {
compare_int(a_chars.length(), b_chars.length())
}
}
}
///|
fn compare_char(a : Char, b : Char) -> Int {
if a < b {
-1
} else if a > b {
1
} else {
0
}
}
///|
fn compare_pre_id(a : PreId, b : PreId) -> Int {
match (a, b) {
(Num(x), Num(y)) => compare_int(x, y)
(Num(_), Str(_)) => -1
(Str(_), Num(_)) => 1
(Str(x), Str(y)) => compare_string(x, y)
}
}
///|
fn compare_prerelease(a : Array[PreId], b : Array[PreId]) -> Int {
if a.is_empty() && b.is_empty() {
0
} else if a.is_empty() {
1
} else if b.is_empty() {
-1
} else {
let len = if a.length() < b.length() { a.length() } else { b.length() }
for i = 0, result = 0; i < len && result == 0; {
let cmp = compare_pre_id(a[i], b[i])
continue i + 1, cmp
} nobreak {
if result != 0 {
result
} else {
compare_int(a.length(), b.length())
}
}
}
}
///|
fn pre_id_to_string(id : PreId) -> String {
match id {
Num(value) => value.to_string()
Str(value) => value
}
}