Compare commits

..

No commits in common. "b40178ede24159251aabb5100afa9d223acd5467" and "12ac9a84ce874a1cbe994147fed05d351758e76d" have entirely different histories.

13 changed files with 1342 additions and 356 deletions

96
Cargo.lock generated
View File

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

View File

@ -7,5 +7,4 @@ edition = "2021"
[dependencies] [dependencies]
chrono = { version = "0.4", features = ["serde"] } 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"] } serde = { version = "1.0", features = ["derive"] }

View File

@ -56,6 +56,19 @@ fn main() -> Result<(), VersionError> {
Ok(()) Ok(())
} }
``` ```
### Git
Git revisions may be used, but may only be compared with other Git revisions. Git
revisions are ordered by date/time and formatted as Unix timestamps.
```
use version::{prelude::VersionError, Version};
fn main() -> Result<(), VersionError> {
let va: Version = "git_r2d2xxx.1705881508-i486".parse()?;
let vb: Version = "git_c3p0xxx.1705881612-i486".parse()?;
assert!(va < vb);
Ok(())
}
```
## Internals ## Internals
When comparing version numbers for equality or ordering, the semver-like versions When comparing version numbers for equality or ordering, the semver-like versions
@ -66,12 +79,11 @@ the prerelease number. The next four bits are used as bitflags representing the
type of prerelease, which may be `PreRelease::None` (which gets the highest bit in type of prerelease, which may be `PreRelease::None` (which gets the highest bit in
the set). the set).
| bits | 15 | 14 | 13 | 12 | 11 | 0-10 | | bits | 15 | 14 | 13 | 12 | 0-11 |
| ---- | --- | --- | --- | --- | --- | ---- | | ---- | --- | --- | --- | --- | ---- |
| use | PreRelease::None | PreRelease::Rc | PreRelease::Beta | PreRelease::Alpha | PreRelease::Git | Numerical component | | use | PreRelease::None | PreRelease::Rc | PreRelease::Beta | PreRelease::Alpha | Numerical component |
This gives an upper limit of 2^11 or 2048 for the numerical component of the prerelease, This gives an upper limit of 2^12 or 4096 for the numerical component, which is more
and 2^12 or 4096 for each SemVer field, which is more
than adequate. By placing the flags in this way, alpha releases will always be lower than adequate. By placing the flags in this way, alpha releases will always be lower
than beta, which will be lower than release candidates, which will be lower than no than beta, which will be lower than release candidates, which will be lower than no
pre-release versions. pre-release versions.

View File

@ -10,15 +10,7 @@ pub enum Arch {
Any, Any,
Arm, Arm,
Arm64, Arm64,
Loongson,
Mips32,
Mips64,
PowerPC,
PowerPC64,
RiscV64, RiscV64,
S390x,
Sparc,
Sparc64,
X86, X86,
X86_64, X86_64,
} }
@ -32,15 +24,7 @@ impl fmt::Display for Arch {
Self::Any => "any", Self::Any => "any",
Self::Arm => "armv7l", Self::Arm => "armv7l",
Self::Arm64 => "aarch64", Self::Arm64 => "aarch64",
Self::Loongson => "loongson",
Self::Mips32 => "mips32",
Self::Mips64 => "mips64",
Self::PowerPC => "ppc",
Self::PowerPC64 => "ppc64",
Self::RiscV64 => "riscv64", Self::RiscV64 => "riscv64",
Self::S390x => "s390x",
Self::Sparc => "sparc",
Self::Sparc64 => "sparc64",
Self::X86 => "i486", Self::X86 => "i486",
Self::X86_64 => "x86_64", Self::X86_64 => "x86_64",
} }
@ -56,13 +40,7 @@ impl str::FromStr for Arch {
"any" | "Any" | "noarch" => Ok(Self::Any), "any" | "Any" | "noarch" => Ok(Self::Any),
"armv7l" | "ArmV7L" | "Armv7L" => Ok(Self::Arm), "armv7l" | "ArmV7L" | "Armv7L" => Ok(Self::Arm),
"arm64" | "Arm64" | "aarch64" | "Aarch64" => Ok(Self::Arm64), "arm64" | "Arm64" | "aarch64" | "Aarch64" => Ok(Self::Arm64),
"Loongson" | "loongson" => Ok(Self::Loongson),
"Mips" | "mips" | "Mips32" | "mips32" => Ok(Self::Mips32),
"Mips64" | "mips64" => Ok(Self::Mips64),
"PowerPC" | "powerpc" | "PPC" | "ppc" => Ok(Self::PowerPC),
"PowerPC64" | "powerpc64" | "PPC64" | "ppc64" => Ok(Self::PowerPC64),
"RiscV64" | "riscv64" => Ok(Self::RiscV64), "RiscV64" | "riscv64" => Ok(Self::RiscV64),
"S390x" | "s390x" => Ok(Self::S390x),
"x86" | "X86" | "i486" => Ok(Self::X86), "x86" | "X86" | "i486" => Ok(Self::X86),
"X86_64" | "Amd64" | "x86_64" | "amd64" => Ok(Self::X86_64), "X86_64" | "Amd64" | "x86_64" | "amd64" => Ok(Self::X86_64),
_ => Err(Error::ParseArch), _ => Err(Error::ParseArch),

306
src/extended.rs Normal file
View File

@ -0,0 +1,306 @@
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);
}
}

