--- /dev/null
+//! Version data and functions.\r
+//!\r
+//! This module contains [`Version`] struct, [`parse`] function for building\r
+//! [`Version`] struct from string and some helper data structures and functions.\r
+//!\r
+//! # Examples\r
+//!\r
+//! Parsing `Version` from string and checking its fields:\r
+//!\r
+//! ```\r
+//! use semver_parser::version;\r
+//!\r
+//! # fn try_main() -> Result<(), String> {\r
+//! let version = version::parse("1.2.3-alpha1")?;\r
+//!\r
+//! assert_eq!(version.major, 1);\r
+//! assert_eq!(version.minor, 2);\r
+//! assert_eq!(version.patch, 3);\r
+//!\r
+//! let expected_pre = vec![\r
+//! version::Identifier::AlphaNumeric(String::from("alpha1")),\r
+//! ];\r
+//!\r
+//! assert_eq!(expected_pre, version.pre);\r
+//! # Ok(())\r
+//! # }\r
+//! #\r
+//! # try_main().unwrap();\r
+//! ```\r
+//! [`Version`]: ./struct.Version.html\r
+//! [`parse`]: ./fn.parse.html\r
+\r
+use crate::parser::{self, Parser};\r
+use std::fmt;\r
+\r
+/// Structure representing version data.\r
+///\r
+/// `Version` struct has some public fields representing version data, like major/minor version\r
+/// string, patch number and vectors of prefix and build identifiers.\r
+///\r
+/// # Examples\r
+///\r
+/// Parsing `Version` from string and checking its fields:\r
+///\r
+/// ```\r
+/// use semver_parser::version;\r
+///\r
+/// # fn try_main() -> Result<(), String> {\r
+/// let version = version::parse("0.1.2-alpha1")?;\r
+/// assert_eq!(version.major, 0);\r
+/// assert_eq!(version.minor, 1);\r
+/// assert_eq!(version.patch, 2);\r
+/// let expected_pre = vec![version::Identifier::AlphaNumeric(String::from("alpha1"))];\r
+/// assert_eq!(expected_pre, version.pre);\r
+/// # Ok(())\r
+/// # }\r
+/// #\r
+/// # try_main().unwrap();\r
+/// ```\r
+#[derive(Clone, PartialOrd, Ord, Hash, Debug, PartialEq, Eq)]\r
+pub struct Version {\r
+ /// Major version as number (`0` in `"0.1.2"`).\r
+ pub major: u64,\r
+ /// Minor version as number (`1` in `"0.1.2"`).\r
+ pub minor: u64,\r
+ /// Patch version as number (`2` in `"0.1.2"`).\r
+ pub patch: u64,\r
+ /// Pre-release metadata as a vector of `Identifier` (`"alpha1"` in `"0.1.2-alpha1"`\r
+ /// or `7` (numeric) in `"0.1.2-7"`, `"pre"` and `0` (numeric) in `"0.1.2-pre.0"`).\r
+ pub pre: Vec<Identifier>,\r
+ /// Build metadata as a vector of `Identifier` (`"build1"` in `"0.1.2+build1"`\r
+ /// or `7` (numeric) in `"0.1.2+7"`, `"build"` and `0` (numeric) in `"0.1.2+pre.0"`).\r
+ pub build: Vec<Identifier>,\r
+}\r
+\r
+/// Helper enum for holding data of alphanumeric or numeric suffix identifiers.\r
+///\r
+/// This enum is used to hold suffix parts of `pre` and `build` fields of\r
+/// [`Version`] struct. Theses suffixes may be either numeric or alphanumeric.\r
+///\r
+/// # Examples\r
+///\r
+/// Parsing [`Version`] with pre-release part composed of two `Identifier`s:\r
+///\r
+/// ```\r
+/// use semver_parser::version;\r
+///\r
+/// # fn try_main() -> Result<(), String> {\r
+/// let version = version::parse("0.1.2-alpha1.0")?;\r
+///\r
+/// let expected_pre = vec![\r
+/// version::Identifier::AlphaNumeric(String::from("alpha1")),\r
+/// version::Identifier::Numeric(0),\r
+/// ];\r
+///\r
+/// assert_eq!(expected_pre, version.pre);\r
+/// # Ok(())\r
+/// # }\r
+/// #\r
+/// # try_main().unwrap();\r
+/// ```\r
+/// [`Version`]: ./struct.Version.html\r
+#[derive(Clone, PartialOrd, Ord, Hash, Debug, PartialEq, Eq)]\r
+pub enum Identifier {\r
+ /// An identifier that's solely numbers.\r
+ Numeric(u64),\r
+ /// An identifier with letters and numbers.\r
+ AlphaNumeric(String),\r
+}\r
+\r
+impl Identifier {\r
+ pub fn concat(self, add_str: &str) -> Identifier {\r
+ match self {\r
+ Identifier::Numeric(n) => Identifier::AlphaNumeric(format!("{}{}", n, add_str)),\r
+ Identifier::AlphaNumeric(s) => Identifier::AlphaNumeric(format!("{}{}", s, add_str)),\r
+ }\r
+ }\r
+}\r
+\r
+/// Function for parsing version string to [`Version`].\r
+///\r
+/// Returns `Result<`[`Version`]`, String>`, where `String` represents an error while parsing.\r
+///\r
+/// # Examples\r
+///\r
+/// Parsing [`Version`] from string and checking its fields:\r
+///\r
+/// ```\r
+/// use semver_parser::version;\r
+///\r
+/// # fn try_main() -> Result<(), String> {\r
+/// let version = version::parse("0.1.2-alpha1")?;\r
+/// assert_eq!(version.major, 0);\r
+/// assert_eq!(version.minor, 1);\r
+/// assert_eq!(version.patch, 2);\r
+/// let expected_pre = vec![version::Identifier::AlphaNumeric(String::from("alpha1"))];\r
+/// assert_eq!(expected_pre, version.pre);\r
+/// # Ok(())\r
+/// # }\r
+/// #\r
+/// # try_main().unwrap();\r
+/// ```\r
+/// [`Version`]: ./struct.Version.html\r
+pub fn parse(input: &str) -> Result<Version, parser::Error> {\r
+ let mut parser = Parser::new(input)?;\r
+ let version = parser.version()?;\r
+\r
+ if !parser.is_eof() {\r
+ return Err(parser::Error::MoreInput(parser.tail()?));\r
+ }\r
+\r
+ Ok(version)\r
+}\r
+\r
+impl fmt::Display for Version {\r
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\r
+ write!(f, "{}.{}.{}", self.major, self.minor, self.patch).expect("write failed");\r
+ if !self.pre.is_empty() {\r
+ let strs: Vec<_> = self.pre.iter().map(ToString::to_string).collect();\r
+ write!(f, "-{}", strs.join(".")).expect("write failed");\r
+ }\r
+ if !self.build.is_empty() {\r
+ let strs: Vec<_> = self.build.iter().map(ToString::to_string).collect();\r
+ write!(f, "+{}", strs.join(".")).expect("write failed");\r
+ }\r
+ Ok(())\r
+ }\r
+}\r
+\r
+impl fmt::Display for Identifier {\r
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\r
+ match *self {\r
+ Identifier::Numeric(ref id) => id.fmt(f),\r
+ Identifier::AlphaNumeric(ref id) => id.fmt(f),\r
+ }\r
+ }\r
+}\r
+\r
+#[cfg(test)]\r
+mod tests {\r
+ use super::*;\r
+ use crate::version;\r
+\r
+ #[test]\r
+ fn parse_empty() {\r
+ let version = "";\r
+\r
+ let parsed = version::parse(version);\r
+\r
+ assert!(\r
+ parsed.is_err(),\r
+ "empty string incorrectly considered a valid parse"\r
+ );\r
+ }\r
+\r
+ #[test]\r
+ fn parse_blank() {\r
+ let version = " ";\r
+\r
+ let parsed = version::parse(version);\r
+\r
+ assert!(\r
+ parsed.is_err(),\r
+ "blank string incorrectly considered a valid parse"\r
+ );\r
+ }\r
+\r
+ #[test]\r
+ fn parse_no_minor_patch() {\r
+ let version = "1";\r
+\r
+ let parsed = version::parse(version);\r
+\r
+ assert!(\r
+ parsed.is_err(),\r
+ format!("'{}' incorrectly considered a valid parse", version)\r
+ );\r
+ }\r
+\r
+ #[test]\r
+ fn parse_no_patch() {\r
+ let version = "1.2";\r
+\r
+ let parsed = version::parse(version);\r
+\r
+ assert!(\r
+ parsed.is_err(),\r
+ format!("'{}' incorrectly considered a valid parse", version)\r
+ );\r
+ }\r
+\r
+ #[test]\r
+ fn parse_empty_pre() {\r
+ let version = "1.2.3-";\r
+\r
+ let parsed = version::parse(version);\r
+\r
+ assert!(\r
+ parsed.is_err(),\r
+ format!("'{}' incorrectly considered a valid parse", version)\r
+ );\r
+ }\r
+\r
+ #[test]\r
+ fn parse_letters() {\r
+ let version = "a.b.c";\r
+\r
+ let parsed = version::parse(version);\r
+\r
+ assert!(\r
+ parsed.is_err(),\r
+ format!("'{}' incorrectly considered a valid parse", version)\r
+ );\r
+ }\r
+\r
+ #[test]\r
+ fn parse_with_letters() {\r
+ let version = "1.2.3 a.b.c";\r
+\r
+ let parsed = version::parse(version);\r
+\r
+ assert!(\r
+ parsed.is_err(),\r
+ format!("'{}' incorrectly considered a valid parse", version)\r
+ );\r
+ }\r
+\r
+ #[test]\r
+ fn parse_basic_version() {\r
+ let version = "1.2.3";\r
+\r
+ let parsed = version::parse(version).unwrap();\r
+\r
+ assert_eq!(1, parsed.major);\r
+ assert_eq!(2, parsed.minor);\r
+ assert_eq!(3, parsed.patch);\r
+ }\r
+\r
+ #[test]\r
+ fn parse_trims_input() {\r
+ let version = " 1.2.3 ";\r
+\r
+ let parsed = version::parse(version).unwrap();\r
+\r
+ assert_eq!(1, parsed.major);\r
+ assert_eq!(2, parsed.minor);\r
+ assert_eq!(3, parsed.patch);\r
+ }\r
+\r
+ #[test]\r
+ fn parse_no_major_leading_zeroes() {\r
+ let version = "01.0.0";\r
+\r
+ let parsed = version::parse(version);\r
+\r
+ assert!(\r
+ parsed.is_err(),\r
+ "01 incorrectly considered a valid major version"\r
+ );\r
+ }\r
+\r
+ #[test]\r
+ fn parse_no_minor_leading_zeroes() {\r
+ let version = "0.01.0";\r
+\r
+ let parsed = version::parse(version);\r
+\r
+ assert!(\r
+ parsed.is_err(),\r
+ "01 incorrectly considered a valid minor version"\r
+ );\r
+ }\r
+\r
+ #[test]\r
+ fn parse_no_patch_leading_zeroes() {\r
+ let version = "0.0.01";\r
+\r
+ let parsed = version::parse(version);\r
+\r
+ assert!(\r
+ parsed.is_err(),\r
+ "01 incorrectly considered a valid patch version"\r
+ );\r
+ }\r
+\r
+ #[test]\r
+ fn parse_no_major_overflow() {\r
+ let version = "98765432109876543210.0.0";\r
+\r
+ let parsed = version::parse(version);\r
+\r
+ assert!(\r
+ parsed.is_err(),\r
+ "98765432109876543210 incorrectly considered a valid major version"\r
+ );\r
+ }\r
+\r
+ #[test]\r
+ fn parse_no_minor_overflow() {\r
+ let version = "0.98765432109876543210.0";\r
+\r
+ let parsed = version::parse(version);\r
+\r
+ assert!(\r
+ parsed.is_err(),\r
+ "98765432109876543210 incorrectly considered a valid minor version"\r
+ );\r
+ }\r
+\r
+ #[test]\r
+ fn parse_no_patch_overflow() {\r
+ let version = "0.0.98765432109876543210";\r
+\r
+ let parsed = version::parse(version);\r
+\r
+ assert!(\r
+ parsed.is_err(),\r
+ "98765432109876543210 incorrectly considered a valid patch version"\r
+ );\r
+ }\r
+\r
+ #[test]\r
+ fn parse_basic_prerelease() {\r
+ let version = "1.2.3-pre";\r
+\r
+ let parsed = version::parse(version).unwrap();\r
+\r
+ let expected_pre = vec![Identifier::AlphaNumeric(String::from("pre"))];\r
+ assert_eq!(expected_pre, parsed.pre);\r
+ }\r
+\r
+ #[test]\r
+ fn parse_prerelease_alphanumeric() {\r
+ let version = "1.2.3-alpha1";\r
+\r
+ let parsed = version::parse(version).unwrap();\r
+\r
+ let expected_pre = vec![Identifier::AlphaNumeric(String::from("alpha1"))];\r
+ assert_eq!(expected_pre, parsed.pre);\r
+ }\r
+\r
+ #[test]\r
+ fn parse_prerelease_zero() {\r
+ let version = "1.2.3-pre.0";\r
+\r
+ let parsed = version::parse(version).unwrap();\r
+\r
+ let expected_pre = vec![\r
+ Identifier::AlphaNumeric(String::from("pre")),\r
+ Identifier::Numeric(0),\r
+ ];\r
+ assert_eq!(expected_pre, parsed.pre);\r
+ }\r
+\r
+ #[test]\r
+ fn parse_basic_build() {\r
+ let version = "1.2.3+build";\r
+\r
+ let parsed = version::parse(version).unwrap();\r
+\r
+ let expected_build = vec![Identifier::AlphaNumeric(String::from("build"))];\r
+ assert_eq!(expected_build, parsed.build);\r
+ }\r
+\r
+ #[test]\r
+ fn parse_build_alphanumeric() {\r
+ let version = "1.2.3+build5";\r
+\r
+ let parsed = version::parse(version).unwrap();\r
+\r
+ let expected_build = vec![Identifier::AlphaNumeric(String::from("build5"))];\r
+ assert_eq!(expected_build, parsed.build);\r
+ }\r
+\r
+ #[test]\r
+ fn parse_pre_and_build() {\r
+ let version = "1.2.3-alpha1+build5";\r
+\r
+ let parsed = version::parse(version).unwrap();\r
+\r
+ let expected_pre = vec![Identifier::AlphaNumeric(String::from("alpha1"))];\r
+ assert_eq!(expected_pre, parsed.pre);\r
+\r
+ let expected_build = vec![Identifier::AlphaNumeric(String::from("build5"))];\r
+ assert_eq!(expected_build, parsed.build);\r
+ }\r
+\r
+ #[test]\r
+ fn parse_complex_metadata_01() {\r
+ let version = "1.2.3-1.alpha1.9+build5.7.3aedf ";\r
+\r
+ let parsed = version::parse(version).unwrap();\r
+\r
+ let expected_pre = vec![\r
+ Identifier::Numeric(1),\r
+ Identifier::AlphaNumeric(String::from("alpha1")),\r
+ Identifier::Numeric(9),\r
+ ];\r
+ assert_eq!(expected_pre, parsed.pre);\r
+\r
+ let expected_build = vec![\r
+ Identifier::AlphaNumeric(String::from("build5")),\r
+ Identifier::Numeric(7),\r
+ Identifier::AlphaNumeric(String::from("3aedf")),\r
+ ];\r
+ assert_eq!(expected_build, parsed.build);\r
+ }\r
+\r
+ #[test]\r
+ fn parse_complex_metadata_02() {\r
+ let version = "0.4.0-beta.1+0851523";\r
+\r
+ let parsed = version::parse(version).unwrap();\r
+\r
+ let expected_pre = vec![\r
+ Identifier::AlphaNumeric(String::from("beta")),\r
+ Identifier::Numeric(1),\r
+ ];\r
+ assert_eq!(expected_pre, parsed.pre);\r
+\r
+ let expected_build = vec![Identifier::AlphaNumeric(String::from("0851523"))];\r
+ assert_eq!(expected_build, parsed.build);\r
+ }\r
+\r
+ #[test]\r
+ fn parse_metadata_overflow() {\r
+ let version = "0.4.0-beta.1+98765432109876543210";\r
+\r
+ let parsed = version::parse(version).unwrap();\r
+\r
+ let expected_pre = vec![\r
+ Identifier::AlphaNumeric(String::from("beta")),\r
+ Identifier::Numeric(1),\r
+ ];\r
+ assert_eq!(expected_pre, parsed.pre);\r
+\r
+ let expected_build = vec![Identifier::AlphaNumeric(String::from(\r
+ "98765432109876543210",\r
+ ))];\r
+ assert_eq!(expected_build, parsed.build);\r
+ }\r
+\r
+ #[test]\r
+ fn parse_regression_01() {\r
+ let version = "0.0.0-WIP";\r
+\r
+ let parsed = version::parse(version).unwrap();\r
+\r
+ assert_eq!(0, parsed.major);\r
+ assert_eq!(0, parsed.minor);\r
+ assert_eq!(0, parsed.patch);\r
+\r
+ let expected_pre = vec![Identifier::AlphaNumeric(String::from("WIP"))];\r
+ assert_eq!(expected_pre, parsed.pre);\r
+ }\r
+\r
+ #[test]\r
+ fn parse_regression_02() {\r
+ // this is used by really old versions of npm, and is valid according to semver.org\r
+ let version = "1.2.3-beta-1";\r
+\r
+ let parsed = version::parse(version).unwrap();\r
+\r
+ assert_eq!(1, parsed.major);\r
+ assert_eq!(2, parsed.minor);\r
+ assert_eq!(3, parsed.patch);\r
+\r
+ let expected_pre = vec![Identifier::AlphaNumeric(String::from("beta-1"))];\r
+ assert_eq!(expected_pre, parsed.pre);\r
+ }\r
+}\r