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