rustversion-detect 0.2.0

Detect rustc compiler version
Documentation
use crate::Channel::*;
use crate::{Date, RustVersion};

pub enum ParseResult {
    Success(RustVersion),
    OopsClippy,
    Unrecognized,
}

pub fn parse(string: &str) -> ParseResult {
    let last_line = string.lines().last().unwrap_or(string);
    let mut words = last_line.trim().split(' ');

    match words.next() {
        Some("rustc") => {}
        Some(word) if word.starts_with("clippy") => return ParseResult::OopsClippy,
        Some(_) | None => return ParseResult::Unrecognized,
    }

    parse_words(&mut words).map_or(ParseResult::Unrecognized, ParseResult::Success)
}

fn parse_words(words: &mut dyn Iterator<Item = &str>) -> Option<RustVersion> {
    let mut version_channel = words.next()?.split('-');
    let version = version_channel.next()?;
    let channel = version_channel.next();

    let mut digits = version.split('.');
    let major = digits.next()?.parse().ok()?;
    let minor = digits.next()?.parse().ok()?;
    let patch = digits.next().unwrap_or("0").parse().ok()?;

    let channel = match channel {
        None => Stable,
        Some("dev") => Development,
        Some(channel) if channel.starts_with("beta") => Beta,
        Some("nightly") => match words.next() {
            Some(hash) if hash.starts_with('(') => match words.next() {
                None if hash.ends_with(')') => Development,
                Some(date) if date.ends_with(')') => {
                    match date[..date.len() - 1].parse::<Date>() {
                        Ok(date) => Nightly { date },
                        Err(_) => return None,
                    }
                }
                None | Some(_) => return None,
            },
            Some(_) => return None,
            None => Development,
        },
        Some(_) => return None,
    };

    Some(RustVersion {
        major,
        minor,
        patch,
        channel,
    })
}

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

    #[test]
    fn test_parse() {
        let cases = &[
            (
                "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14)",
                RustVersion {
                    major: 1,
                    minor: 0,
                    patch: 0,
                    channel: Stable,
                },
            ),
            (
                "rustc 1.18.0",
                RustVersion {
                    major: 1,
                    minor: 18,
                    patch: 0,
                    channel: Stable,
                },
            ),
            (
                "rustc 1.24.1 (d3ae9a9e0 2018-02-27)",
                RustVersion {
                    major: 1,
                    minor: 24,
                    patch: 1,
                    channel: Stable,
                },
            ),
            (
                "rustc 1.35.0-beta.3 (c13114dc8 2019-04-27)",
                RustVersion {
                    major: 1,
                    minor: 35,
                    patch: 0,
                    channel: Beta,
                },
            ),
            (
                "rustc 1.36.0-nightly (938d4ffe1 2019-04-27)",
                RustVersion {
                    major: 1,
                    minor: 36,
                    patch: 0,
                    channel: Nightly {
                        date: Date::new(
                            2019,
                            4,
                            27,
                        )
                    },
                },
            ),
            (
                "rustc 1.36.0-dev",
                RustVersion {
                    major: 1,
                    minor: 36,
                    patch: 0,
                    channel: Development,
                },
            ),
            (
                "rustc 1.36.0-nightly",
                RustVersion {
                    major: 1,
                    minor: 36,
                    patch: 0,
                    channel: Development,
                },
            ),
            (
                "warning: invalid logging spec 'warning', ignoring it
                 rustc 1.30.0-nightly (3bc2ca7e4 2018-09-20)",
                RustVersion {
                    major: 1,
                    minor: 30,
                    patch: 0,
                    channel: Nightly {
                        date: Date::new(
                            2018,
                            9,
                            20
                        ),
                    },
                },
            ),
            (
                "rustc 1.52.1-nightly (gentoo)",
                RustVersion {
                    major: 1,
                    minor: 52,
                    patch: 1,
                    channel: Development,
                },
            ),
        ];

        for (string, expected) in cases {
            match parse(string) {
                ParseResult::Success(version) => assert_eq!(version, *expected),
                ParseResult::OopsClippy | ParseResult::Unrecognized => {
                    panic!("unrecognized: {:?}", string);
                }
            }
        }
    }
}