const-semver 0.1.4

Library for constructing semver-compatible Versions at compile-time
Documentation
use std::cmp::Ordering;

#[cfg(feature = "macros")]
#[macro_use]
mod macros;

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Identifier {
    Numeric(u64),
    Alphanumeric(&'static str),
}

#[derive(Clone, Eq, Debug)]
pub struct Version {
    pub major: u64,
    pub minor: u64,
    pub patch: u64,
    pub pre: &'static [Identifier],
    pub build: &'static [Identifier],
}

impl Version {
    pub const fn new(major: u64, minor: u64, patch: u64) -> Self {
        Self {
            major,
            minor,
            patch,
            pre: &[],
            build: &[],
        }
    }

    pub const fn pre(self, identifiers: &'static [Identifier]) -> Self {
        Self {
            major: self.major,
            minor: self.minor,
            patch: self.patch,
            pre: identifiers,
            build: self.build,
        }
    }

    pub const fn build(self, build: &'static [Identifier]) -> Self {
        Self {
            major: self.major,
            minor: self.minor,
            patch: self.patch,
            pre: self.pre,
            build,
        }
    }
}

#[cfg(feature = "semver")]
impl From<&Identifier> for semver::Identifier {
    fn from(ident: &Identifier) -> Self {
        match ident {
            Identifier::Numeric(n) => semver::Identifier::Numeric(*n),
            Identifier::Alphanumeric(s) => semver::Identifier::AlphaNumeric(s.to_string()),
        }
    }
}

#[cfg(feature = "semver")]
impl From<Identifier> for semver::Identifier {
    fn from(ident: Identifier) -> Self {
        match ident {
            Identifier::Numeric(n) => semver::Identifier::Numeric(n),
            Identifier::Alphanumeric(s) => semver::Identifier::AlphaNumeric(s.to_string()),
        }
    }
}

#[cfg(feature = "semver")]
impl From<Version> for semver::Version {
    fn from(version: Version) -> semver::Version {
        semver::Version {
            major: version.major,
            minor: version.minor,
            patch: version.patch,
            pre: version.pre.iter().map(From::from).collect(),
            build: version.build.iter().map(From::from).collect(),
        }
    }
}

impl std::hash::Hash for Version {
    fn hash<H: std::hash::Hasher>(&self, into: &mut H) {
        self.major.hash(into);
        self.minor.hash(into);
        self.patch.hash(into);
        self.pre.hash(into);
    }
}

impl std::cmp::PartialEq for Version {
    #[inline]
    fn eq(&self, other: &Version) -> bool {
        self.major == other.major
            && self.minor == other.minor
            && self.patch == other.patch
            && self.pre == other.pre
    }
}

impl std::cmp::PartialOrd for Version {
    fn partial_cmp(&self, other: &Version) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl std::cmp::Ord for Version {
    fn cmp(&self, other: &Version) -> Ordering {
        match self.major.cmp(&other.major) {
            Ordering::Equal => {}
            r => return r,
        }

        match self.minor.cmp(&other.minor) {
            Ordering::Equal => {}
            r => return r,
        }

        match self.patch.cmp(&other.patch) {
            Ordering::Equal => {}
            r => return r,
        }

        // NB: semver spec says 0.0.0-pre < 0.0.0
        // but the version of ord defined for vec
        // says that [] < [pre] so we alter it here
        match (self.pre.len(), other.pre.len()) {
            (0, 0) => Ordering::Equal,
            (0, _) => Ordering::Greater,
            (_, 0) => Ordering::Less,
            (_, _) => self.pre.cmp(&other.pre),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{Identifier, Version};

    #[test]
    fn build() {
        let constructed = Version::new(1, 2, 3)
            .pre(&[Identifier::Alphanumeric("alpha")])
            .build(&[Identifier::Alphanumeric("amd64")]);

        let with_build = Version {
            major: 1,
            minor: 2,
            patch: 3,
            pre: &[Identifier::Alphanumeric("alpha")],
            build: &[Identifier::Alphanumeric("amd64")],
        };

        let without_build = Version {
            major: 1,
            minor: 2,
            patch: 3,
            pre: &[Identifier::Alphanumeric("alpha")],
            build: &[],
        };

        assert_eq!(with_build, constructed);
        assert_eq!(without_build, constructed);
    }

    #[test]
    fn convert() {
        let version = semver::Version::parse("1.2.3-alpha.3+amd64").unwrap();

        let const_correct: semver::Version = Version::new(1, 2, 3)
            .pre(&[Identifier::Alphanumeric("alpha"), Identifier::Numeric(3)])
            .build(&[Identifier::Alphanumeric("amd64")])
            .into();

        assert_eq!(version, const_correct);
    }

    #[test]
    #[cfg(feature = "macros")]
    fn test_macro() {
        let handmade = Version {
            major: 1,
            minor: 2,
            patch: 3,
            pre: &[Identifier::Alphanumeric("alpha"), Identifier::Numeric(4)],
            build: &[],
        };

        let version = version!(1, 2, 3 - alpha, 4);

        assert_eq!(version, handmade);
    }
}