summaryrefslogtreecommitdiff
path: root/semver-parser/src/range.rs
diff options
context:
space:
mode:
Diffstat (limited to 'semver-parser/src/range.rs')
-rw-r--r--semver-parser/src/range.rs696
1 files changed, 696 insertions, 0 deletions
diff --git a/semver-parser/src/range.rs b/semver-parser/src/range.rs
new file mode 100644
index 0000000..858be9f
--- /dev/null
+++ b/semver-parser/src/range.rs
@@ -0,0 +1,696 @@
+use common::{self, numeric_identifier, letters_numbers_dash_dot};
+use version::Identifier;
+use std::str::{FromStr, from_utf8};
+use recognize::*;
+
+#[derive(Debug)]
+pub struct VersionReq {
+ pub predicates: Vec<Predicate>,
+}
+
+#[derive(PartialEq,Debug)]
+pub enum WildcardVersion {
+ Major,
+ Minor,
+ Patch,
+}
+
+#[derive(PartialEq,Debug)]
+pub enum Op {
+ Ex, // Exact
+ Gt, // Greater than
+ GtEq, // Greater than or equal to
+ Lt, // Less than
+ LtEq, // Less than or equal to
+ Tilde, // e.g. ~1.0.0
+ Compatible, // compatible by definition of semver, indicated by ^
+ Wildcard(WildcardVersion), // x.y.*, x.*, *
+}
+
+impl FromStr for Op {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Op, String> {
+ match s {
+ "=" => Ok(Op::Ex),
+ ">" => Ok(Op::Gt),
+ ">=" => Ok(Op::GtEq),
+ "<" => Ok(Op::Lt),
+ "<=" => Ok(Op::LtEq),
+ "~" => Ok(Op::Tilde),
+ "^" => Ok(Op::Compatible),
+ _ => Err(String::from("Could not parse Op")),
+ }
+ }
+}
+
+#[derive(PartialEq,Debug)]
+pub struct Predicate {
+ pub op: Op,
+ pub major: u64,
+ pub minor: Option<u64>,
+ pub patch: Option<u64>,
+ pub pre: Vec<Identifier>,
+}
+
+fn numeric_or_wild(s: &[u8]) -> Option<(Option<u64>, usize)> {
+ if let Some((val, len)) = numeric_identifier(s) {
+ Some((Some(val), len))
+ } else if let Some(len) = OneOf(b"*xX").p(s) {
+ Some((None, len))
+ } else {
+ None
+ }
+}
+
+fn dot_numeric_or_wild(s: &[u8]) -> Option<(Option<u64>, usize)> {
+ b'.'.p(s).and_then(|len|
+ numeric_or_wild(&s[len..]).map(|(val, len2)| (val, len + len2))
+ )
+}
+
+fn operation(s: &[u8]) -> Option<(Op, usize)> {
+ if let Some(len) = "=".p(s) {
+ Some((Op::Ex, len))
+ } else if let Some(len) = ">=".p(s) {
+ Some((Op::GtEq, len))
+ } else if let Some(len) = ">".p(s) {
+ Some((Op::Gt, len))
+ } else if let Some(len) = "<=".p(s) {
+ Some((Op::LtEq, len))
+ } else if let Some(len) = "<".p(s) {
+ Some((Op::Lt, len))
+ } else if let Some(len) = "~".p(s) {
+ Some((Op::Tilde, len))
+ } else if let Some(len) = "^".p(s) {
+ Some((Op::Compatible, len))
+ } else {
+ None
+ }
+}
+
+fn whitespace(s: &[u8]) -> Option<usize> {
+ ZeroOrMore(OneOf(b"\t\r\n ")).p(s)
+}
+
+pub fn parse_predicate(range: &str) -> Result<Predicate, String> {
+ let s = range.trim().as_bytes();
+ let mut i = 0;
+ let mut operation = if let Some((op, len)) = operation(&s[i..]) {
+ i += len;
+ op
+ } else {
+ // operations default to Compatible
+ Op::Compatible
+ };
+ if let Some(len) = whitespace.p(&s[i..]) {
+ i += len;
+ }
+ let major = if let Some((major, len)) = numeric_identifier(&s[i..]) {
+ i += len;
+ major
+ } else {
+ return Err("Error parsing major version number: ".to_string());
+ };
+ let minor = if let Some((minor, len)) = dot_numeric_or_wild(&s[i..]) {
+ i += len;
+ if minor.is_none() {
+ operation = Op::Wildcard(WildcardVersion::Minor);
+ }
+ minor
+ } else {
+ None
+ };
+ let patch = if let Some((patch, len)) = dot_numeric_or_wild(&s[i..]) {
+ i += len;
+ if patch.is_none() {
+ operation = Op::Wildcard(WildcardVersion::Patch);
+ }
+ patch
+ } else {
+ None
+ };
+ let (pre, pre_len) = common::parse_optional_meta(&s[i..], b'-')?;
+ i += pre_len;
+ if let Some(len) = (b'+', letters_numbers_dash_dot).p(&s[i..]) {
+ i += len;
+ }
+ if i != s.len() {
+ return Err("Extra junk after valid predicate: ".to_string() +
+ from_utf8(&s[i..]).unwrap());
+ }
+ Ok(Predicate {
+ op: operation,
+ major: major,
+ minor: minor,
+ patch: patch,
+ pre: pre,
+ })
+}
+
+pub fn parse(ranges: &str) -> Result<VersionReq, String> {
+ // null is an error
+ if ranges == "\0" {
+ return Err(String::from("Null is not a valid VersionReq"));
+ }
+
+ // an empty range is a major version wildcard
+ // so is a lone * or x of either capitalization
+ if (ranges == "")
+ || (ranges == "*")
+ || (ranges == "x")
+ || (ranges == "X") {
+ return Ok(VersionReq {
+ predicates: vec![Predicate {
+ op: Op::Wildcard(WildcardVersion::Major),
+ major: 0,
+ minor: None,
+ patch: None,
+ pre: Vec::new(),
+ }],
+ });
+ }
+
+
+ let ranges = ranges.trim();
+
+ let predicates: Result<Vec<_>, String> = ranges
+ .split(",")
+ .map(|range| {
+ parse_predicate(range)
+ })
+ .collect();
+
+ let predicates = try!(predicates);
+
+ if predicates.len() == 0 {
+ return Err(String::from("VersionReq did not parse properly"));
+ }
+
+ Ok(VersionReq {
+ predicates: predicates,
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use range;
+ use version::Identifier;
+
+ #[test]
+ fn test_parsing_default() {
+ let r = range::parse("1.0.0").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Compatible,
+ major: 1,
+ minor: Some(0),
+ patch: Some(0),
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ fn test_parsing_exact_01() {
+ let r = range::parse("=1.0.0").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Ex,
+ major: 1,
+ minor: Some(0),
+ patch: Some(0),
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ fn test_parsing_exact_02() {
+ let r = range::parse("=0.9.0").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Ex,
+ major: 0,
+ minor: Some(9),
+ patch: Some(0),
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ fn test_parsing_exact_03() {
+ let r = range::parse("=0.1.0-beta2.a").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Ex,
+ major: 0,
+ minor: Some(1),
+ patch: Some(0),
+ pre: vec![Identifier::AlphaNumeric(String::from("beta2")),
+ Identifier::AlphaNumeric(String::from("a"))],
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ pub fn test_parsing_greater_than() {
+ let r = range::parse("> 1.0.0").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Gt,
+ major: 1,
+ minor: Some(0),
+ patch: Some(0),
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ pub fn test_parsing_greater_than_01() {
+ let r = range::parse(">= 1.0.0").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::GtEq,
+ major: 1,
+ minor: Some(0),
+ patch: Some(0),
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ pub fn test_parsing_greater_than_02() {
+ let r = range::parse(">= 2.1.0-alpha2").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::GtEq,
+ major: 2,
+ minor: Some(1),
+ patch: Some(0),
+ pre: vec![Identifier::AlphaNumeric(String::from("alpha2"))],
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ pub fn test_parsing_less_than() {
+ let r = range::parse("< 1.0.0").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Lt,
+ major: 1,
+ minor: Some(0),
+ patch: Some(0),
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ pub fn test_parsing_less_than_eq() {
+ let r = range::parse("<= 2.1.0-alpha2").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::LtEq,
+ major: 2,
+ minor: Some(1),
+ patch: Some(0),
+ pre: vec![Identifier::AlphaNumeric(String::from("alpha2"))],
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ pub fn test_parsing_tilde() {
+ let r = range::parse("~1").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Tilde,
+ major: 1,
+ minor: None,
+ patch: None,
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ pub fn test_parsing_compatible() {
+ let r = range::parse("^0").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Compatible,
+ major: 0,
+ minor: None,
+ patch: None,
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ fn test_parsing_blank() {
+ let r = range::parse("").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Wildcard(WildcardVersion::Major),
+ major: 0,
+ minor: None,
+ patch: None,
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ fn test_parsing_wildcard() {
+ let r = range::parse("*").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Wildcard(WildcardVersion::Major),
+ major: 0,
+ minor: None,
+ patch: None,
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ fn test_parsing_x() {
+ let r = range::parse("x").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Wildcard(WildcardVersion::Major),
+ major: 0,
+ minor: None,
+ patch: None,
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ fn test_parsing_capital_x() {
+ let r = range::parse("X").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Wildcard(WildcardVersion::Major),
+ major: 0,
+ minor: None,
+ patch: None,
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ fn test_parsing_minor_wildcard_star() {
+ let r = range::parse("1.*").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Wildcard(WildcardVersion::Minor),
+ major: 1,
+ minor: None,
+ patch: None,
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ fn test_parsing_minor_wildcard_x() {
+ let r = range::parse("1.x").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Wildcard(WildcardVersion::Minor),
+ major: 1,
+ minor: None,
+ patch: None,
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ fn test_parsing_minor_wildcard_capital_x() {
+ let r = range::parse("1.X").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Wildcard(WildcardVersion::Minor),
+ major: 1,
+ minor: None,
+ patch: None,
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ fn test_parsing_patch_wildcard_star() {
+ let r = range::parse("1.2.*").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Wildcard(WildcardVersion::Patch),
+ major: 1,
+ minor: Some(2),
+ patch: None,
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ fn test_parsing_patch_wildcard_x() {
+ let r = range::parse("1.2.x").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Wildcard(WildcardVersion::Patch),
+ major: 1,
+ minor: Some(2),
+ patch: None,
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ fn test_parsing_patch_wildcard_capital_x() {
+ let r = range::parse("1.2.X").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Wildcard(WildcardVersion::Patch),
+ major: 1,
+ minor: Some(2),
+ patch: None,
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+ }
+
+ #[test]
+ pub fn test_multiple_01() {
+ let r = range::parse("> 0.0.9, <= 2.5.3").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Gt,
+ major: 0,
+ minor: Some(0),
+ patch: Some(9),
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+
+ assert_eq!(Predicate {
+ op: Op::LtEq,
+ major: 2,
+ minor: Some(5),
+ patch: Some(3),
+ pre: Vec::new(),
+ },
+ r.predicates[1]
+ );
+ }
+
+ #[test]
+ pub fn test_multiple_02() {
+ let r = range::parse("0.3.0, 0.4.0").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Compatible,
+ major: 0,
+ minor: Some(3),
+ patch: Some(0),
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+
+ assert_eq!(Predicate {
+ op: Op::Compatible,
+ major: 0,
+ minor: Some(4),
+ patch: Some(0),
+ pre: Vec::new(),
+ },
+ r.predicates[1]
+ );
+ }
+
+ #[test]
+ pub fn test_multiple_03() {
+ let r = range::parse("<= 0.2.0, >= 0.5.0").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::LtEq,
+ major: 0,
+ minor: Some(2),
+ patch: Some(0),
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+
+ assert_eq!(Predicate {
+ op: Op::GtEq,
+ major: 0,
+ minor: Some(5),
+ patch: Some(0),
+ pre: Vec::new(),
+ },
+ r.predicates[1]
+ );
+ }
+
+ #[test]
+ pub fn test_multiple_04() {
+ let r = range::parse("0.1.0, 0.1.4, 0.1.6").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::Compatible,
+ major: 0,
+ minor: Some(1),
+ patch: Some(0),
+ pre: Vec::new(),
+ },
+ r.predicates[0]
+ );
+
+ assert_eq!(Predicate {
+ op: Op::Compatible,
+ major: 0,
+ minor: Some(1),
+ patch: Some(4),
+ pre: Vec::new(),
+ },
+ r.predicates[1]
+ );
+
+ assert_eq!(Predicate {
+ op: Op::Compatible,
+ major: 0,
+ minor: Some(1),
+ patch: Some(6),
+ pre: Vec::new(),
+ },
+ r.predicates[2]
+ );
+ }
+
+ #[test]
+ pub fn test_multiple_05() {
+ let r = range::parse(">=0.5.1-alpha3, <0.6").unwrap();
+
+ assert_eq!(Predicate {
+ op: Op::GtEq,
+ major: 0,
+ minor: Some(5),
+ patch: Some(1),
+ pre: vec![Identifier::AlphaNumeric(String::from("alpha3"))],
+ },
+ r.predicates[0]
+ );
+
+ assert_eq!(Predicate {
+ op: Op::Lt,
+ major: 0,
+ minor: Some(6),
+ patch: None,
+ pre: Vec::new(),
+ },
+ r.predicates[1]
+ );
+ }
+
+ #[test]
+ fn test_parse_build_metadata_with_predicate() {
+ assert_eq!(range::parse("^1.2.3+meta").unwrap().predicates[0].op,
+ Op::Compatible);
+ assert_eq!(range::parse("~1.2.3+meta").unwrap().predicates[0].op,
+ Op::Tilde);
+ assert_eq!(range::parse("=1.2.3+meta").unwrap().predicates[0].op,
+ Op::Ex);
+ assert_eq!(range::parse("<=1.2.3+meta").unwrap().predicates[0].op,
+ Op::LtEq);
+ assert_eq!(range::parse(">=1.2.3+meta").unwrap().predicates[0].op,
+ Op::GtEq);
+ assert_eq!(range::parse("<1.2.3+meta").unwrap().predicates[0].op,
+ Op::Lt);
+ assert_eq!(range::parse(">1.2.3+meta").unwrap().predicates[0].op,
+ Op::Gt);
+ }
+
+ #[test]
+ pub fn test_parse_errors() {
+ assert!(range::parse("\0").is_err());
+ assert!(range::parse(">= >= 0.0.2").is_err());
+ assert!(range::parse(">== 0.0.2").is_err());
+ assert!(range::parse("a.0.0").is_err());
+ assert!(range::parse("1.0.0-").is_err());
+ assert!(range::parse(">=").is_err());
+ assert!(range::parse("> 0.1.0,").is_err());
+ assert!(range::parse("> 0.3.0, ,").is_err());
+ }
+
+ #[test]
+ pub fn test_large_major_version() {
+ assert!(range::parse("18446744073709551617.0.0").is_err());
+ }
+
+ #[test]
+ pub fn test_large_minor_version() {
+ assert!(range::parse("0.18446744073709551617.0").is_err());
+ }
+
+ #[test]
+ pub fn test_large_patch_version() {
+ assert!(range::parse("0.0.18446744073709551617").is_err());
+ }
+}