diff --git a/Cargo.toml b/Cargo.toml index bc92de9..4006aba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" license = "MIT" [dependencies] -winnow = "0.4.7" +winnow = "0.6.5" [dev-dependencies] rstest = "0.18.2" diff --git a/src/maven/version.rs b/src/maven/version.rs index 5778dae..97e3c34 100644 --- a/src/maven/version.rs +++ b/src/maven/version.rs @@ -3,9 +3,9 @@ use std::cmp::Ordering; use winnow::ascii::digit1; -use winnow::combinator::{alt, eof, repeat}; +use winnow::combinator::{alt, repeat}; use winnow::token::take_while; -use winnow::{IResult, Parser}; +use winnow::{PResult, Parser}; #[derive(Debug)] enum RawToken<'a> { @@ -33,7 +33,7 @@ struct Token { value: TokenValue, } -fn number(input: &str) -> IResult<&str, RawToken> { +fn number<'a>(input: &mut &'a str) -> PResult> { digit1 .try_map(|s| -> Result { let num = str::parse::(s)?; @@ -42,7 +42,7 @@ fn number(input: &str) -> IResult<&str, RawToken> { .parse_next(input) } -fn qualifier(input: &str) -> IResult<&str, RawToken> { +fn qualifier<'a>(input: &mut &'a str) -> PResult> { take_while( 1.., |c: char| matches!(c, 'a'..='z' | 'A'..='Z' | '+' | '_'), @@ -51,19 +51,19 @@ fn qualifier(input: &str) -> IResult<&str, RawToken> { .parse_next(input) } -fn dot_separator(input: &str) -> IResult<&str, RawToken> { +fn dot_separator<'a>(input: &mut &'a str) -> PResult> { '.'.map(|_| RawToken::DotChar).parse_next(input) } -fn hyphen_separator(input: &str) -> IResult<&str, RawToken> { +fn hyphen_separator<'a>(input: &mut &'a str) -> PResult> { '-'.map(|_| RawToken::HyphenChar).parse_next(input) } -fn raw_token(input: &str) -> IResult<&str, RawToken> { +fn raw_token<'a>(input: &mut &'a str) -> PResult> { alt((number, dot_separator, hyphen_separator, qualifier)).parse_next(input) } -fn raw_tokens(input: &str) -> IResult<&str, Vec> { +fn raw_tokens<'a>(input: &mut &'a str) -> PResult>> { repeat(1.., raw_token).parse_next(input) } @@ -166,10 +166,10 @@ fn parse_raw_tokens(raw_tokens: Vec) -> Vec { tokens } -fn version_tokens(input: &str) -> IResult<&str, Version> { +fn version_tokens(input: &mut &'_ str) -> PResult { raw_tokens .map(parse_raw_tokens) - .map(Version::from_tokens) + .map(|tokens| Version { tokens }) .parse_next(input) } @@ -231,19 +231,11 @@ struct Version { tokens: Vec, } -impl Version { - fn from_tokens(tokens: Vec) -> Version { - Version { tokens } - } +impl std::str::FromStr for Version { + type Err = String; - fn parse(input: &str) -> Option { - match (version_tokens, eof) - .map(|(version, _)| version) - .parse_next(input) - { - Ok((_, version)) => Some(version), - _ => None, - } + fn from_str(s: &str) -> Result { + version_tokens.parse(s).map_err(|e| e.to_string()) } } diff --git a/src/maven/version_tests.rs b/src/maven/version_tests.rs index f30ff48..08edacd 100644 --- a/src/maven/version_tests.rs +++ b/src/maven/version_tests.rs @@ -5,6 +5,7 @@ use insta::*; use itertools::Itertools; use rstest::rstest; use serde::ser::{Serialize, SerializeStruct, Serializer}; +use std::str::FromStr; /// @see https://github.com/apache/maven/blob/master/maven-artifact/src/test/java/org/apache/maven/artifact/versioning/ComparableVersionTest.java @@ -15,7 +16,7 @@ use serde::ser::{Serialize, SerializeStruct, Serializer}; #[case("000123", 123)] #[case("000123000", 123000)] fn parse_number(#[case] input: &str, #[case] expected: u64) { - let (_, res) = number(input).unwrap(); + let (_, res) = number.parse_peek(input).unwrap(); if let RawToken::Num(res) = res { assert_eq!(res, expected); } else { @@ -31,7 +32,7 @@ fn parse_number(#[case] input: &str, #[case] expected: u64) { #[case("foo.bar", "foo")] #[case("foo-bar", "foo")] fn parse_qualifier(#[case] input: &str, #[case] expected: &str) { - let (_, res) = qualifier(input).unwrap(); + let (_, res) = qualifier.parse_peek(input).unwrap(); if let RawToken::Qual(res) = res { assert_eq!(res, expected); } else { @@ -115,7 +116,7 @@ where #[test] fn tokenization() { let get_tokens = |input: &str| -> Vec { - let (_, output) = version_tokens(input).unwrap(); + let output = version_tokens.parse(input).unwrap(); output.tokens }; @@ -138,13 +139,13 @@ fn tokenization() { #[case("1.0.0-foo.0.0", "1-foo")] #[case("1.0.0-0.0.0", "1")] fn equivalent_tokenization(#[case] input: &str, #[case] expected: &str) { - assert_eq!(version_tokens(input), version_tokens(expected)); + assert_eq!(version_tokens.parse(input), version_tokens.parse(expected)); } #[test] fn version_constructor() { assert_eq!( - Version::parse("1.2.3").unwrap(), + Version::from_str("1.2.3").unwrap(), Version { tokens: vec![ Token { @@ -163,7 +164,7 @@ fn version_constructor() { } ); - assert!(Version::parse("1.2.3=#!").is_none()); + assert!(Version::from_str("1.2.3=#!").is_err()); } #[rstest] @@ -217,8 +218,8 @@ fn version_constructor() { #[case("1.0.0.x", "1-x")] #[case("1.x", "1.0.0-x")] fn equality(#[case] left: &str, #[case] right: &str) { - let left = Version::parse(left).unwrap(); - let right = Version::parse(right).unwrap(); + let left = Version::from_str(left).unwrap(); + let right = Version::from_str(right).unwrap(); assert_eq!(left.partial_cmp(&right), Some(Ordering::Equal)); assert_eq!(right.partial_cmp(&left), Some(Ordering::Equal)); } @@ -247,8 +248,8 @@ fn equality(#[case] left: &str, #[case] right: &str) { #[case("2.0.1", "2.0.1-123")] #[case("2.0.1-xyz", "2.0.1-123")] fn comparison(#[case] left: &str, #[case] right: &str) { - let left = Version::parse(left).unwrap(); - let right = Version::parse(right).unwrap(); + let left = Version::from_str(left).unwrap(); + let right = Version::from_str(right).unwrap(); assert_eq!(left.partial_cmp(&right), Some(Ordering::Less)); assert_eq!(right.partial_cmp(&left), Some(Ordering::Greater)); } @@ -256,9 +257,9 @@ fn comparison(#[case] left: &str, #[case] right: &str) { /// @see https://issues.apache.org/jira/browse/MNG-5568 #[test] fn mng_5568() { - let a = Version::parse("6.1.0").unwrap(); - let b = Version::parse("6.1.0rc3").unwrap(); - let c = Version::parse("6.1H.5-beta").unwrap(); // this is the unusual version string, with 'H' in the middle + let a = Version::from_str("6.1.0").unwrap(); + let b = Version::from_str("6.1.0rc3").unwrap(); + let c = Version::from_str("6.1H.5-beta").unwrap(); // this is the unusual version string, with 'H' in the middle assert_eq!(b.partial_cmp(&a), Some(Ordering::Less)); // classical assert_eq!(b.partial_cmp(&c), Some(Ordering::Less)); // now b < c, but before MNG-5568, we had b > c @@ -268,10 +269,10 @@ fn mng_5568() { /// @see https://jira.apache.org/jira/browse/MNG-6572 #[test] fn mng_6572() { - let a = Version::parse("20190126.230843").unwrap(); // resembles a SNAPSHOT - let b = Version::parse("1234567890.12345").unwrap(); // 10 digit number - let c = Version::parse("123456789012345.1H.5-beta").unwrap(); // 15 digit number - let d = Version::parse("12345678901234567890.1H.5-beta").unwrap(); // 20 digit number + let a = Version::from_str("20190126.230843").unwrap(); // resembles a SNAPSHOT + let b = Version::from_str("1234567890.12345").unwrap(); // 10 digit number + let c = Version::from_str("123456789012345.1H.5-beta").unwrap(); // 15 digit number + let d = Version::from_str("12345678901234567890.1H.5-beta").unwrap(); // 20 digit number assert_eq!(a.partial_cmp(&b), Some(Ordering::Less)); assert_eq!(b.partial_cmp(&c), Some(Ordering::Less)); @@ -307,8 +308,8 @@ fn version_equal_with_leading_zeroes() { for combination in versions.into_iter().combinations(2) { let (left, right) = (combination[0], combination[1]); - let left = Version::parse(left).unwrap(); - let right = Version::parse(right).unwrap(); + let left = Version::from_str(left).unwrap(); + let right = Version::from_str(right).unwrap(); assert_eq!(left.partial_cmp(&right), Some(Ordering::Equal)); assert_eq!(right.partial_cmp(&left), Some(Ordering::Equal)); } @@ -340,8 +341,8 @@ fn test_version_zero_equal_with_leading_zeroes() { for combination in versions.into_iter().combinations(2) { let (left, right) = (combination[0], combination[1]); - let left = Version::parse(left).unwrap(); - let right = Version::parse(right).unwrap(); + let left = Version::from_str(left).unwrap(); + let right = Version::from_str(right).unwrap(); assert_eq!(left.partial_cmp(&right), Some(Ordering::Equal)); assert_eq!(right.partial_cmp(&left), Some(Ordering::Equal)); } @@ -350,9 +351,9 @@ fn test_version_zero_equal_with_leading_zeroes() { /// @see https://issues.apache.org/jira/browse/MNG-6964 #[test] fn test_mng_6964() { - let a = Version::parse("1-0.alpha").unwrap(); - let b = Version::parse("1-0.beta").unwrap(); - let c = Version::parse("1").unwrap(); + let a = Version::from_str("1-0.alpha").unwrap(); + let b = Version::from_str("1-0.beta").unwrap(); + let c = Version::from_str("1").unwrap(); assert_eq!(a.partial_cmp(&c), Some(Ordering::Less)); // Now a < c, but before MNG-6964 they were equal assert_eq!(b.partial_cmp(&c), Some(Ordering::Less)); // Now b < c, but before MNG-6964 they were equal @@ -376,14 +377,14 @@ fn test_mng_7644() { for qual in quals { // 1.0.0.X1 < 1.0.0-X2 for any string x - let a = Version::parse(&format!("1.0.0.{}1", qual)).unwrap(); - let b = Version::parse(&format!("1.0.0-{}2", qual)).unwrap(); + let a = Version::from_str(&format!("1.0.0.{}1", qual)).unwrap(); + let b = Version::from_str(&format!("1.0.0-{}2", qual)).unwrap(); assert_eq!(a.partial_cmp(&b), Some(Ordering::Less)); // 2.0.X == 2-X == 2.0.0.X for any string x - let c = Version::parse(&format!("2-{}", qual)).unwrap(); - let d = Version::parse(&format!("2.0.{}", qual)).unwrap(); - let e = Version::parse(&format!("2.0.0.{}", qual)).unwrap(); + let c = Version::from_str(&format!("2-{}", qual)).unwrap(); + let d = Version::from_str(&format!("2.0.{}", qual)).unwrap(); + let e = Version::from_str(&format!("2.0.0.{}", qual)).unwrap(); assert_eq!(c.partial_cmp(&d), Some(Ordering::Equal)); // previously ordered, now equals assert_eq!(c.partial_cmp(&e), Some(Ordering::Equal)); // previously ordered, now equals assert_eq!(d.partial_cmp(&e), Some(Ordering::Equal)); // previously ordered, now equals