Initial refactor

This commit is contained in:
Nathan Fisher 2024-01-31 22:36:20 -05:00
parent 12ac9a84ce
commit 98b74bd2a7
11 changed files with 275 additions and 1346 deletions

96
Cargo.lock generated
View File

@ -46,9 +46,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.31"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
dependencies = [
"android-tzdata",
"iana-time-zone",
@ -56,7 +56,7 @@ dependencies = [
"num-traits",
"serde",
"wasm-bindgen",
"windows-targets 0.48.5",
"windows-targets",
]
[[package]]
@ -65,6 +65,14 @@ version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "epoch"
version = "0.1.0"
source = "git+https://git.hitchhiker-linux.org/jeang3nie/epoch-rs.git#9563b1da0d9e985555d2deff2ca0ee279bd04bee"
dependencies = [
"serde",
]
[[package]]
name = "iana-time-zone"
version = "0.1.59"
@ -144,18 +152,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.195"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.195"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
@ -184,6 +192,7 @@ name = "version"
version = "0.1.0"
dependencies = [
"chrono",
"epoch",
"serde",
]
@ -247,22 +256,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
"windows-targets",
]
[[package]]
@ -271,93 +265,51 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"

View File

@ -7,4 +7,5 @@ edition = "2021"
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
epoch = { git = "https://git.hitchhiker-linux.org/jeang3nie/epoch-rs.git", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }

View File

