fnm 1.22.9

Fast and simple Node.js version manager
use crate::version::Version;
use std::str::FromStr;

#[derive(Clone, Debug)]
pub enum UserVersion {
    OnlyMajor(u64),
    MajorMinor(u64, u64),
    Full(Version),
}

impl UserVersion {
    pub fn to_version<'a, T>(
        &self,
        available_versions: T,
        config: &crate::config::FnmConfig,
    ) -> Option<&'a Version>
    where
        T: IntoIterator<Item = &'a Version>,
    {
        available_versions
            .into_iter()
            .filter(|x| self.matches(x, config))
            .max()
    }

    pub fn alias_name(&self) -> Option<String> {
        match self {
            Self::Full(version) => version.alias_name(),
            _ => None,
        }
    }

    pub fn matches(&self, version: &Version, config: &crate::config::FnmConfig) -> bool {
        match (self, version) {
            (Self::Full(a), b) if a == b => true,
            (Self::Full(user_version), maybe_alias) => {
                match (user_version.alias_name(), maybe_alias.find_aliases(&config)) {
                    (None, _) | (_, Err(_)) => false,
                    (Some(user_alias), Ok(aliases)) => aliases
                        .iter()
                        .find(|alias| alias.name() == user_alias)
                        .is_some(),
                }
            }
            (_, Version::Bypassed) => false,
            (_, Version::Lts(_)) => false,
            (_, Version::Alias(_)) => false,
            (Self::OnlyMajor(major), Version::Semver(other)) => *major == other.major,
            (Self::MajorMinor(major, minor), Version::Semver(other)) => {
                *major == other.major && *minor == other.minor
            }
        }
    }
}

fn next_of<'a, T: FromStr, It: Iterator<Item = &'a str>>(i: &mut It) -> Option<T> {
    let x = i.next()?;
    T::from_str(x).ok()
}

impl std::fmt::Display for UserVersion {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Full(x) => x.fmt(f),
            Self::OnlyMajor(major) => write!(f, "v{}.x.x", major),
            Self::MajorMinor(major, minor) => write!(f, "v{}.{}.x", major, minor),
        }
    }
}

fn skip_first_v(str: &str) -> &str {
    if str.starts_with('v') {
        &str[1..]
    } else {
        str
    }
}

impl FromStr for UserVersion {
    type Err = semver::SemVerError;
    fn from_str(s: &str) -> Result<UserVersion, Self::Err> {
        match Version::parse(s) {
            Ok(v) => Ok(Self::Full(v)),
            Err(e) => {
                let mut parts = skip_first_v(s.trim()).split('.');
                match (next_of::<u64, _>(&mut parts), next_of::<u64, _>(&mut parts)) {
                    (Some(major), None) => Ok(Self::OnlyMajor(major)),
                    (Some(major), Some(minor)) => Ok(Self::MajorMinor(major, minor)),
                    _ => Err(e),
                }
            }
        }
    }
}

#[cfg(test)]
impl PartialEq for UserVersion {
    fn eq(&self, other: &Self) -> bool {
        use UserVersion::*;

        match (self, other) {
            (OnlyMajor(a), OnlyMajor(b)) if a == b => true,
            (MajorMinor(a1, a2), MajorMinor(b1, b2)) if (a1, a2) == (b1, b2) => true,
            (Full(v1), Full(v2)) if v1 == v2 => true,
            (_, _) => false,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parsing_only_major() {
        let version = UserVersion::from_str("10").ok();
        assert_eq!(version, Some(UserVersion::OnlyMajor(10)));
    }

    #[test]
    fn test_parsing_major_minor() {
        let version = UserVersion::from_str("10.20").ok();
        assert_eq!(version, Some(UserVersion::MajorMinor(10, 20)));
    }

    #[test]
    fn test_parsing_only_major_with_v() {
        let version = UserVersion::from_str("v10").ok();
        assert_eq!(version, Some(UserVersion::OnlyMajor(10)));
    }

    #[test]
    fn test_major_to_version() {
        let expected = Version::parse("6.1.0").unwrap();
        let versions = vec![
            Version::parse("6.0.0").unwrap(),
            Version::parse("6.0.1").unwrap(),
            expected.clone(),
            Version::parse("7.0.1").unwrap(),
        ];
        let result = UserVersion::OnlyMajor(6).to_version(&versions, &Default::default());

        assert_eq!(result, Some(&expected));
    }

    #[test]
    fn test_major_minor_to_version() {
        let expected = Version::parse("6.0.1").unwrap();
        let versions = vec![
            Version::parse("6.0.0").unwrap(),
            Version::parse("6.1.0").unwrap(),
            expected.clone(),
            Version::parse("7.0.1").unwrap(),
        ];
        let result = UserVersion::MajorMinor(6, 0).to_version(&versions, &Default::default());

        assert_eq!(result, Some(&expected));
    }

    #[test]
    fn test_semver_to_version() {
        let expected = Version::parse("6.0.0").unwrap();
        let versions = vec![
            expected.clone(),
            Version::parse("6.1.0").unwrap(),
            Version::parse("6.0.1").unwrap(),
            Version::parse("7.0.1").unwrap(),
        ];
        let result = UserVersion::Full(expected.clone()).to_version(&versions, &Default::default());

        assert_eq!(result, Some(&expected));
    }
}