Flexible and capable versioning in Rust
Go to file
2024-02-01 22:50:03 -05:00
src Add tests for impl FromStr for Version 2024-02-01 22:50:03 -05:00
.gitignore Initial commit 2024-01-05 09:22:51 -05:00
Cargo.lock Initial refactor 2024-01-31 22:36:20 -05:00
Cargo.toml Initial refactor 2024-01-31 22:36:20 -05:00
README.md Fix failing doc test, adjust README 2024-01-31 22:59:31 -05:00

Contents

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:

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(())
}

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 11 0-10
use PreRelease::None PreRelease::Rc PreRelease::Beta PreRelease::Alpha PreRelease::Git Numerical component

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.

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.