@ -1,306 +0,0 @@
use {
crate::{
error::Error, gitrev::GitRev, prerelease::PreRelease, rapid::Rapid, semver::SemVer,
simple::Simple, MAX_U12,
},
serde::{Deserialize, Serialize},
std::{cmp::Ordering, fmt, str::FromStr},
};
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct Extended {
pub major: u16,
pub minor: u16,
pub patch: u16,
pub build: u16,
pub pre: PreRelease,
}
impl fmt::Display for Extended {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}.{}.{}.{}",
self.major, self.minor, self.patch, self.build
)?;
match self.pre {
PreRelease::None => Ok(()),
p => write!(f, "_{p}"),
}
}
}
impl FromStr for Extended {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (s, pre) = match s.split_once('_') {
Some((a, b)) => (a, b.parse::<PreRelease>()?),
None => (s, PreRelease::None),
};
let split = s.split('.').collect::<Vec<_>>();
match split.len() {
4 => {
let major = split.first().unwrap().parse::<u16>()?;
let minor = split.get(1).unwrap().parse::<u16>()?;
let patch = split.get(2).unwrap().parse::<u16>()?;
let build = split.get(3).unwrap().parse::<u16>()?;
if major > MAX_U12 || minor > MAX_U12 || patch > MAX_U12 || build > MAX_U12 {
return Err(Error::Range);
}
Ok(Self {
major,
minor,
patch,
build,
pre,
})
}
_ => Err(Error::ParseSemver),
}
}
}
impl From<Extended> for u64 {
fn from(value: Extended) -> Self {
let major = u64::from(value.major) << 52;
let minor = u64::from(value.minor) << 40;
let patch = u64::from(value.patch) << 28;
let build = u64::from(value.build) << 16;
let pre = u64::from(u16::from(value.pre));
major | minor | patch | build | pre
}
}
impl TryFrom<u64> for Extended {
type Error = Error;
fn try_from(value: u64) -> Result<Self, Self::Error> {
let mut mask: u64 = 0o7777 << 52;
let major = (value & mask) >> 52;
mask = 0o7777 << 40;
let minor = (value & mask) >> 40;
mask = 0o7777 << 28;
let patch = (value & mask) >> 28;
mask = 0o7777 << 16;
let build = (value & mask) >> 16;
mask = 0o37777;
let p = u16::try_from(value & mask)?;
let pre: PreRelease = p.try_into()?;
Ok(Self {
major: major.try_into()?,
minor: minor.try_into()?,
patch: patch.try_into()?,
build: build.try_into()?,
pre,
})
}
}
impl PartialEq for Extended {
fn eq(&self, other: &Self) -> bool {
self.major == other.major
&& self.minor == other.minor
&& self.patch == other.patch
&& self.build == other.build
&& self.pre == other.pre
}
}
impl Eq for Extended {}
impl Ord for Extended {
fn cmp(&self, other: &Self) -> Ordering {
let a = u64::from(*self);
let b = u64::from(*other);
a.cmp(&b)
}
}
impl PartialOrd for Extended {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl From<Simple> for Extended {
fn from(value: Simple) -> Self {
Self {
major: value.major,
minor: 0,
patch: 0,
build: 0,
pre: value.pre,
}
}
}
impl From<Rapid> for Extended {
fn from(value: Rapid) -> Self {
Self {
major: value.major,
minor: value.minor,
patch: 0,
build: 0,
pre: value.pre,
}
}
}
impl From<SemVer> for Extended {
fn from(value: SemVer) -> Self {
Self {
major: value.major,
minor: value.minor,
patch: value.patch,
build: 0,
pre: value.pre,
}
}
}
impl PartialEq<SemVer> for Extended {
fn eq(&self, other: &SemVer) -> bool {
self.major == other.major
&& self.minor == other.minor
&& self.patch == other.patch
&& self.build == 0
&& self.pre == other.pre
}
}
impl PartialEq<Rapid> for Extended {
fn eq(&self, other: &Rapid) -> bool {
self.major == other.major
&& self.minor == other.minor
&& self.patch == 0
&& self.build == 0
&& self.pre == other.pre
}
}
impl PartialEq<Simple> for Extended {
fn eq(&self, other: &Simple) -> bool {
self.major == other.major
&& self.minor == 0
&& self.patch == 0
&& self.build == 0
&& self.pre == other.pre
}
}
impl PartialEq<GitRev> for Extended {
fn eq(&self, _other: &GitRev) -> bool {
false
}
}
impl PartialOrd<SemVer> for Extended {
fn partial_cmp(&self, other: &SemVer) -> Option<Ordering> {
Some(u64::from(*self).cmp(&u64::from(*other)))
}
}
impl PartialOrd<Rapid> for Extended {
fn partial_cmp(&self, other: &Rapid) -> Option<Ordering> {
Some(u64::from(*self).cmp(&u64::from(*other)))
}
}
impl PartialOrd<Simple> for Extended {
fn partial_cmp(&self, other: &Simple) -> Option<Ordering> {
Some(u64::from(*self).cmp(&u64::from(*other)))
}
}
impl PartialOrd<GitRev> for Extended {
fn partial_cmp(&self, _other: &GitRev) -> Option<Ordering> {
None
}
}
#[cfg(test)]
mod tests {
use std::num::NonZeroU16;
use super::*;
#[test]
fn from_str() {
let mut ex: Extended = "1.2.3.4".parse().unwrap();
assert_eq!(
ex,
Extended {
major: 1,
minor: 2,
patch: 3,
build: 4,
pre: PreRelease::None,
}
);
ex = "3.0.14.1_beta2".parse().unwrap();
assert_eq!(
ex,
Extended {
major: 3,
minor: 0,
patch: 14,
build: 1,
pre: PreRelease::Beta(Some(NonZeroU16::new(2).unwrap())),
}
);
}
#[test]
fn to_string() {
let mut ex = Extended {
major: 2,
minor: 11,
patch: 0,
build: 1,
pre: PreRelease::None,
};
assert_eq!(ex.to_string(), "2.11.0.1");
ex.pre = PreRelease::RC(None);
assert_eq!(ex.to_string(), "2.11.0.1_rc");
ex.pre = PreRelease::Alpha(Some(NonZeroU16::new(3).unwrap()));
assert_eq!(ex.to_string(), "2.11.0.1_alpha3");
}
#[test]
fn eq() {
assert_eq!(
"1.0.2.1_beta1".parse::<Extended>().unwrap(),
"1.0.2.1_beta".parse::<Extended>().unwrap()
);
assert_ne!(
"1.0.2.1_alpha".parse::<Extended>().unwrap(),
"1.0.2.1_alpha2".parse::<Extended>().unwrap()
);
}
#[test]
fn ord() {
let a: Extended = "1.0.14.1".parse().unwrap();
let b: Extended = "1.0.14.1_alpha4".parse().unwrap();
let c: Extended = "1.0.14.1_beta2".parse().unwrap();
let d: Extended = "1.0.14.1_beta3".parse().unwrap();
let e: Extended = "2.0.14.1".parse().unwrap();
let f: Extended = "2.1.14.1".parse().unwrap();
let g: Extended = "2.1.13.1".parse().unwrap();
assert!(a > b);
assert!(b < a);
assert!(a > c);
assert!(b < c);
assert!(c < a);
assert!(c > b);
assert!(d > b);
assert!(d < a);
assert!(e > a);
assert!(f > e);
assert!(g < f);
assert!(d < e);
assert!(e < f);
assert!(f > g);
}
}

View File

@ -1,175 +0,0 @@
use {
crate::{
error::Error, extended::Extended, rapid::Rapid, semver::SemVer, simple::Simple,
version::Kind, version::Version,
},
chrono::{offset::Utc, DateTime},
serde::{Deserialize, Serialize},
std::{cmp::Ordering, fmt, str::FromStr},
};
/// Represents a Git revisionrather than a regular release.
/// A `GitRev` release contains the revision's hash and the Date/Time of the release.
/// Git revisions are not the preferred method of distribution for obvious reasons, as
/// they generally do not represent a stable release of the code. Another drawback is
/// that a Git revision can only be compared against another Git revision, making it
/// impossible to properly order updates between a proper `SemVer` type release and a\
/// Git revision.
/// ### Notes on display formatting
/// The hash as stored in this struct should be the short form revision hash (the first
/// 7 characters of the full hash). The full date and time information is saved minus
/// any fractional seconds. When displaying, the date and time should be converted to a
/// Unix timestamp.
/// ### Parsing from a string
/// In order to parse this struct from a string, two fields `hash` and `datetime` should
/// be separated by a period character '.' and the date/time take the form of a Unix
/// timestamp. This information can be retrieved from git for a given commit using the
/// following git commandline:
/// ```Sh
/// git show --pretty=format:"%h.%at" <hash> | head -n 1
/// ```
/// The resulting string can then be parsed using the `FromStr` trait, which provides
/// both `from_str` and `parse`.
/// ### Comparison - ordering
/// Git revisions are ordered by date/time
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct GitRev {
/// the short revision hash
pub hash: String,
/// the time of the revision commit
pub datetime: DateTime<Utc>,
}
impl fmt::Display for GitRev {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "git_{}.{}", self.hash, self.datetime.timestamp())
}
}
impl PartialOrd for GitRev {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for GitRev {
fn cmp(&self, other: &Self) -> Ordering {
self.datetime.cmp(&other.datetime)
}
}
impl FromStr for GitRev {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(gitrev) = s.strip_prefix("git_") {
if let Some((hash, datetime)) = gitrev.split_once('.') {
if hash.len() == 7 {
let secs: i64 = datetime.parse()?;
let Some(datetime) = DateTime::<Utc>::from_timestamp(secs, 0) else {
return Err(Error::ParseInt);
};
return Ok(Self {
hash: hash.to_string(),
datetime,
});
}
}
}
Err(Error::ParseGitRev)
}
}
impl PartialEq<Extended> for GitRev {
fn eq(&self, _other: &Extended) -> bool {
false
}
}
impl PartialOrd<Extended> for GitRev {
fn partial_cmp(&self, _other: &Extended) -> Option<Ordering> {
None
}
}
impl PartialEq<SemVer> for GitRev {
fn eq(&self, _other: &SemVer) -> bool {
false
}
}
impl PartialOrd<SemVer> for GitRev {
fn partial_cmp(&self, _other: &SemVer) -> Option<Ordering> {
None
}
}
impl PartialEq<Rapid> for GitRev {
fn eq(&self, _other: &Rapid) -> bool {
false
}
}
impl PartialOrd<Rapid> for GitRev {
fn partial_cmp(&self, _other: &Rapid) -> Option<Ordering> {
None
}
}
impl PartialEq<Simple> for GitRev {
fn eq(&self, _other: &Simple) -> bool {
false
}
}
impl PartialOrd<Simple> for GitRev {
fn partial_cmp(&self, _other: &Simple) -> Option<Ordering> {
None
}
}
impl TryFrom<Version> for GitRev {
type Error = Error;
fn try_from(value: Version) -> Result<Self, Self::Error> {
match value.0 {
Kind::GitRev(g) => Ok(g),
_ => Err(Error::ParseGitRev),
}
}
}
#[cfg(test)]
mod test {
use std::{thread, time::Duration};
use super::*;
#[test]
fn ord() {
let a = GitRev {
hash: "aaabxxx".to_string(),
datetime: Utc::now(),
};
thread::sleep(Duration::from_millis(10));
let b = GitRev {
hash: "aaaaxxx".to_string(),
datetime: Utc::now(),
};
assert!(a < b);
}
#[test]
fn from_str() {
let now = DateTime::<Utc>::from_timestamp(Utc::now().timestamp(), 0).unwrap();
let rev: GitRev = format!("git_r2d2xxx.{}", now.timestamp()).parse().unwrap();
println!("Version = {rev}");
assert_eq!(
rev,
GitRev {
hash: "r2d2xxx".to_string(),
datetime: now,
}
);
}
}

