Versioning

Parse and increment a version string

semver cat-config version string

Constructs a semver::Version⮳ from a string literal using semver::Version::parse⮳ then increments it by patch, minor, and major version number one by one.

Note that in accordance with the semantic versioning specification`⮳, incrementing the minor version number resets the patch version number to 0 and incrementing the major version number resets both the minor and patch version numbers to 0.

use anyhow::Result;
use semver::Version;

fn main() -> Result<()> {
    let parsed_version = Version::parse("0.2.6")?;
    // Note: a SemVer version must have exactly three components

    assert_eq!(parsed_version, Version {
        major: 0,
        minor: 2,
        patch: 6,
        pre: semver::Prerelease::EMPTY,
        build: semver::BuildMetadata::EMPTY,
    });

    // parsed_version.increment_patch();
    // assert_eq!(parsed_version.to_string(), "0.2.7");
    // println!("New patch release: v{}", parsed_version);

    // parsed_version.increment_minor();
    // assert_eq!(parsed_version.to_string(), "0.3.0");
    // println!("New minor release: v{}", parsed_version);

    // parsed_version.increment_major();
    // assert_eq!(parsed_version.to_string(), "1.0.0");
    // println!("New major release: v{}", parsed_version);

    Ok(())
}

Parse a complex version string

semver cat-config

Constructs a semver::Version⮳ from a complex version string using semver::Version::parse⮳ The string contains pre-release and build metadata as defined in the semantic versioning specification`⮳.

Note that, in accordance with the Specification, build metadata is parsed but not considered when comparing versions. In other words, two versions may be equal even if their build strings differ.

use anyhow::Result;
use semver::BuildMetadata;
use semver::Prerelease;
use semver::Version;

fn main() -> Result<()> {
    let version_str = "1.0.49-125+g72ee7853";
    let parsed_version = Version::parse(version_str)?;

    assert_eq!(parsed_version, Version {
        major: 1,
        minor: 0,
        patch: 49,
        pre: Prerelease::new("125")?,
        build: BuildMetadata::new("g72ee7853")?,
    });

    let serialized_version = parsed_version.to_string();
    assert_eq!(&serialized_version, version_str);
    println!("{}", serialized_version);

    Ok(())
}

Check if a given version is pre-release

semver cat-config

Given two versions, semver::Version⮳ asserts that one is pre-release and the other is not.

use anyhow::Result;
use semver::Version;

fn main() -> Result<()> {
    let version_1 = Version::parse("1.0.0-alpha")?;
    println!("{:?}", version_1);
    let version_2 = Version::parse("1.0.0")?;
    println!("{:?}", version_2);

    assert!(!version_1.pre.is_empty());
    assert!(version_2.pre.is_empty());

    Ok(())
}

Find the latest version satisfying a given range

semver cat-config

Given a list of version &strs, finds the latest semver::Versionsemver::VersionReq⮳ filters the list with semver::VersionReq::matches⮳ Also demonstrates semver⮳ pre-release preferences.

use anyhow::Result;
use semver::Version;
use semver::VersionReq;

fn find_max_matching_version<'a, I>(
    version_req_str: &str,
    iterable: I,
) -> Result<Option<Version>>
where
    I: IntoIterator<Item = &'a str>,
{
    let vreq = VersionReq::parse(version_req_str)?;

    Ok(iterable
        .into_iter()
        .filter_map(|s| Version::parse(s).ok())
        .filter(|s| vreq.matches(s))
        .max())
}

fn main() -> Result<()> {
    assert_eq!(
        find_max_matching_version("<= 1.0.0", vec!["0.9.0", "1.0.0", "1.0.1"])?,
        Some(Version::parse("1.0.0")?)
    );

    assert_eq!(
        find_max_matching_version(">1.2.3-alpha.3", vec![
            "1.2.3-alpha.3",
            "1.2.3-alpha.4",
            "1.2.3-alpha.10",
            "1.2.3-beta.4",
            "3.4.5-alpha.9",
        ])?,
        Some(Version::parse("1.2.3-beta.4")?)
    );

    Ok(())
}

Check external command version for compatibility

semver cat-text-processing cat-os

Runs git --version using std::process::Command⮳ then parses the version number into a semver::Version⮳ using semver::Version::parsesemver::VersionReq::matches⮳ compares semver::VersionReq to the parsed version. The command output resembles "git version x.y.z".

use std::process::Command;

use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use anyhow::bail;
use semver::Version;
use semver::VersionReq;

fn main() -> Result<()> {
    let version_constraint = "> 1.12.0";
    let version_test = VersionReq::parse(version_constraint)?;
    let output = Command::new("git").arg("--version").output()?;

    if !output.status.success() {
        bail!("Command executed with failing error code");
    }

    let stdout = String::from_utf8(output.stdout)?;
    let version = stdout
        .split(" ")
        .last()
        .ok_or_else(|| anyhow!("Invalid command output"))?
        .trim(); // Remove any extraneous newlines
    let parsed_version =
        Version::parse(version).context(format!("version: {}", version))?;

    if !version_test.matches(&parsed_version) {
        bail!(
            "Command version lower than minimum supported version (found {}, need {})",
            parsed_version,
            version_constraint
        );
    }
    println!("{:?}", parsed_version);
    Ok(())
}