Compare commits

...

2 Commits

Author SHA1 Message Date
17e52c2b01 Fix failing doc test, adjust README 2024-01-31 22:59:31 -05:00
98b74bd2a7 Initial refactor 2024-01-31 22:36:20 -05:00
13 changed files with 302 additions and 1363 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

@ -56,19 +56,6 @@ fn main() -> Result<(), VersionError> {
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
When comparing version numbers for equality or ordering, the semver-like versions
@ -79,11 +66,12 @@ 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
the set).
| bits | 15 | 14 | 13 | 12 | 0-11 |
| ---- | --- | --- | --- | --- | ---- |
| use | PreRelease::None | PreRelease::Rc | PreRelease::Beta | PreRelease::Alpha | Numerical component |
| bits | 15 | 14 | 13 | 12 | 11 | 0-10 |
| ---- | --- | --- | --- | --- | --- | ---- |
| use | PreRelease::None | PreRelease::Rc | PreRelease::Beta | PreRelease::Alpha | PreRelease::Git | Numerical component |
This gives an upper limit of 2^12 or 4096 for the numerical component, which is more
This gives an upper limit of 2^11 or 2048 for the numerical component of the prerelease,
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 beta, which will be lower than release candidates, which will be lower than no
pre-release versions.

View File

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

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))
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 {
Err(Error::ParseVersion)
return Err(Error::Range);
}
}
Some(Err(e)) => return Err(e.into()),
None => {
return Ok(Self {
kind: Kind::Simple { major },
pre,
arch,
})
}
impl From<Simple> for Kind {
fn from(value: Simple) -> Self {
Self::Simple(value)
};
let patch: u16 = match split.next().map(|p| p.parse()) {
Some(Ok(p)) => {
if p < MAX_U12 {
p
} else {
return Err(Error::Range);
}
}
impl From<Rapid> for Kind {
fn from(value: Rapid) -> Self {
Self::Rapid(value)
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);
}
}
impl From<SemVer> for Kind {
fn from(value: SemVer) -> Self {
Self::SemVer(value)
Some(Err(e)) => return Err(e.into()),
None => {
return Ok(Self {
kind: Kind::SemVer {
major,
minor,
patch,
},
pre,
arch,
})
}
}
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,
};
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,
)
);
}
}