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 | |