From 9447ae3423efcd03ebbc13359b4c1d5b37b39118 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 11 Jan 2021 12:08:43 +0100 Subject: [PATCH] Make parser more robust to input variations (fixes #11) --- src/lib.rs | 93 ++++++++++++++++++++++++---------------------------- tests/all.rs | 18 ---------- 2 files changed, 43 insertions(+), 68 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 913c263..b5745bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,6 +56,7 @@ extern crate doc_comment; #[cfg(test)] doctest!("../README.md"); +use std::collections::HashMap; use std::process::Command; use std::{env, error, fmt, io, num, str}; use std::{ffi::OsString, str::FromStr}; @@ -206,65 +207,43 @@ pub fn version_meta() -> Result { /// the SemVer version and additional metadata /// like the git short hash and build date. pub fn version_meta_for(verbose_version_string: &str) -> Result { - let out: Vec<_> = verbose_version_string.lines().collect(); - - if !(out.len() >= 6 && out.len() <= 8) { - return Err(Error::UnexpectedVersionFormat); - } - - let short_version_string = out[0]; - - #[allow(clippy::manual_strip)] - fn expect_prefix<'a>(line: &'a str, prefix: &str) -> Result<&'a str> { - if line.starts_with(prefix) { - Ok(&line[prefix.len()..]) - } else { - Err(Error::UnexpectedVersionFormat) + let mut map = HashMap::new(); + for (i, line) in verbose_version_string.lines().enumerate() { + if i == 0 { + map.insert("short", line); + continue; } - } - - let commit_hash = match expect_prefix(out[2], "commit-hash: ")? { - "unknown" => None, - hash => Some(hash.to_owned()), - }; - - let commit_date = match expect_prefix(out[3], "commit-date: ")? { - "unknown" => None, - hash => Some(hash.to_owned()), - }; - // Handle that the build date may or may not be present. - let mut idx = 4; - let mut build_date = None; - if out[idx].starts_with("build-date") { - build_date = match expect_prefix(out[idx], "build-date: ")? { - "unknown" => None, - s => Some(s.to_owned()), + let mut parts = line.splitn(2, ": "); + let key = match parts.next() { + Some(key) => key, + None => continue, }; - idx += 1; + + if let Some(value) = parts.next() { + map.insert(key, value); + } } - let host = expect_prefix(out[idx], "host: ")?; - idx += 1; - let release = expect_prefix(out[idx], "release: ")?; - idx += 1; + let short_version_string = expect_key("short", &map)?; + let host = expect_key("host", &map)?; + let release = expect_key("release", &map)?; let semver: Version = release.parse()?; - let channel = if semver.pre.is_empty() { - Channel::Stable - } else { - match semver.pre[0] { - Identifier::AlphaNumeric(ref s) if s == "dev" => Channel::Dev, - Identifier::AlphaNumeric(ref s) if s == "beta" => Channel::Beta, - Identifier::AlphaNumeric(ref s) if s == "nightly" => Channel::Nightly, - ref x => return Err(Error::UnknownPreReleaseTag(x.clone())), - } + let channel = match semver.pre.first() { + None => Channel::Stable, + Some(Identifier::AlphaNumeric(s)) if s == "dev" => Channel::Dev, + Some(Identifier::AlphaNumeric(s)) if s == "beta" => Channel::Beta, + Some(Identifier::AlphaNumeric(s)) if s == "nightly" => Channel::Nightly, + Some(x) => return Err(Error::UnknownPreReleaseTag(x.clone())), }; - let llvm_version = if let Some(&line) = out.get(idx) { - Some(expect_prefix(line, "LLVM version: ")?.parse()?) - } else { - None + let commit_hash = expect_key_or_unknown("commit-hash", &map)?; + let commit_date = expect_key_or_unknown("commit-date", &map)?; + let build_date = expect_key_or_unknown("build-date", &map).ok().flatten(); + let llvm_version = match map.get("LLVM version") { + Some(&v) => Some(v.parse()?), + None => None, }; Ok(VersionMeta { @@ -279,6 +258,20 @@ pub fn version_meta_for(verbose_version_string: &str) -> Result { }) } +fn expect_key_or_unknown(key: &str, map: &HashMap<&str, &str>) -> Result, Error> { + match map.get(key) { + Some(&v) if v == "unknown" => Ok(None), + Some(&v) => Ok(Some(String::from(v))), + None => Err(Error::UnexpectedVersionFormat), + } +} + +fn expect_key(key: &str, map: &HashMap<&str, &str>) -> Result { + map.get(key) + .map(|&v| String::from(v)) + .ok_or(Error::UnexpectedVersionFormat) +} + /// LLVM Version Parse Error #[derive(Debug)] pub enum LlvmVersionParseError { diff --git a/tests/all.rs b/tests/all.rs index 3756240..f1dc890 100644 --- a/tests/all.rs +++ b/tests/all.rs @@ -29,24 +29,6 @@ fn smoketest() { assert!(version().unwrap() >= Version::parse("1.0.0").unwrap()); } -#[test] -fn parse_unexpected() { - let res = version_meta_for( - "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14) -binary: rustc -commit-hash: a59de37e99060162a2674e3ff45409ac73595c0e -commit-date: 2015-05-13 -rust-birthday: 2015-05-14 -host: x86_64-unknown-linux-gnu -release: 1.0.0", - ); - - assert!(match res { - Err(Error::UnexpectedVersionFormat) => true, - _ => false, - }); -} - #[test] fn parse_1_0_0() { let version = version_meta_for(