diff options
Diffstat (limited to 'semver-parser/src/version.rs')
-rw-r--r-- | semver-parser/src/version.rs | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/semver-parser/src/version.rs b/semver-parser/src/version.rs new file mode 100644 index 0000000..570f947 --- /dev/null +++ b/semver-parser/src/version.rs @@ -0,0 +1,365 @@ +use std::fmt; +use std::str::from_utf8; + +use recognize::*; + +use common::{self, numeric_identifier}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Version { + pub major: u64, + pub minor: u64, + pub patch: u64, + pub pre: Vec<Identifier>, + pub build: Vec<Identifier>, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Identifier { + /// An identifier that's solely numbers. + Numeric(u64), + /// An identifier with letters and numbers. + AlphaNumeric(String), +} + +pub fn parse(version: &str) -> Result<Version, String> { + let s = version.trim().as_bytes(); + let mut i = 0; + let major = if let Some((major, len)) = numeric_identifier(&s[i..]) { + i += len; + major + } else { + return Err("Error parsing major identifier".to_string()); + }; + if let Some(len) = b'.'.p(&s[i..]) { + i += len; + } else { + return Err("Expected dot".to_string()); + } + let minor = if let Some((minor, len)) = numeric_identifier(&s[i..]) { + i += len; + minor + } else { + return Err("Error parsing minor identifier".to_string()); + }; + if let Some(len) = b'.'.p(&s[i..]) { + i += len; + } else { + return Err("Expected dot".to_string()); + } + let patch = if let Some((patch, len)) = numeric_identifier(&s[i..]) { + i += len; + patch + } else { + return Err("Error parsing patch identifier".to_string()); + }; + let (pre, pre_len) = common::parse_optional_meta(&s[i..], b'-')?; + i += pre_len; + let (build, build_len) = common::parse_optional_meta(&s[i..], b'+')?; + i += build_len; + if i != s.len() { + return Err("Extra junk after valid version: ".to_string() + + from_utf8(&s[i..]).unwrap()); + } + Ok(Version { + major: major, + minor: minor, + patch: patch, + pre: pre, + build: build, + }) +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(write!(f, "{}.{}.{}", self.major, self.minor, self.patch)); + if !self.pre.is_empty() { + let strs: Vec<_> = + self.pre.iter().map(ToString::to_string).collect(); + try!(write!(f, "-{}", strs.join("."))); + } + if !self.build.is_empty() { + let strs: Vec<_> = + self.build.iter().map(ToString::to_string).collect(); + try!(write!(f, "+{}", strs.join("."))); + } + Ok(()) + } +} + +impl fmt::Display for Identifier { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Identifier::Numeric(ref id) => id.fmt(f), + Identifier::AlphaNumeric(ref id) => id.fmt(f), + } + } +} + +#[cfg(test)] +mod tests { + use version; + use super::*; + + #[test] + fn parse_empty() { + let version = ""; + + let parsed = version::parse(version); + + assert!(parsed.is_err(), "empty string incorrectly considered a valid parse"); + } + + #[test] + fn parse_blank() { + let version = " "; + + let parsed = version::parse(version); + + assert!(parsed.is_err(), "blank string incorrectly considered a valid parse"); + } + + #[test] + fn parse_no_minor_patch() { + let version = "1"; + + let parsed = version::parse(version); + + assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version)); + } + + #[test] + fn parse_no_patch() { + let version = "1.2"; + + let parsed = version::parse(version); + + assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version)); + } + + #[test] + fn parse_empty_pre() { + let version = "1.2.3-"; + + let parsed = version::parse(version); + + assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version)); + } + + #[test] + fn parse_letters() { + let version = "a.b.c"; + + let parsed = version::parse(version); + + assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version)); + } + + #[test] + fn parse_with_letters() { + let version = "1.2.3 a.b.c"; + + let parsed = version::parse(version); + + assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version)); + } + + #[test] + fn parse_basic_version() { + let version = "1.2.3"; + + let parsed = version::parse(version).unwrap(); + + assert_eq!(1, parsed.major); + assert_eq!(2, parsed.minor); + assert_eq!(3, parsed.patch); + } + + #[test] + fn parse_trims_input() { + let version = " 1.2.3 "; + + let parsed = version::parse(version).unwrap(); + + assert_eq!(1, parsed.major); + assert_eq!(2, parsed.minor); + assert_eq!(3, parsed.patch); + } + + #[test] + fn parse_no_major_leading_zeroes() { + let version = "01.0.0"; + + let parsed = version::parse(version); + + assert!(parsed.is_err(), "01 incorrectly considered a valid major version"); + } + + #[test] + fn parse_no_minor_leading_zeroes() { + let version = "0.01.0"; + + let parsed = version::parse(version); + + assert!(parsed.is_err(), "01 incorrectly considered a valid minor version"); + } + + #[test] + fn parse_no_patch_leading_zeroes() { + let version = "0.0.01"; + + let parsed = version::parse(version); + + assert!(parsed.is_err(), "01 incorrectly considered a valid patch version"); + } + + #[test] + fn parse_no_major_overflow() { + let version = "98765432109876543210.0.0"; + + let parsed = version::parse(version); + + assert!(parsed.is_err(), "98765432109876543210 incorrectly considered a valid major version"); + } + + #[test] + fn parse_no_minor_overflow() { + let version = "0.98765432109876543210.0"; + + let parsed = version::parse(version); + + assert!(parsed.is_err(), "98765432109876543210 incorrectly considered a valid minor version"); + } + + #[test] + fn parse_no_patch_overflow() { + let version = "0.0.98765432109876543210"; + + let parsed = version::parse(version); + + assert!(parsed.is_err(), "98765432109876543210 incorrectly considered a valid patch version"); + } + + #[test] + fn parse_basic_prerelease() { + let version = "1.2.3-pre"; + + let parsed = version::parse(version).unwrap(); + + let expected_pre = vec![Identifier::AlphaNumeric(String::from("pre"))]; + assert_eq!(expected_pre, parsed.pre); + } + + #[test] + fn parse_prerelease_alphanumeric() { + let version = "1.2.3-alpha1"; + + let parsed = version::parse(version).unwrap(); + + let expected_pre = vec![Identifier::AlphaNumeric(String::from("alpha1"))]; + assert_eq!(expected_pre, parsed.pre); + } + + #[test] + fn parse_prerelease_zero() { + let version = "1.2.3-pre.0"; + + let parsed = version::parse(version).unwrap(); + + let expected_pre = vec![Identifier::AlphaNumeric(String::from("pre")), + Identifier::Numeric(0)]; + assert_eq!(expected_pre, parsed.pre); + } + + #[test] + fn parse_basic_build() { + let version = "1.2.3+build"; + + let parsed = version::parse(version).unwrap(); + + let expected_build = vec![Identifier::AlphaNumeric(String::from("build"))]; + assert_eq!(expected_build, parsed.build); + } + + #[test] + fn parse_build_alphanumeric() { + let version = "1.2.3+build5"; + + let parsed = version::parse(version).unwrap(); + + let expected_build = vec![Identifier::AlphaNumeric(String::from("build5"))]; + assert_eq!(expected_build, parsed.build); + } + + #[test] + fn parse_pre_and_build() { + let version = "1.2.3-alpha1+build5"; + + let parsed = version::parse(version).unwrap(); + + let expected_pre = vec![Identifier::AlphaNumeric(String::from("alpha1"))]; + assert_eq!(expected_pre, parsed.pre); + + let expected_build = vec![Identifier::AlphaNumeric(String::from("build5"))]; + assert_eq!(expected_build, parsed.build); + } + + #[test] + fn parse_complex_metadata_01() { + let version = "1.2.3-1.alpha1.9+build5.7.3aedf "; + + let parsed = version::parse(version).unwrap(); + + let expected_pre = vec![Identifier::Numeric(1), + Identifier::AlphaNumeric(String::from("alpha1")), + Identifier::Numeric(9)]; + assert_eq!(expected_pre, parsed.pre); + + let expected_build = vec![Identifier::AlphaNumeric(String::from("build5")), + Identifier::Numeric(7), + Identifier::AlphaNumeric(String::from("3aedf"))]; + assert_eq!(expected_build, parsed.build); + } + + #[test] + fn parse_complex_metadata_02() { + let version = "0.4.0-beta.1+0851523"; + + let parsed = version::parse(version).unwrap(); + + let expected_pre = vec![Identifier::AlphaNumeric(String::from("beta")), + Identifier::Numeric(1)]; + assert_eq!(expected_pre, parsed.pre); + + let expected_build = vec![Identifier::AlphaNumeric(String::from("0851523"))]; + assert_eq!(expected_build, parsed.build); + } + + #[test] + fn parse_metadata_overflow() { + let version = "0.4.0-beta.1+98765432109876543210"; + + let parsed = version::parse(version).unwrap(); + + let expected_pre = vec![Identifier::AlphaNumeric(String::from("beta")), + Identifier::Numeric(1)]; + assert_eq!(expected_pre, parsed.pre); + + let expected_build = vec![Identifier::AlphaNumeric(String::from("98765432109876543210"))]; + assert_eq!(expected_build, parsed.build); + } + + #[test] + fn parse_regression_01() { + let version = "0.0.0-WIP"; + + let parsed = version::parse(version).unwrap(); + + assert_eq!(0, parsed.major); + assert_eq!(0, parsed.minor); + assert_eq!(0, parsed.patch); + + let expected_pre = vec![Identifier::AlphaNumeric(String::from("WIP"))]; + assert_eq!(expected_pre, parsed.pre); + } +} |