| 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 | sSplit<'_, &'static str>.split("." ) |
| 13 | .map(|part: &str| { |
| 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: usize) = prefix_char.p(s) { |
| 30 | let start: usize = len; |
| 31 | if let Some(len: usize) = letters_numbers_dash_dot(&s[start..]) { |
| 32 | let end: usize = 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: u64, len: usize)) = 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: usize) = Alt(b'0' , OneOrMore(Inclusive(b'0' ..b'9' ))).p(s) { |
| 55 | from_utf8(&s[0..len]).unwrap().parse().ok().map(|val: u64| (val, len)) |
| 56 | } else { |
| 57 | None |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | pub fn letters_numbers_dash_dot(s: &[u8]) -> Option<usize> { |
| 62 | OneOrMore(OneByte(|c: u8| 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 | |