View File

@ -3,13 +3,8 @@
mod arch;
mod error;
mod extended;
mod gitrev;
pub mod prelude;
mod prerelease;
mod rapid;
mod semver;
mod simple;
mod version;
pub use version::Version;

View File

@ -1,4 +1 @@
pub use crate::{
arch::Arch, error::Error as VersionError, extended::Extended, gitrev::GitRev, rapid::Rapid,
semver::SemVer, simple::Simple, version::Kind as VersionKind,
};
pub use crate::{arch::Arch, error::Error as VersionError, version::Kind as VersionKind};

View File

@ -1,5 +1,6 @@
use {
crate::{error::Error, MAX_U12},
epoch::DateTime,
serde::{Deserialize, Serialize},
std::{cmp, convert::Into, fmt, num::NonZeroU16, str},
};
@ -10,31 +11,45 @@ pub enum PreRelease {
Alpha(Option<NonZeroU16>),
Beta(Option<NonZeroU16>),
RC(Option<NonZeroU16>),
Git {
hash: [char; 7],
datetime: DateTime,
},
/// `PreRelease::None` is equivalent to a normal release
None,
}
impl PreRelease {
pub fn is_prerelease(self) -> bool {
pub fn is_prerelease(&self) -> bool {
!matches!(self, Self::None)
}
pub fn is_alpha(self) -> bool {
pub fn is_alpha(&self) -> bool {
matches!(self, Self::Alpha(_))
}
pub fn is_beta(self) -> bool {
pub fn is_beta(&self) -> bool {
matches!(self, Self::Beta(_))
}
pub fn is_rc(self) -> bool {
pub fn is_rc(&self) -> bool {
matches!(self, Self::RC(_))
}
pub fn is_gitrev(&self) -> bool {
matches!(
self,
Self::Git {
hash: _,
datetime: _
}
)
}
pub fn number(self) -> Option<u16> {
match self {
Self::Alpha(n) | Self::Beta(n) | Self::RC(n) => n.map(Into::into),
Self::None => None,
_ => None,
}
}
@ -52,7 +67,7 @@ impl PreRelease {
}
None => *n = Some(NonZeroU16::new(2).unwrap()),
},
Self::None => {}
_ => {}
}
Ok(())
}
@ -65,7 +80,7 @@ impl PreRelease {
Self::Alpha(_) => *self = Self::Beta(None),
Self::Beta(_) => *self = Self::RC(None),
Self::RC(_) => *self = Self::None,
Self::None => {}
_ => {}
}
}
}
@ -73,12 +88,19 @@ impl PreRelease {
impl fmt::Display for PreRelease {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Alpha(Some(v)) => write!(f, "alpha{v}"),
Self::Alpha(None) => write!(f, "alpha"),
Self::Beta(Some(v)) => write!(f, "beta{v}"),
Self::Beta(None) => write!(f, "beta"),
Self::RC(Some(v)) => write!(f, "rc{v}"),
Self::RC(None) => write!(f, "rc"),
Self::Alpha(Some(v)) => write!(f, "_alpha{v}"),
Self::Alpha(None) => write!(f, "_alpha"),
Self::Beta(Some(v)) => write!(f, "_beta{v}"),
Self::Beta(None) => write!(f, "_beta"),
Self::RC(Some(v)) => write!(f, "_rc{v}"),
Self::RC(None) => write!(f, "_rc"),
Self::Git { hash, datetime } => {
write!(f, "_git_")?;
for c in hash {
write!(f, "{c}")?;
}
write!(f, ".{}", datetime.timestamp())
}
Self::None => Ok(()),
}
}
@ -142,57 +164,50 @@ impl str::FromStr for PreRelease {
_ => Err(Error::ParsePreRelease),
}
}
s if s.starts_with("git_") => {
let s = s.strip_prefix("git_").unwrap();
let Some((h, ts)) = s.split_once('.') else {
return Err(Error::ParseGitRev);
};
if h.len() != 7 {
return Err(Error::ParseGitRev);
}
let mut hash = ['x'; 7];
for (idx, c) in h.chars().enumerate() {
hash[idx] = c;
}
let ts: i64 = ts.parse()?;
let datetime = DateTime::from_timestamp(ts);
Ok(Self::Git { hash, datetime })
}
_ => Err(Error::ParsePreRelease),
}
}
}
impl From<PreRelease> for u16 {
impl From<PreRelease> for u128 {
fn from(value: PreRelease) -> Self {
let mask: u16 = 0o1_777;
let mask: u16 = 0o3_777;
match value {
PreRelease::Git { hash: _, datetime } => {
0o4_000 << 64 | u128::try_from(datetime.timestamp()).unwrap()
}
PreRelease::Alpha(Some(v)) => {
let v = u16::from(v) & mask;
v | 0o10_000
u128::from(v | 0o10_000) << 64
}
PreRelease::Alpha(None) => 0o2_000,
PreRelease::Alpha(None) => 0o10_000 << 64,
PreRelease::Beta(Some(v)) => {
let v = u16::from(v) & mask;
v | 0o20_000
u128::from(v | 0o20_000) << 64
}
PreRelease::Beta(None) => 0o4_000,
PreRelease::Beta(None) => 0o20_000 << 64,
PreRelease::RC(Some(v)) => {
let v = u16::from(v) & mask;
v | 0o40_000
u128::from(v | 0o40_000) << 64
}
PreRelease::RC(None) => 0o10_000,
PreRelease::None => 0o100_000,
}
}
}
impl TryFrom<u16> for PreRelease {
type Error = crate::error::Error;
fn try_from(value: u16) -> Result<Self, crate::error::Error> {
let mask = 0o1_777;
let v = value & mask;
let flag = value & !mask;
match flag {
0o10_000 => {
let v = if v > 0 { Some(v.try_into()?) } else { None };
Ok(Self::Alpha(v))
}
0o20_000 => {
let v = if v > 0 { Some(v.try_into()?) } else { None };
Ok(Self::Beta(v))
}
0o40_000 => {
let v = if v > 0 { Some(v.try_into()?) } else { None };
Ok(Self::RC(v))
}
0o100_000 => Ok(Self::None),
_ => Err(Error::FromUint),
PreRelease::RC(None) => 0o40_000 << 64,
PreRelease::None => 0o100_000 << 64,
}
}
}
@ -211,8 +226,8 @@ impl PartialEq for PreRelease {
n == 1
}
_ => {
let a: u16 = (*self).into();
let b: u16 = (*other).into();
let a: u128 = (*self).into();
let b: u128 = (*other).into();
a == b
}
}
@ -223,8 +238,8 @@ impl Eq for PreRelease {}
impl cmp::Ord for PreRelease {
fn cmp(&self, other: &Self) -> cmp::Ordering {
let a: u16 = (*self).into();
let b: u16 = (*other).into();
let a: u128 = (*self).into();
let b: u128 = (*other).into();
a.cmp(&b)
}
}

