semver_sort 1.0.0

Sorts semver strings or numbers
Documentation
use regex::Regex;

static SEMVER_REGEX: &str = r"^v?(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$";

enum SemverValue<'a> {
    Number(u32),
    String(&'a str),
}

fn parse_type(semver: &str) -> (&str, SemverValue) {
    match semver.parse::<u32>() {
        Ok(num) => ("number", SemverValue::Number(num)),
        Err(_) => ("string", SemverValue::String(semver)),
    }
}

#[derive(Debug, PartialEq, PartialOrd)]
pub struct Semver<'a> {
    pub major: &'a str,
    pub minor: &'a str,
    pub patch: &'a str,
    pub prerelease: Option<&'a str>,
    pub buildmetadata: Option<&'a str>,
}

impl<'a> Semver<'a> {
    pub fn new(
        major: Option<&'a str>,
        minor: Option<&'a str>,
        patch: Option<&'a str>,
        prerelease: Option<&'a str>,
        buildmetadata: Option<&'a str>,
    ) -> Self {
        Self {
            major: major.unwrap_or("0"),
            minor: minor.unwrap_or("0"),
            patch: patch.unwrap_or("0"),
            prerelease: prerelease,
            buildmetadata: buildmetadata,
        }
    }
}

pub fn semver_regex(semver: &str) -> Semver {
    let re = Regex::new(SEMVER_REGEX).unwrap();
    let captures = re.captures(semver).unwrap();

    return Semver::new(
        captures.name("major").map(|c| c.as_str()),
        captures.name("minor").map(|c| c.as_str()),
        captures.name("patch").map(|c| c.as_str()),
        captures.name("prerelease").map(|c| c.as_str()),
        captures.name("buildmetadata").map(|c| c.as_str()),
    );
}

fn compare_version(left: Option<&str>, right: Option<&str>) -> bool {
    match (left, right) {
        (Some(left), Some(right)) => {
            let left_type = parse_type(left);
            let right_type = parse_type(right);

            match (left_type.1, right_type.1) {
                (SemverValue::Number(left), SemverValue::Number(right)) => return left < right,
                (SemverValue::String(left), SemverValue::String(right)) => {
                    return left.cmp(right) == std::cmp::Ordering::Greater
                }
                _ => return false,
            }
        }

        (Some(_), None) => return false,
        (None, Some(_)) => return true,
        (None, None) => return false,
    }
}

pub fn semver_compare(left: &str, right: &str, asc: bool) -> bool {
    let left_semver = semver_regex(if asc { left } else { right });
    let right_semver = semver_regex(if asc { right } else { left });

    if left_semver.major != right_semver.major {
        return compare_version(Some(left_semver.major), Some(right_semver.major));
    } else if left_semver.minor != right_semver.minor {
        return compare_version(Some(left_semver.minor), Some(right_semver.minor));
    } else if left_semver.patch != right_semver.patch {
        return compare_version(Some(left_semver.patch), Some(right_semver.patch));
    } else if left_semver.prerelease != right_semver.prerelease {
        return compare_version(left_semver.prerelease, right_semver.prerelease);
    } else if left_semver.buildmetadata != right_semver.buildmetadata {
        return compare_version(left_semver.buildmetadata, right_semver.buildmetadata);
    }

    false
}