1 use version::Identifier; 2 use recognize::{Recognize, Alt, OneOrMore, Inclusive, OneByte}; 3 use std::str::from_utf8; 4 5 // by the time we get here, we know that it's all valid characters, so this doesn't need to return 6 // a result or anything 7 fn parse_meta(s: &str) -> Vec<Identifier> { 8 // Originally, I wanted to implement this method via calling parse, but parse is tolerant of 9 // leading zeroes, and we want anything with leading zeroes to be considered alphanumeric, not 10 // numeric. So the strategy is to check with a recognizer first, and then call parse once 11 // we've determined that it's a number without a leading zero. 12 s.split(".") 13 .map(|part| { 14 // another wrinkle: we made sure that any number starts with a 15 // non-zero. But there's a problem: an actual zero is a number, yet 16 // gets left out by this heuristic. So let's also check for the 17 // single, lone zero. 18 if is_alpha_numeric(part) { 19 Identifier::AlphaNumeric(part.to_string()) 20 } else { 21 // we can unwrap here because we know it is only digits due to the regex 22 Identifier::Numeric(part.parse().unwrap()) 23 } 24 }).collect() 25 } 26 27 // parse optional metadata (preceded by the prefix character) 28 pub fn parse_optional_meta(s: &[u8], prefix_char: u8)-> Result<(Vec<Identifier>, usize), String> { 29 if let Some(len) = prefix_char.p(s) { 30 let start = len; 31 if let Some(len) = letters_numbers_dash_dot(&s[start..]) { 32 let end = start + len; 33 Ok((parse_meta(from_utf8(&s[start..end]).unwrap()), end)) 34 } else { 35 Err("Error parsing prerelease".to_string()) 36 } 37 } else { 38 Ok((Vec::new(), 0)) 39 } 40 } 41 42 pub fn is_alpha_numeric(s: &str) -> bool { 43 if let Some((_val, len)) = numeric_identifier(s.as_bytes()) { 44 // Return true for number with leading zero 45 // Note: doing it this way also handily makes overflow fail over. 46 len != s.len() 47 } else { 48 true 49 } 50 } 51 52 // Note: could plumb overflow error up to return value as Result 53 pub fn numeric_identifier(s: &[u8]) -> Option<(u64, usize)> { 54 if let Some(len) = Alt(b'0', OneOrMore(Inclusive(b'0'..b'9'))).p(s) { 55 from_utf8(&s[0..len]).unwrap().parse().ok().map(|val| (val, len)) 56 } else { 57 None 58 } 59 } 60 61 pub fn letters_numbers_dash_dot(s: &[u8]) -> Option<usize> { 62 OneOrMore(OneByte(|c| c == b'-' || c == b'.' || 63 (b'0' <= c && c <= b'9') || 64 (b'a' <= c && c <= b'z') || 65 (b'A' <= c && c <= b'Z'))).p(s) 66 } 67