Compare commits
No commits in common. "17e52c2b013f74037b784483772b41736eea69d5" and "12ac9a84ce874a1cbe994147fed05d351758e76d" have entirely different histories.
17e52c2b01
...
12ac9a84ce
96
Cargo.lock
generated
96
Cargo.lock
generated
@ -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"
|
||||||
|
@ -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"] }
|
||||||
|
22
README.md
22
README.md
@ -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.
|
||||||
|
22
src/arch.rs
22
src/arch.rs
@ -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
306
src/extended.rs
Normal 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
175
src/gitrev.rs
Normal 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,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
};
|
||||||
|
@ -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
159
src/rapid.rs
Normal 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
260
src/semver.rs
Normal 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
144
src/simple.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
339
src/version.rs
339
src/version.rs
@ -1,215 +1,202 @@
|
|||||||
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 Ord for Version {
|
|
||||||
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
|
||||||
u128::from(*self).cmp(&u128::from(*other))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(self.cmp(other))
|
self.0.partial_cmp(&other.0)
|
||||||
} else {
|
} else {
|
||||||
None
|
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,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user