version-rs/src/version.rs

276 lines
7.6 KiB
Rust

use {
crate::{arch::Arch, error::Error, prerelease::PreRelease, MAX_U12},
serde::{Deserialize, Serialize},
std::{cmp, fmt, str},
};
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub enum Kind {
Simple {
major: u16,
},
Rapid {
major: u16,
minor: u16,
},
SemVer {
major: u16,
minor: u16,
patch: u16,
},
Extended {
major: u16,
minor: u16,
patch: u16,
build: u16,
},
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct Version {
pub kind: Kind,
pub pre: PreRelease,
pub arch: Arch,
}
impl fmt::Display for Kind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Simple { major } => write!(f, "{major}"),
Self::Rapid { major, minor } => write!(f, "{major}.{minor}"),
Self::SemVer {
major,
minor,
patch,
} => write!(f, "{major}.{minor}.{patch}"),
Self::Extended {
major,
minor,
patch,
build,
} => write!(f, "{major}.{minor}.{patch}.{build}"),
}
}
}
impl From<Kind> for u128 {
fn from(value: Kind) -> Self {
match value {
Kind::Simple { major } => u128::from(major) << (64 + 52),
Kind::Rapid { major, minor } => {
let major = u64::from(major) << 52;
let minor = u64::from(minor) << 40;
u128::from(major | minor) << 64
}
Kind::SemVer {
major,
minor,
patch,
} => {
let major = u64::from(major) << 52;
let minor = u64::from(minor) << 40;
let patch = u64::from(patch) << 28;
u128::from(major | minor | patch) << 64
}
Kind::Extended {
major,
minor,
patch,
build,
} => {
let major = u64::from(major) << 52;
let minor = u64::from(minor) << 40;
let patch = u64::from(patch) << 28;
let build = u64::from(build) << 16;
u128::from(major | minor | patch | build) << 64
}
}
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}-{}", self.kind, self.pre, self.arch)
}
}
impl From<Version> for u128 {
fn from(value: Version) -> Self {
u128::from(value.kind) | u128::from(value.pre)
}
}
impl str::FromStr for Version {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((s, arch)) = s.split_once('-') else {
return Err(Error::ParseArch);
};
let arch: Arch = arch.parse()?;
let (s, pre) = match s.split_once('_') {
Some((a, b)) => (a, b.parse::<PreRelease>()?),
None => (s, PreRelease::None),
};
let mut split = s.split('.');
let Some(Ok(major)) = split.next().map(|m| m.parse::<u16>()) else {
return Err(Error::ParseSemver);
};
if major > MAX_U12 {
return Err(Error::Range);
};
let minor: u16 = match split.next().map(|m| m.parse()) {
Some(Ok(m)) => {
if m <= MAX_U12 {
m
} else {
return Err(Error::Range);
}
}
Some(Err(e)) => return Err(e.into()),
None => {
return Ok(Self {
kind: Kind::Simple { major },
pre,
arch,
})
}
};
let patch: u16 = match split.next().map(|p| p.parse()) {
Some(Ok(p)) => {
if p < MAX_U12 {
p
} else {
return Err(Error::Range);
}
}
Some(Err(e)) => return Err(e.into()),
None => {
return Ok(Self {
kind: Kind::Rapid { major, minor },
pre,
arch,
})
}
};
let build: u16 = match split.next().map(|b| b.parse()) {
Some(Ok(b)) => {
if b <= MAX_U12 {
b
} else {
return Err(Error::Range);
}
}
Some(Err(e)) => return Err(e.into()),
None => {
return Ok(Self {
kind: Kind::SemVer {
major,
minor,
patch,
},
pre,
arch,
})
}
};
match split.next() {
Some(_) => Err(Error::ParseVersion),
None => Ok(Self {
kind: Kind::Extended {
major,
minor,
patch,
build,
},
pre,
arch,
}),
}
}
}
impl PartialEq for Version {
fn eq(&self, other: &Self) -> bool {
u128::from(*self) == u128::from(*other) && self.arch == other.arch
}
}
impl Eq for Version {}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
if self.arch == other.arch {
Some(u128::from(*self).cmp(&u128::from(*other)))
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::num::NonZeroU16;
#[test]
fn from_str() {
let mut version: Version = "2.4.1_alpha2-aarch64".parse().unwrap();
assert_eq!(
version,
Version {
kind: Kind::SemVer {
major: 2,
minor: 4,
patch: 1
},
pre: PreRelease::Alpha(Some(NonZeroU16::new(2).unwrap())),
arch: Arch::Arm64,
}
);
version = "6.4-i486".parse().unwrap();
assert_eq!(
version,
Version {
kind: Kind::Rapid { major: 6, minor: 4 },
pre: PreRelease::None,
arch: Arch::X86,
}
);
version = "3.14.6_git_r2d2xxx.1705881493-amd64".parse().unwrap();
assert_eq!(
version,
Version {
kind: Kind::SemVer {
major: 3,
minor: 14,
patch: 6
},
pre: PreRelease::Git {
hash: ['r', '2', 'd', '2', 'x', 'x', 'x'],
datetime: epoch::DateTime {
year: epoch::prelude::Year::Leap(2024),
month: epoch::prelude::Month::Janurary,
day: 21,
hour: 23,
minute: 58,
second: 13,
zone: epoch::prelude::TimeZone::Utc
}
},
arch: Arch::X86_64,
}
)
}
#[test]
fn to_string() {
let s = "3.14.0_beta2-riscv64";
let version: Version = s.parse().unwrap();
assert_eq!(s, version.to_string());
let s = "3.14.6_git_r2d2xxx.1705881493-x86_64";
let version: Version = s.parse().unwrap();
assert_eq!(s, version.to_string());
}
}