Add README, give prerelease numbers 12 bits rather than 10
This commit is contained in:
parent
ab572849c6
commit
12ac9a84ce
102
README.md
Normal file
102
README.md
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
Contents
|
||||||
|
========
|
||||||
|
- [Introduction](#introduction)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Internals](#internals)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
This crate aims to provide methods for parsing, formatting, comparing and ordering
|
||||||
|
version numbers in most of the common formats in use today. In a perfect world, all
|
||||||
|
software be it free or non-free would use semantic versioning and strictly stick to
|
||||||
|
the rules governing semver. In the real world version numbers may be given using 1,
|
||||||
|
2, 3 or 4 digits and may or may not be consistent between releases. One release may
|
||||||
|
take the form "2.4" and be followed by a "2.4.1" release. A robust library for parsing
|
||||||
|
and comparing versions must be able to compare the two for ordering purposes in
|
||||||
|
order to determine that "2.4.1" is a newer patch release in the "2.4" series. In
|
||||||
|
addition, Pre-release numbering must be accounted for and the library must be able to
|
||||||
|
determine that an alpha is older than a beta, a full release newer than a release
|
||||||
|
candidate.
|
||||||
|
|
||||||
|
This library provides a `Version` struct which can handle 1 to 4 numerical fields for
|
||||||
|
semver-like versioning plus alpha, beta or rc prereleases, plus git revisions. It also
|
||||||
|
tracks machine architecture, and will return -ne if the version numbers match but not
|
||||||
|
the arch when doing comparisons.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
For the time being, this crate is not yet release on crates.io. It can be used from
|
||||||
|
git in your Cargo.toml file like so:
|
||||||
|
```Toml
|
||||||
|
version = { git = "https://codeberg.org/jeang3nie/version-rs" }
|
||||||
|
```
|
||||||
|
### Parsing and formatting
|
||||||
|
`Version` implements `Display` and `FromStr`.
|
||||||
|
```
|
||||||
|
use version::{prelude::VersionError, Version};
|
||||||
|
|
||||||
|
fn main() -> Result::<(), VersionError> {
|
||||||
|
let vstr = "2.0.3_alpha1-aarch64";
|
||||||
|
let v: Version = vstr.parse()?;
|
||||||
|
assert_eq!(v.to_string(), vstr);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### Comparison
|
||||||
|
`Version` implements `PartialOrd` and `PartialEq`
|
||||||
|
```
|
||||||
|
use version::{prelude::VersionError, Version};
|
||||||
|
|
||||||
|
fn main() -> Result<(), VersionError> {
|
||||||
|
let va: Version = "1.0-x86_64".parse()?;
|
||||||
|
let vb: Version = "1.0.0-x86_64".parse()?;
|
||||||
|
let vc: Version = "1.0_rc4-x86_64".parse()?;
|
||||||
|
let vd: Version = "1.0-riscv64".parse()?;
|
||||||
|
assert_eq!(va, vb);
|
||||||
|
assert!(vb > vc);
|
||||||
|
assert_ne!(va, vd);
|
||||||
|
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
|
||||||
|
are first encoded into a single `u64`, allowing for an easy comparison using the
|
||||||
|
`==`, `<` and `>` operators. This is done using some simple bit shifting logic.
|
||||||
|
The twelve least significant bits, or LSB, are given for any numerical component to
|
||||||
|
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 |
|
||||||
|
|
||||||
|
This gives an upper limit of 2^12 or 4096 for the numerical component, 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.
|
||||||
|
|
||||||
|
The fields major, minor, patch and build each get 12 of the remaining bits, arranged
|
||||||
|
so that major is the most significant followed by minor, patch and build.
|
||||||
|
|
||||||
|
| bits | 52-63 | 40-51 | 28-39 | 16-27 |
|
||||||
|
| ---- | ----- | ----- | ----- | ----- |
|
||||||
|
| use | major | minor | patch | build |
|
||||||
|
|
||||||
|
In this way, version "1.0" will be equal to "1.0.0" and less than "1.0.1" in a simple
|
||||||
|
comparison operation, without requiring complex match statements.
|
||||||
|
|
||||||
|
In this way the traits PartialEq, Eq, PartialOrd and Ord can be implemented in a
|
||||||
|
straightforward and concise manner for the various versioning schemes.
|
@ -66,7 +66,7 @@ impl From<Extended> for u64 {
|
|||||||
let major = u64::from(value.major) << 52;
|
let major = u64::from(value.major) << 52;
|
||||||
let minor = u64::from(value.minor) << 40;
|
let minor = u64::from(value.minor) << 40;
|
||||||
let patch = u64::from(value.patch) << 28;
|
let patch = u64::from(value.patch) << 28;
|
||||||
let build = u64::from(value.build) << 14;
|
let build = u64::from(value.build) << 16;
|
||||||
let pre = u64::from(u16::from(value.pre));
|
let pre = u64::from(u16::from(value.pre));
|
||||||
major | minor | patch | build | pre
|
major | minor | patch | build | pre
|
||||||
}
|
}
|
||||||
@ -82,8 +82,8 @@ impl TryFrom<u64> for Extended {
|
|||||||
let minor = (value & mask) >> 40;
|
let minor = (value & mask) >> 40;
|
||||||
mask = 0o7777 << 28;
|
mask = 0o7777 << 28;
|
||||||
let patch = (value & mask) >> 28;
|
let patch = (value & mask) >> 28;
|
||||||
mask = 0o7777 << 14;
|
mask = 0o7777 << 16;
|
||||||
let build = (value & mask) >> 14;
|
let build = (value & mask) >> 16;
|
||||||
mask = 0o37777;
|
mask = 0o37777;
|
||||||
let p = u16::try_from(value & mask)?;
|
let p = u16::try_from(value & mask)?;
|
||||||
let pre: PreRelease = p.try_into()?;
|
let pre: PreRelease = p.try_into()?;
|
||||||
|
@ -15,6 +15,23 @@ use {
|
|||||||
/// that a Git revision can only be compared against another Git revision, making it
|
/// 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\
|
/// impossible to properly order updates between a proper `SemVer` type release and a\
|
||||||
/// Git revision.
|
/// 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)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct GitRev {
|
pub struct GitRev {
|
||||||
/// the short revision hash
|
/// the short revision hash
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
#![warn(clippy::all, clippy::pedantic)]
|
#![warn(clippy::all, clippy::pedantic)]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
mod arch;
|
mod arch;
|
||||||
mod error;
|
mod error;
|
||||||
mod extended;
|
mod extended;
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
use {
|
use {
|
||||||
crate::error::Error,
|
crate::{error::Error, MAX_U12},
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Serialize},
|
||||||
std::{cmp, convert::Into, fmt, num::NonZeroU16, str},
|
std::{cmp, convert::Into, fmt, num::NonZeroU16, str},
|
||||||
};
|
};
|
||||||
|
|
||||||
static MAX_U10: u16 = 1024;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
/// A specification for non-production releases
|
/// A specification for non-production releases
|
||||||
pub enum PreRelease {
|
pub enum PreRelease {
|
||||||
@ -47,7 +45,7 @@ impl PreRelease {
|
|||||||
match self {
|
match self {
|
||||||
Self::Alpha(n) | Self::Beta(n) | Self::RC(n) => match n {
|
Self::Alpha(n) | Self::Beta(n) | Self::RC(n) => match n {
|
||||||
Some(num) => {
|
Some(num) => {
|
||||||
if u16::from(*num) >= MAX_U10 {
|
if u16::from(*num) >= MAX_U12 {
|
||||||
return Err(Error::Range);
|
return Err(Error::Range);
|
||||||
}
|
}
|
||||||
*num = num.saturating_add(1);
|
*num = num.saturating_add(1);
|
||||||
@ -155,20 +153,20 @@ impl From<PreRelease> for u16 {
|
|||||||
match value {
|
match value {
|
||||||
PreRelease::Alpha(Some(v)) => {
|
PreRelease::Alpha(Some(v)) => {
|
||||||
let v = u16::from(v) & mask;
|
let v = u16::from(v) & mask;
|
||||||
v | 0o2_000
|
v | 0o10_000
|
||||||
}
|
}
|
||||||
PreRelease::Alpha(None) => 0o2_000,
|
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;
|
||||||
v | 0o4_000
|
v | 0o20_000
|
||||||
}
|
}
|
||||||
PreRelease::Beta(None) => 0o4_000,
|
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;
|
||||||
v | 0o10_000
|
v | 0o40_000
|
||||||
}
|
}
|
||||||
PreRelease::RC(None) => 0o10_000,
|
PreRelease::RC(None) => 0o10_000,
|
||||||
PreRelease::None => 0o20_000,
|
PreRelease::None => 0o100_000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,19 +179,19 @@ impl TryFrom<u16> for PreRelease {
|
|||||||
let v = value & mask;
|
let v = value & mask;
|
||||||
let flag = value & !mask;
|
let flag = value & !mask;
|
||||||
match flag {
|
match flag {
|
||||||
0o2_000 => {
|
0o10_000 => {
|
||||||
let v = if v > 0 { Some(v.try_into()?) } else { None };
|
let v = if v > 0 { Some(v.try_into()?) } else { None };
|
||||||
Ok(Self::Alpha(v))
|
Ok(Self::Alpha(v))
|
||||||
}
|
}
|
||||||
0o4_000 => {
|
0o20_000 => {
|
||||||
let v = if v > 0 { Some(v.try_into()?) } else { None };
|
let v = if v > 0 { Some(v.try_into()?) } else { None };
|
||||||
Ok(Self::Beta(v))
|
Ok(Self::Beta(v))
|
||||||
}
|
}
|
||||||
0o10_000 => {
|
0o40_000 => {
|
||||||
let v = if v > 0 { Some(v.try_into()?) } else { None };
|
let v = if v > 0 { Some(v.try_into()?) } else { None };
|
||||||
Ok(Self::RC(v))
|
Ok(Self::RC(v))
|
||||||
}
|
}
|
||||||
0o20_000 => Ok(Self::None),
|
0o100_000 => Ok(Self::None),
|
||||||
_ => Err(Error::FromUint),
|
_ => Err(Error::FromUint),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user