use nom::{
bytes::complete::tag,
character::complete::digit1,
combinator::{map_res, verify},
sequence::separated_pair,
IResult, Parser,
};
use time::{Date, Weekday};
use crate::{
error::Error,
version::{Patch, Version, Week, Year},
};
fn no_leading_zero(input: &str) -> bool {
input.len() == 1 || !input.starts_with('0')
}
pub fn year(input: &str) -> IResult<&str, Year> {
nom::branch::alt((
nom::combinator::map(nom::combinator::verify(digit1, |s: &str| s == "0"), |_| 0),
nom::sequence::preceded(
nom::bytes::complete::tag("-"),
nom::combinator::verify(digit1, |s: &str| !s.starts_with('0') && s != "0"),
)
.map(|digits: &str| -digits.parse::<Year>().unwrap_or(0)),
nom::combinator::verify(digit1, |s: &str| !s.starts_with('0') || s.len() == 1)
.map(|s: &str| s.parse::<Year>().unwrap_or(0)),
))
.parse(input)
}
pub fn week(input: &str) -> IResult<&str, Week> {
verify(
map_res(verify(digit1, no_leading_zero), |s: &str| {
s.parse::<Week>()
.map_err(|_| Error::parse(s, "invalid week number"))
}),
|w: &Week| (1..=53).contains(w),
)
.parse(input)
}
pub fn year_week(input: &str) -> IResult<&str, (Year, Week)> {
verify(separated_pair(year, tag("."), week), |(year, week)| {
let full_year = *year + 2000;
Date::from_iso_week_date(full_year, *week, Weekday::Monday).is_ok()
})
.parse(input)
}
pub fn patch(input: &str) -> IResult<&str, Patch> {
map_res(verify(digit1, no_leading_zero), |s: &str| {
s.parse::<Patch>()
.map_err(|_| Error::parse(s, "invalid patch number"))
})
.parse(input)
}
pub fn parse_version(input: &str) -> IResult<&str, Version> {
let (input, ((year, week), patch)) = separated_pair(year_week, tag("."), patch).parse(input)?;
Ok((input, Version {
year,
week,
patch,
}))
}
pub use parse_version as version;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_year() {
assert_eq!(year("25"), Ok(("", 25)));
assert_eq!(year("-5"), Ok(("", -5)));
assert!(year("025").is_err());
}
#[test]
fn test_week() {
assert_eq!(week("12"), Ok(("", 12)));
assert!(week("0").is_err());
assert!(week("54").is_err());
assert!(week("01").is_err());
}
#[test]
fn test_year_week() {
assert_eq!(year_week("25.12"), Ok(("", (25, 12))));
assert!(year_week("25.54").is_err());
assert!(year_week("25.00").is_err());
}
#[test]
fn test_patch() {
assert_eq!(patch("1"), Ok(("", 1)));
assert_eq!(patch("123"), Ok(("", 123)));
assert!(patch("01").is_err());
}
#[test]
fn test_version() {
assert_eq!(
parse_version("25.12.1"),
Ok(("", Version {
year: 25,
week: 12,
patch: 1
}))
);
assert!(parse_version("25.54.1").is_err());
assert!(parse_version("25.12.01").is_err());
}
#[test]
fn test_iso_week_date_parsing() {
assert_eq!(year_week("21.1"), Ok(("", (21, 1))));
assert_eq!(year_week("20.53"), Ok(("", (20, 53))));
assert!(year_week("21.53").is_err());
}
}