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 minor = u64::from(value.minor) << 40;
|
||||
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));
|
||||
major | minor | patch | build | pre
|
||||
}
|
||||
@ -82,8 +82,8 @@ impl TryFrom<u64> for Extended {
|
||||
let minor = (value & mask) >> 40;
|
||||
mask = 0o7777 << 28;
|
||||
let patch = (value & mask) >> 28;
|
||||
mask = 0o7777 << 14;
|
||||
let build = (value & mask) >> 14;
|
||||
mask = 0o7777 << 16;
|
||||
let build = (value & mask) >> 16;
|
||||
mask = 0o37777;
|
||||
let p = u16::try_from(value & mask)?;
|
||||
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
|
||||
/// 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
|
||||
|
@ -1,4 +1,6 @@
|
||||
#![warn(clippy::all, clippy::pedantic)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
mod arch;
|
||||
mod error;
|
||||
mod extended;
|
||||
|
@ -1,11 +1,9 @@
|
||||
use {
|
||||
crate::error::Error,
|
||||
crate::{error::Error, MAX_U12},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{cmp, convert::Into, fmt, num::NonZeroU16, str},
|
||||
};
|
||||
|
||||
static MAX_U10: u16 = 1024;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||
/// A specification for non-production releases
|
||||
pub enum PreRelease {
|
||||
@ -47,7 +45,7 @@ impl PreRelease {
|
||||
match self {
|
||||
Self::Alpha(n) | Self::Beta(n) | Self::RC(n) => match n {
|
||||
Some(num) => {
|
||||
if u16::from(*num) >= MAX_U10 {
|
||||
if u16::from(*num) >= MAX_U12 {
|
||||
return Err(Error::Range);
|
||||
}
|
||||
*num = num.saturating_add(1);
|
||||
@ -155,20 +153,20 @@ impl From<PreRelease> for u16 {
|
||||
match value {
|
||||
PreRelease::Alpha(Some(v)) => {
|
||||
let v = u16::from(v) & mask;
|
||||
v | 0o2_000
|
||||
v | 0o10_000
|
||||
}
|
||||
PreRelease::Alpha(None) => 0o2_000,
|
||||
PreRelease::Beta(Some(v)) => {
|
||||
let v = u16::from(v) & mask;
|
||||
v | 0o4_000
|
||||
v | 0o20_000
|
||||
}
|
||||
PreRelease::Beta(None) => 0o4_000,
|
||||
PreRelease::RC(Some(v)) => {
|
||||
let v = u16::from(v) & mask;
|
||||
v | 0o10_000
|
||||
v | 0o40_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 flag = value & !mask;
|
||||
match flag {
|
||||
0o2_000 => {
|
||||
0o10_000 => {
|
||||
let v = if v > 0 { Some(v.try_into()?) } else { None };
|
||||
Ok(Self::Alpha(v))
|
||||
}
|
||||
0o4_000 => {
|
||||
0o20_000 => {
|
||||
let v = if v > 0 { Some(v.try_into()?) } else { None };
|
||||
Ok(Self::Beta(v))
|
||||
}
|
||||
0o10_000 => {
|
||||
0o40_000 => {
|
||||
let v = if v > 0 { Some(v.try_into()?) } else { None };
|
||||
Ok(Self::RC(v))
|
||||
}
|
||||
0o20_000 => Ok(Self::None),
|
||||
0o100_000 => Ok(Self::None),
|
||||
_ => Err(Error::FromUint),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user