use std::str;
use nom;
use nom::IResult;
use version::Identifier;
pub fn try_parse(i: &[u8]) -> Result<super::Version, String> {
match version(i) {
IResult::Done(rest, version) => {
if rest.len() > 0 {
let err = format!("Failed with unparsed input: '{}'",
String::from_utf8(rest.to_vec()).unwrap());
Err(err)
} else {
Ok(version)
}
}
_ => Err("Parse error".to_string()),
}
}
fn number(i: &[u8]) -> IResult<&[u8], u64> {
use std::str::from_utf8;
map_res!(i,
map_res!(nom::digit, from_utf8),
|d| str::FromStr::from_str(d))
}
fn ascii_or_hyphen(chr: u8) -> bool {
chr == 45 || (chr >= 48 && chr <= 57) || (chr >= 65 && chr <= 90) || (chr >= 97 && chr <= 122)
}
named!(take_ascii_or_hyphen, take_while!(ascii_or_hyphen));
fn convert_identifiers(identifiers: Vec<&str>) -> Vec<Identifier> {
let mut result = Vec::new();
for identifier in identifiers {
match identifier.parse() {
Ok(n) => result.push(Identifier::Numeric(n)),
Err(_) => result.push(Identifier::AlphaNumeric(identifier.to_string())),
}
}
result
}
fn identifiers(i: &[u8]) -> IResult<&[u8], Vec<Identifier>> {
use std::str::from_utf8;
map!(i,
separated_list!(tag!("."), map_res!(take_ascii_or_hyphen, from_utf8)),
convert_identifiers)
}
named!(dot_number<&[u8], u64>, preceded!(char!('.'), number));
named!(pre<&[u8], Option<Vec<Identifier> > >, opt!(complete!(preceded!(tag!("-"), identifiers))));
named!(build<&[u8], Option<Vec<Identifier> > >, opt!(complete!(preceded!(tag!("+"), identifiers))));
named!(extras<&[u8], (Option<Vec<Identifier>>, Option<Vec<Identifier>>) >, pair!(pre, build));
named!(version<&[u8], super::Version>, chain!(
major: number ~
minor: dot_number ~
patch: dot_number ~
extras: extras,
|| {
super::Version {
major: major,
minor: minor,
patch: patch,
pre: extras.0.clone().unwrap_or(Vec::new()),
build: extras.1.clone().unwrap_or(Vec::new()),
}
}
));
#[cfg(test)]
mod tests {
use super::number;
use super::dot_number;
use super::version;
use version::Identifier;
use Version;
fn done<T>(x: T) -> ::nom::IResult<&'static [u8], T> {
::nom::IResult::Done(&[][..], x)
}
#[test]
fn parse_number() {
let v = "10".as_bytes();
assert_eq!(number(v), done(10));
}
#[test]
fn parse_dot_number() {
let v = ".10".as_bytes();
assert_eq!(dot_number(v), done(10));
}
#[test]
fn parse_version() {
let v1 = "10.11.12".as_bytes();
let v2 = Version {
major: 10,
minor: 11,
patch: 12,
pre: Vec::new(),
build: Vec::new(),
};
assert_eq!(version(v1), done(v2));
}
#[test]
fn parse_pre_basic() {
let v1 = "1.0.0-alpha".as_bytes();
let v2 = Version {
major: 1,
minor: 0,
patch: 0,
pre: vec![Identifier::AlphaNumeric(String::from("alpha"))],
build: Vec::new(),
};
assert_eq!(version(v1), done(v2));
}
#[test]
fn parse_pre_dot() {
let v1 = "1.0.0-alpha.1".as_bytes();
let v2 = Version {
major: 1,
minor: 0,
patch: 0,
pre: vec![Identifier::AlphaNumeric(String::from("alpha")), Identifier::Numeric(1)],
build: Vec::new(),
};
assert_eq!(version(v1), done(v2));
}
#[test]
fn parse_build_basic() {
let v1 = "1.0.0-alpha+001".as_bytes();
let v2 = Version {
major: 1,
minor: 0,
patch: 0,
pre: vec![Identifier::AlphaNumeric(String::from("alpha"))],
build: vec![Identifier::Numeric(1)],
};
assert_eq!(version(v1), done(v2));
}
#[test]
fn parse_build_no_pre() {
let v1 = "1.0.0+001".as_bytes();
let v2 = Version {
major: 1,
minor: 0,
patch: 0,
pre: Vec::new(),
build: vec![Identifier::Numeric(1)],
};
assert_eq!(version(v1), done(v2));
}
#[test]
fn parse_hypen_in_pre() {
let v1 = "3.0.0-rc1-1".as_bytes();
let v2 = Version {
major: 3,
minor: 0,
patch: 0,
pre: vec![Identifier::AlphaNumeric(String::from("rc1-1"))],
build: Vec::new(),
};
assert_eq!(version(v1), done(v2));
}
}