View File

@ -1,159 +0,0 @@
use {
crate::{
error::Error, extended::Extended, gitrev::GitRev, prerelease::PreRelease, semver::SemVer,
simple::Simple, MAX_U12,
},
serde::{Deserialize, Serialize},
std::{cmp::Ordering, fmt, str::FromStr},
};
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct Rapid {
pub major: u16,
pub minor: u16,
pub pre: PreRelease,
}
impl fmt::Display for Rapid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}", self.major, self.minor)?;
match self.pre {
PreRelease::None => Ok(()),
p => write!(f, "_{p}"),
}
}
}
impl FromStr for Rapid {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (s, pre) = match s.split_once('_') {
Some((a, b)) => (a, b.parse::<PreRelease>()?),
None => (s, PreRelease::None),
};
let split = s.split('.').collect::<Vec<_>>();
match split.len() {
2 => {
let major = split.first().unwrap().parse::<u16>()?;
let minor = split.get(1).unwrap().parse::<u16>()?;
if major > MAX_U12 || minor > MAX_U12 {
return Err(Error::Range);
}
Ok(Self { major, minor, pre })
}
_ => Err(Error::ParseSemver),
}
}
}
impl From<Rapid> for u64 {
fn from(value: Rapid) -> Self {
let major = u64::from(value.major) << 52;
let minor = u64::from(value.minor) << 40;
let pre = u64::from(u16::from(value.pre));
major | minor | pre
}
}
impl TryFrom<u64> for Rapid {
type Error = Error;
fn try_from(value: u64) -> Result<Self, Self::Error> {
let mut mask: u64 = 0o7777 << 52;
let major = (value & mask) >> 52;
mask = 0o7777 << 40;
let minor = (value & mask) >> 40;
mask = 0o37777;
let p = u16::try_from(value & mask)?;
let pre: PreRelease = p.try_into()?;
Ok(Self {
major: major.try_into()?,
minor: minor.try_into()?,
pre,
})
}
}
impl PartialEq for Rapid {
fn eq(&self, other: &Self) -> bool {
self.major == other.major && self.minor == other.minor && self.pre == other.pre
}
}
impl Eq for Rapid {}
impl Ord for Rapid {
fn cmp(&self, other: &Self) -> Ordering {
let a = u64::from(*self);
let b = u64::from(*other);
a.cmp(&b)
}
}
impl PartialOrd for Rapid {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq<Extended> for Rapid {
fn eq(&self, other: &Extended) -> bool {
self.major == other.major
&& self.minor == other.minor
&& other.patch == 0
&& other.build == 0
&& self.pre == other.pre
}
}
impl PartialEq<SemVer> for Rapid {
fn eq(&self, other: &SemVer) -> bool {
self.major == other.major
&& self.minor == other.minor
&& other.patch == 0
&& self.pre == other.pre
}
}
impl PartialEq<Simple> for Rapid {
fn eq(&self, other: &Simple) -> bool {
self.major == other.major && self.minor == 0 && self.pre == other.pre
}
}
impl PartialEq<GitRev> for Rapid {
fn eq(&self, _other: &GitRev) -> bool {
false
}
}
impl PartialOrd<Extended> for Rapid {
fn partial_cmp(&self, other: &Extended) -> Option<Ordering> {
let a = u64::from(*self);
let b = u64::from(*other);
a.partial_cmp(&b)
}
}
impl PartialOrd<SemVer> for Rapid {
fn partial_cmp(&self, other: &SemVer) -> Option<Ordering> {
let a = u64::from(*self);
let b = u64::from(*other);
a.partial_cmp(&b)
}
}
impl PartialOrd<Simple> for Rapid {
fn partial_cmp(&self, other: &Simple) -> Option<Ordering> {
let a = u64::from(*self);
let b = u64::from(*other);
a.partial_cmp(&b)
}
}
impl PartialOrd<GitRev> for Rapid {
fn partial_cmp(&self, _other: &GitRev) -> Option<Ordering> {
None
}
}

View File

@ -1,260 +0,0 @@
use {
crate::{
error::Error, extended::Extended, gitrev::GitRev, prerelease::PreRelease, rapid::Rapid,
simple::Simple, MAX_U12,
},
serde::{Deserialize, Serialize},
std::{cmp::Ordering, fmt, str::FromStr},
};
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct SemVer {
pub major: u16,
pub minor: u16,
pub patch: u16,
pub pre: PreRelease,
}
impl fmt::Display for SemVer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
match self.pre {
PreRelease::None => Ok(()),
p => write!(f, "_{p}"),
}
}
}
impl FromStr for SemVer {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (s, pre) = match s.split_once('_') {
Some((a, b)) => (a, b.parse::<PreRelease>()?),
None => (s, PreRelease::None),
};
let split = s.split('.').collect::<Vec<_>>();
match split.len() {
3 => {
let major = split.first().unwrap().parse::<u16>()?;
let minor = split.get(1).unwrap().parse::<u16>()?;
let patch = split.get(2).unwrap().parse::<u16>()?;
if major > MAX_U12 || minor > MAX_U12 || patch > MAX_U12 {
return Err(Error::Range);
}
Ok(Self {
major,
minor,
patch,
pre,
})
}
_ => Err(Error::ParseSemver),
}
}
}
impl From<SemVer> for u64 {
fn from(value: SemVer) -> Self {
let major = u64::from(value.major) << 52;
let minor = u64::from(value.minor) << 40;
let patch = u64::from(value.patch) << 28;
let pre = u64::from(u16::from(value.pre));
major | minor | patch | pre
}
}
impl TryFrom<u64> for SemVer {
type Error = Error;
fn try_from(value: u64) -> Result<Self, Self::Error> {
let mut mask: u64 = 0o7777 << 52;
let major = (value & mask) >> 52;
mask = 0o7777 << 40;
let minor = (value & mask) >> 40;
mask = 0o7777 << 28;
let patch = (value & mask) >> 28;
mask = 0o37777;
let p = u16::try_from(value & mask)?;
let pre: PreRelease = p.try_into()?;
Ok(Self {
major: major.try_into()?,
minor: minor.try_into()?,
patch: patch.try_into()?,
pre,
})
}
}
impl PartialEq for SemVer {
fn eq(&self, other: &Self) -> bool {
self.major == other.major
&& self.minor == other.minor
&& self.patch == other.patch
&& self.pre == other.pre
}
}
impl Eq for SemVer {}
impl Ord for SemVer {
fn cmp(&self, other: &Self) -> Ordering {
let a = u64::from(*self);
let b = u64::from(*other);
a.cmp(&b)
}
}
impl PartialOrd for SemVer {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq<Extended> for SemVer {
fn eq(&self, other: &Extended) -> bool {
self.major == other.major
&& self.minor == other.minor
&& self.patch == other.patch
&& other.build == 0
&& self.pre == other.pre
}
}
impl PartialEq<Rapid> for SemVer {
fn eq(&self, other: &Rapid) -> bool {
self.major == other.major
&& self.minor == other.minor
&& self.patch == 0
&& self.pre == other.pre
}
}
impl PartialEq<Simple> for SemVer {
fn eq(&self, other: &Simple) -> bool {
self.major == other.major && self.minor == 0 && self.patch == 0 && self.pre == other.pre
}
}
impl PartialEq<GitRev> for SemVer {
fn eq(&self, _other: &GitRev) -> bool {
false
}
}
impl PartialOrd<Extended> for SemVer {
fn partial_cmp(&self, other: &Extended) -> Option<Ordering> {
let a = u64::from(*self);
let b = u64::from(*other);
Some(a.cmp(&b))
}
}
impl PartialOrd<Rapid> for SemVer {
fn partial_cmp(&self, other: &Rapid) -> Option<Ordering> {
let a = u64::from(*self);
let b = u64::from(*other);
Some(a.cmp(&b))
}
}
impl PartialOrd<Simple> for SemVer {
fn partial_cmp(&self, other: &Simple) -> Option<Ordering> {
let a = u64::from(*self);
let b = u64::from(*other);
Some(a.cmp(&b))
}
}
impl PartialOrd<GitRev> for SemVer {
fn partial_cmp(&self, _other: &GitRev) -> Option<Ordering> {
None
}
}
#[cfg(test)]
mod tests {
use std::num::NonZeroU16;
use super::*;
#[test]
fn from_str() {
let mut s: SemVer = "1.2.3".parse().unwrap();
assert_eq!(
s,
SemVer {
major: 1,
minor: 2,
patch: 3,
pre: PreRelease::None,
}
);
s = "0.3.0_alpha4".parse().unwrap();
assert_eq!(
s,
SemVer {
major: 0,
minor: 3,
patch: 0,
pre: PreRelease::Alpha(Some(NonZeroU16::new(4).unwrap())),
}
)
}
#[test]
fn to_string() {
let mut s = SemVer {
major: 1,
minor: 0,
patch: 2,
pre: PreRelease::None,
};
assert_eq!(s.to_string(), "1.0.2");
s = SemVer {
major: 2,
minor: 1,
patch: 14,
pre: PreRelease::Beta(Some(NonZeroU16::new(2).unwrap())),
};
assert_eq!(s.to_string(), "2.1.14_beta2");
}
#[test]
fn to_from_u64() {
let sem = SemVer {
major: 1,
minor: 0,
patch: 11,
pre: PreRelease::Beta(Some(NonZeroU16::new(2).unwrap())),
};
assert_eq!(SemVer::try_from(u64::from(sem)).unwrap(), sem);
}
#[test]
fn eq() {
assert_eq!(
"1.0.0_alpha".parse::<SemVer>().unwrap(),
"1.0.0_alpha1".parse::<SemVer>().unwrap(),
);
assert_eq!(
"2.1.3_beta".parse::<SemVer>().unwrap(),
"2.1.3_beta1".parse::<SemVer>().unwrap(),
);
assert_eq!(
"1.11.0_rc".parse::<SemVer>().unwrap(),
"1.11.0_rc1".parse::<SemVer>().unwrap(),
);
}
#[test]
fn ord() {
let a: SemVer = "1.0.2".parse().unwrap();
let b: SemVer = "1.0.3".parse().unwrap();
let c: SemVer = "1.0.2_alpha1".parse().unwrap();
let d: SemVer = "1.0.2_alpha2".parse().unwrap();
assert!(a < b);
assert!(c < a);
assert!(d > c);
}
}

View File

@ -1,144 +0,0 @@
use {
crate::{
error::Error, extended::Extended, gitrev::GitRev, prerelease::PreRelease, rapid::Rapid,
semver::SemVer, MAX_U12,
},
serde::{Deserialize, Serialize},
std::{cmp::Ordering, fmt, str::FromStr},
};
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct Simple {
pub major: u16,
pub pre: PreRelease,
}
impl fmt::Display for Simple {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.major)?;
match self.pre {
PreRelease::None => Ok(()),
p => write!(f, "_{p}"),
}
}
}
impl FromStr for Simple {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (s, pre) = match s.split_once('_') {
Some((a, b)) => (a, b.parse::<PreRelease>()?),
None => (s, PreRelease::None),
};
let major = s.parse::<u16>()?;
if major > MAX_U12 {
return Err(Error::Range);
}
Ok(Self { major, pre })
}
}
impl From<Simple> for u64 {
fn from(value: Simple) -> Self {
let major = u64::from(value.major) << 52;
let pre = u64::from(u16::from(value.pre));
major | pre
}
}
impl TryFrom<u64> for Simple {
type Error = Error;
fn try_from(value: u64) -> Result<Self, Self::Error> {
let mut mask: u64 = 0o7777 << 52;
let major = (value & mask) >> 52;
mask = 0o37777;
let p = u16::try_from(value & mask)?;
let pre: PreRelease = p.try_into()?;
Ok(Self {
major: major.try_into()?,
pre,
})
}
}
impl PartialEq for Simple {
fn eq(&self, other: &Self) -> bool {
self.major == other.major && self.pre == other.pre
}
}
impl Eq for Simple {}
impl Ord for Simple {
fn cmp(&self, other: &Self) -> Ordering {
let a = u64::from(*self);
let b = u64::from(*other);
a.cmp(&b)
}
}
impl PartialOrd for Simple {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq<Extended> for Simple {
fn eq(&self, other: &Extended) -> bool {
self.major == other.major
&& other.minor == 0
&& other.patch == 0
&& other.build == 0
&& self.pre == other.pre
}
}
impl PartialEq<SemVer> for Simple {
fn eq(&self, other: &SemVer) -> bool {
self.major == other.major && other.minor == 0 && other.patch == 0 && self.pre == other.pre
}
}
impl PartialEq<Rapid> for Simple {
fn eq(&self, other: &Rapid) -> bool {
self.major == other.major && other.minor == 0 && self.pre == other.pre
}
}
impl PartialEq<GitRev> for Simple {
fn eq(&self, _other: &GitRev) -> bool {
false
}
}
impl PartialOrd<Extended> for Simple {
fn partial_cmp(&self, other: &Extended) -> Option<Ordering> {
let a = u64::from(*self);
let b = u64::from(*other);
Some(a.cmp(&b))
}
}
impl PartialOrd<SemVer> for Simple {
fn partial_cmp(&self, other: &SemVer) -> Option<Ordering> {
let a = u64::from(*self);
let b = u64::from(*other);
Some(a.cmp(&b))
}
}
impl PartialOrd<Rapid> for Simple {
fn partial_cmp(&self, other: &Rapid) -> Option<Ordering> {
let a = u64::from(*self);
let b = u64::from(*other);
Some(a.cmp(&b))
}
}
impl PartialOrd<GitRev> for Simple {
fn partial_cmp(&self, _other: &GitRev) -> Option<Ordering> {
None
}
}

View File

@ -1,202 +1,215 @@
use {
crate::{
arch::Arch, error::Error, extended::Extended, gitrev::GitRev, rapid::Rapid, semver::SemVer,
simple::Simple,
},
crate::{arch::Arch, error::Error, prerelease::PreRelease, MAX_U12},
serde::{Deserialize, Serialize},
std::{fmt, str::FromStr},
std::{cmp, fmt, str},
};
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub enum Kind {
Simple(Simple),
Rapid(Rapid),
SemVer(SemVer),
Extended(Extended),
GitRev(GitRev),
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, Debug, Deserialize, Serialize)]
pub struct Version(pub Kind, pub Arch);
#[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 {
match &self.0 {
Kind::Simple(s) => write!(f, "{s}-{}", self.1),
Kind::Rapid(r) => write!(f, "{r}-{}", self.1),
Kind::SemVer(s) => write!(f, "{s}-{}", self.1),
Kind::Extended(x) => write!(f, "{x}-{}", self.1),
Kind::GitRev(g) => write!(f, "{g}-{}", self.1),
}
write!(f, "{}{}-{}", self.kind, self.pre, self.arch)
}
}
impl FromStr for Version {
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, Some(arch))) = s.split_once('-').map(|(s, a)| (s, a.parse::<Arch>().ok()))
else {
let Some((s, arch)) = s.split_once('-') else {
return Err(Error::ParseArch);
};
if let Ok(v) = Simple::from_str(s) {
Ok(Self(Kind::Simple(v), arch))
} else if let Ok(v) = Rapid::from_str(s) {
Ok(Self(Kind::Rapid(v), arch))
} else if let Ok(v) = SemVer::from_str(s) {
Ok(Self(Kind::SemVer(v), arch))
} else if let Ok(v) = Extended::from_str(s) {
Ok(Self(Kind::Extended(v), arch))
} else if let Ok(v) = GitRev::from_str(s) {
Ok(Self(Kind::GitRev(v), arch))
} else {
Err(Error::ParseVersion)
}
}
}
impl From<Simple> for Kind {
fn from(value: Simple) -> Self {
Self::Simple(value)
}
}
impl From<Rapid> for Kind {
fn from(value: Rapid) -> Self {
Self::Rapid(value)
}
}
impl From<SemVer> for Kind {
fn from(value: SemVer) -> Self {
Self::SemVer(value)
}
}
impl From<Extended> for Kind {
fn from(value: Extended) -> Self {
Self::Extended(value)
}
}
impl From<GitRev> for Kind {
fn from(value: GitRev) -> Self {
Self::GitRev(value)
}
}
impl PartialEq for Kind {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Simple(a), Self::Simple(b)) => a == b,
(Self::Simple(a), Self::Rapid(b)) => a == b,
(Self::Simple(a), Self::SemVer(b)) => a == b,
(Self::Simple(a), Self::Extended(b)) => a == b,
(Self::Rapid(a), Self::Simple(b)) => a == b,
(Self::Rapid(a), Self::Rapid(b)) => a == b,
(Self::Rapid(a), Self::SemVer(b)) => a == b,
(Self::Rapid(a), Self::Extended(b)) => a == b,
(Self::SemVer(a), Self::Simple(b)) => a == b,
(Self::SemVer(a), Self::Rapid(b)) => a == b,
(Self::SemVer(a), Self::SemVer(b)) => a == b,
(Self::SemVer(a), Self::Extended(b)) => a == b,
(Self::Extended(a), Self::Simple(b)) => a == b,
(Self::Extended(a), Self::Rapid(b)) => a == b,
(Self::Extended(a), Self::SemVer(b)) => a == b,
(Self::Extended(a), Self::Extended(b)) => a == b,
(Self::GitRev(a), Self::GitRev(b)) => a == b,
_ => false,
}
}
}
impl Eq for Kind {}
impl PartialOrd for Kind {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(Self::Simple(a), Self::Simple(b)) => a.partial_cmp(b),
(Self::Simple(a), Self::Rapid(b)) => a.partial_cmp(b),
(Self::Simple(a), Self::SemVer(b)) => a.partial_cmp(b),
(Self::Simple(a), Self::Extended(b)) => a.partial_cmp(b),
(Self::Rapid(a), Self::Simple(b)) => a.partial_cmp(b),
(Self::Rapid(a), Self::Rapid(b)) => a.partial_cmp(b),
(Self::Rapid(a), Self::SemVer(b)) => a.partial_cmp(b),
(Self::Rapid(a), Self::Extended(b)) => a.partial_cmp(b),
(Self::SemVer(a), Self::Simple(b)) => a.partial_cmp(b),
(Self::SemVer(a), Self::Rapid(b)) => a.partial_cmp(b),
(Self::SemVer(a), Self::SemVer(b)) => a.partial_cmp(b),
(Self::SemVer(a), Self::Extended(b)) => a.partial_cmp(b),
(Self::Extended(a), Self::Simple(b)) => a.partial_cmp(b),
(Self::Extended(a), Self::Rapid(b)) => a.partial_cmp(b),
(Self::Extended(a), Self::SemVer(b)) => a.partial_cmp(b),
(Self::Extended(a), Self::Extended(b)) => a.partial_cmp(b),
(Self::GitRev(a), Self::GitRev(b)) => a.partial_cmp(b),
_ => None,
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 {
self.0 == other.0 && self.1 == other.1
u128::from(*self) == u128::from(*other) && self.arch == other.arch
}
}
impl Eq for Version {}
impl Ord for Version {
fn cmp(&self, other: &Self) -> cmp::Ordering {
u128::from(*self).cmp(&u128::from(*other))
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
if self.1 == other.1 {
self.0.partial_cmp(&other.0)
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
if self.arch == other.arch {
Some(self.cmp(other))
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use chrono::{DateTime, Utc};
use std::num::NonZeroU16;
use super::*;
#[test]
fn from_str() {
let mut version: Version = "2.4.1_alpha2-aarch64".parse().unwrap();
assert_eq!(
version,
Version(
Kind::SemVer(SemVer {
major: 2,
minor: 4,
patch: 1,
pre: crate::prerelease::PreRelease::Alpha(Some(NonZeroU16::new(2).unwrap()))
}),
Arch::Arm64
)
);
version = "6.4-i486".parse().unwrap();
assert_eq!(
version,
Version(
Kind::Rapid(Rapid {
major: 6,
minor: 4,
pre: crate::prerelease::PreRelease::None,
}),
Arch::X86,
)
);
version = "git_r2d2xxx.1705881493-amd64".parse().unwrap();
assert_eq!(
version,
Version(
Kind::GitRev(GitRev {
hash: "r2d2xxx".to_string(),
datetime: DateTime::<Utc>::from_timestamp(1705881493, 0).unwrap(),
}),
Arch::X86_64,
)
);
}
}