175
src/gitrev.rs Normal file
View File

@ -0,0 +1,175 @@
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,8 +3,13 @@
mod arch; mod arch;
mod error; mod error;
mod extended;
mod gitrev;
pub mod prelude; pub mod prelude;
mod prerelease; mod prerelease;
mod rapid;
mod semver;
mod simple;
mod version; mod version;
pub use version::Version; pub use version::Version;

View File

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

View File

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

159
src/rapid.rs Normal file
View File

@ -0,0 +1,159 @@
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
}
}

260
src/semver.rs Normal file
View File

@ -0,0 +1,260 @@
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);
}
}

144
src/simple.rs Normal file
View File

@ -0,0 +1,144 @@
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,207 +1,152 @@
use { use {
crate::{arch::Arch, error::Error, prerelease::PreRelease, MAX_U12}, crate::{
arch::Arch, error::Error, extended::Extended, gitrev::GitRev, rapid::Rapid, semver::SemVer,
simple::Simple,
},
serde::{Deserialize, Serialize}, serde::{Deserialize, Serialize},
std::{cmp, fmt, str}, std::{fmt, str::FromStr},
}; };
#[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub enum Kind { pub enum Kind {
Simple { Simple(Simple),
major: u16, Rapid(Rapid),
}, SemVer(SemVer),
Rapid { Extended(Extended),
major: u16, GitRev(GitRev),
minor: u16,
},
SemVer {
major: u16,
minor: u16,
patch: u16,
},
Extended {
major: u16,
minor: u16,
patch: u16,
build: u16,
},
} }
#[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Version { pub struct Version(pub Kind, pub Arch);
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 { impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}-{}", self.kind, self.pre, self.arch) 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),
}
} }
} }
impl From<Version> for u128 { impl FromStr for Version {
fn from(value: Version) -> Self {
u128::from(value.kind) | u128::from(value.pre)
}
}
impl str::FromStr for Version {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((s, arch)) = s.split_once('-') else { let Some((s, Some(arch))) = s.split_once('-').map(|(s, a)| (s, a.parse::<Arch>().ok()))
else {
return Err(Error::ParseArch); return Err(Error::ParseArch);
}; };
let arch: Arch = arch.parse()?; if let Ok(v) = Simple::from_str(s) {
let (s, pre) = match s.split_once('_') { Ok(Self(Kind::Simple(v), arch))
Some((a, b)) => (a, b.parse::<PreRelease>()?), } else if let Ok(v) = Rapid::from_str(s) {
None => (s, PreRelease::None), Ok(Self(Kind::Rapid(v), arch))
}; } else if let Ok(v) = SemVer::from_str(s) {
let mut split = s.split('.'); Ok(Self(Kind::SemVer(v), arch))
let Some(Ok(major)) = split.next().map(|m| m.parse::<u16>()) else { } else if let Ok(v) = Extended::from_str(s) {
return Err(Error::ParseSemver); Ok(Self(Kind::Extended(v), arch))
}; } else if let Ok(v) = GitRev::from_str(s) {
if major > MAX_U12 { Ok(Self(Kind::GitRev(v), arch))
return Err(Error::Range);
};
let minor: u16 = match split.next().map(|m| m.parse()) {
Some(Ok(m)) => {
if m <= MAX_U12 {
m
} else { } else {
return Err(Error::Range); Err(Error::ParseVersion)
} }
} }
Some(Err(e)) => return Err(e.into()), }
None => {
return Ok(Self { impl From<Simple> for Kind {
kind: Kind::Simple { major }, fn from(value: Simple) -> Self {
pre, Self::Simple(value)
arch,
})
} }
}; }
let patch: u16 = match split.next().map(|p| p.parse()) {
Some(Ok(p)) => { impl From<Rapid> for Kind {
if p < MAX_U12 { fn from(value: Rapid) -> Self {
p Self::Rapid(value)
} else { }
return Err(Error::Range); }
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,
} }
} }
Some(Err(e)) => return Err(e.into()), }
None => {
return Ok(Self { impl Eq for Kind {}
kind: Kind::Rapid { major, minor },
pre, impl PartialOrd for Kind {
arch, 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),
let build: u16 = match split.next().map(|b| b.parse()) { (Self::Simple(a), Self::SemVer(b)) => a.partial_cmp(b),
Some(Ok(b)) => { (Self::Simple(a), Self::Extended(b)) => a.partial_cmp(b),
if b <= MAX_U12 { (Self::Rapid(a), Self::Simple(b)) => a.partial_cmp(b),
b (Self::Rapid(a), Self::Rapid(b)) => a.partial_cmp(b),
} else { (Self::Rapid(a), Self::SemVer(b)) => a.partial_cmp(b),
return Err(Error::Range); (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),
Some(Err(e)) => return Err(e.into()), (Self::SemVer(a), Self::SemVer(b)) => a.partial_cmp(b),
None => { (Self::SemVer(a), Self::Extended(b)) => a.partial_cmp(b),
return Ok(Self { (Self::Extended(a), Self::Simple(b)) => a.partial_cmp(b),
kind: Kind::SemVer { (Self::Extended(a), Self::Rapid(b)) => a.partial_cmp(b),
major, (Self::Extended(a), Self::SemVer(b)) => a.partial_cmp(b),
minor, (Self::Extended(a), Self::Extended(b)) => a.partial_cmp(b),
patch, (Self::GitRev(a), Self::GitRev(b)) => a.partial_cmp(b),
}, _ => None,
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 { impl PartialEq for Version {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
u128::from(*self) == u128::from(*other) && self.arch == other.arch self.0 == other.0 && self.1 == other.1
} }
} }
impl Eq for Version {}
impl PartialOrd for Version { impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
if self.arch == other.arch { if self.1 == other.1 {
Some(u128::from(*self).cmp(&u128::from(*other))) self.0.partial_cmp(&other.0)
} else { } else {
None None
} }
@ -210,81 +155,48 @@ impl PartialOrd for Version {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use chrono::{DateTime, Utc};
use std::num::NonZeroU16;
use super::*; use super::*;
use std::{num::NonZeroU16, str::FromStr};
#[test] #[test]
fn from_str() { fn from_str() {
let mut version: Version = "2.4.1_alpha2-aarch64".parse().unwrap(); let mut version: Version = "2.4.1_alpha2-aarch64".parse().unwrap();
assert_eq!( assert_eq!(
version, version,
Version { Version(
kind: Kind::SemVer { Kind::SemVer(SemVer {
major: 2, major: 2,
minor: 4, minor: 4,
patch: 1 patch: 1,
}, pre: crate::prerelease::PreRelease::Alpha(Some(NonZeroU16::new(2).unwrap()))
pre: PreRelease::Alpha(Some(NonZeroU16::new(2).unwrap())), }),
arch: Arch::Arm64, Arch::Arm64
} )
); );
version = "6.4-i486".parse().unwrap(); version = "6.4-i486".parse().unwrap();
assert_eq!( assert_eq!(
version, version,
Version { Version(
kind: Kind::Rapid { major: 6, minor: 4 }, Kind::Rapid(Rapid {
pre: PreRelease::None, major: 6,
arch: Arch::X86, minor: 4,
} pre: crate::prerelease::PreRelease::None,
}),
Arch::X86,
)
); );
version = "3.14.6_git_r2d2xxx.1705881493-amd64".parse().unwrap(); version = "git_r2d2xxx.1705881493-amd64".parse().unwrap();
assert_eq!( assert_eq!(
version, version,
Version { Version(
kind: Kind::SemVer { Kind::GitRev(GitRev {
major: 3, hash: "r2d2xxx".to_string(),
minor: 14, datetime: DateTime::<Utc>::from_timestamp(1705881493, 0).unwrap(),
patch: 6 }),
}, Arch::X86_64,
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());
}
#[test]
fn cmp() {
let astr = "3.14.0-x86_64";
let bstr = "3.14.0_alpha1-x86_64";
let cstr = "3.14_alpha1-amd64";
let dstr = "3.14.0_beta3-x86_64";
let estr = "3.14.0_git_c3poxxx.1705881493-x86_64";
let fstr = "3.14.0_git_r2d2xxx.1705900000-x86_64";
assert!(Version::from_str(astr).unwrap() > Version::from_str(bstr).unwrap());
assert!(Version::from_str(cstr).unwrap() == Version::from_str(bstr).unwrap());
assert!(Version::from_str(cstr).unwrap() < Version::from_str(dstr).unwrap());
assert!(Version::from_str(dstr).unwrap() > Version::from_str(estr).unwrap());
assert!(Version::from_str(fstr).unwrap() > Version::from_str(estr).unwrap());
} }
} }