From 4391a5e35967fd290e3c7cab25d0de5c0beb198f Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Mon, 19 Jun 2023 15:41:32 +0200 Subject: [PATCH 1/2] fix: >=1!164.3095,<1!165 --- .../src/match_spec/parse.rs | 33 ++++--------------- .../src/version_spec/mod.rs | 2 +- .../src/version_spec/version_tree.rs | 8 +++-- 3 files changed, 14 insertions(+), 29 deletions(-) diff --git a/crates/rattler_conda_types/src/match_spec/parse.rs b/crates/rattler_conda_types/src/match_spec/parse.rs index 274cde275..861ac3ccc 100644 --- a/crates/rattler_conda_types/src/match_spec/parse.rs +++ b/crates/rattler_conda_types/src/match_spec/parse.rs @@ -1,6 +1,7 @@ use super::matcher::{StringMatcher, StringMatcherParseError}; use super::MatchSpec; use crate::package::ArchiveType; +use crate::version_spec::version_tree::recognize_constraint; use crate::version_spec::{is_start_of_version_constraint, ParseVersionSpecError}; use crate::{NamelessMatchSpec, ParseChannelError, VersionSpec}; use nom::branch::alt; @@ -9,7 +10,7 @@ use nom::character::complete::{char, multispace0, one_of}; use nom::combinator::{opt, recognize}; use nom::error::{context, ParseError}; use nom::multi::{separated_list0, separated_list1}; -use nom::sequence::{delimited, pair, separated_pair, terminated}; +use nom::sequence::{delimited, separated_pair, terminated}; use nom::{Finish, IResult}; use smallvec::SmallVec; use std::borrow::Cow; @@ -198,34 +199,10 @@ fn strip_package_name(input: &str) -> Result<(&str, &str), ParseMatchSpecError> /// Splits a string into version and build constraints. fn split_version_and_build(input: &str) -> Result<(&str, Option<&str>), ParseMatchSpecError> { - fn parse_operator(input: &str) -> IResult<&str, &str> { - alt(( - tag(">="), - tag("<="), - tag("~="), - tag("=="), - tag("!="), - tag("="), - tag("<"), - tag(">"), - ))(input) - } - - fn parse_constraint(input: &str) -> IResult<&str, &str> { - recognize(pair( - whitespace_enclosed(opt(parse_operator)), - take_till1(|c: char| { - is_start_of_version_constraint(c) - || c.is_whitespace() - || matches!(c, ',' | '|' | ')' | '(') - }), - ))(input) - } - fn parse_version_constraint_or_group(input: &str) -> IResult<&str, &str> { alt(( delimited(tag("("), parse_version_group, tag(")")), - parse_constraint, + recognize_constraint, ))(input) } @@ -486,6 +463,10 @@ mod tests { Ok(("*", Some("openblas_0"))) ); assert_eq!(split_version_and_build("* *"), Ok(("*", Some("*")))); + assert_eq!( + split_version_and_build(">=1!164.3095,<1!165"), + Ok((">=1!164.3095,<1!165", None)) + ); } #[test] diff --git a/crates/rattler_conda_types/src/version_spec/mod.rs b/crates/rattler_conda_types/src/version_spec/mod.rs index 397fa7874..2f36be694 100644 --- a/crates/rattler_conda_types/src/version_spec/mod.rs +++ b/crates/rattler_conda_types/src/version_spec/mod.rs @@ -2,7 +2,7 @@ //! [`crate::MatchSpec`], e.g.: `>=3.4,<4.0`. mod constraint; -mod version_tree; +pub(crate) mod version_tree; use crate::version_spec::constraint::{Constraint, ParseConstraintError}; use crate::version_spec::version_tree::ParseVersionTreeError; diff --git a/crates/rattler_conda_types/src/version_spec/version_tree.rs b/crates/rattler_conda_types/src/version_spec/version_tree.rs index 502865c3c..d21e01306 100644 --- a/crates/rattler_conda_types/src/version_spec/version_tree.rs +++ b/crates/rattler_conda_types/src/version_spec/version_tree.rs @@ -84,7 +84,7 @@ fn recognize_version<'a, E: ParseError<&'a str>>( } /// A parser that recognized a constraint but does not actually parse it. -fn recognize_constraint<'a, E: ParseError<&'a str> + ContextError<&'a str>>( +pub(crate) fn recognize_constraint<'a, E: ParseError<&'a str> + ContextError<&'a str>>( input: &'a str, ) -> Result<(&'a str, &'a str), nom::Err> { alt(( @@ -95,7 +95,11 @@ fn recognize_constraint<'a, E: ParseError<&'a str> + ContextError<&'a str>>( // Version with optional operator followed by optional glob. recognize(terminated( preceded( - opt(parse_operator), + opt(delimited( + opt(multispace0), + parse_operator, + opt(multispace0), + )), cut(context("version", recognize_version)), ), opt(alt((tag(".*"), tag("*")))), From 6c35e299910a332c9c156b2e5f2e2c3f7a98fc0d Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Mon, 19 Jun 2023 17:25:55 +0200 Subject: [PATCH 2/2] fix: splitting of build string and version component --- .../src/match_spec/parse.rs | 67 +++++++++++++++---- ...spec__parse__tests__parsed matchspecs.snap | 4 ++ .../src/version_spec/version_tree.rs | 20 ++++-- 3 files changed, 71 insertions(+), 20 deletions(-) diff --git a/crates/rattler_conda_types/src/match_spec/parse.rs b/crates/rattler_conda_types/src/match_spec/parse.rs index 861ac3ccc..1d5644f7a 100644 --- a/crates/rattler_conda_types/src/match_spec/parse.rs +++ b/crates/rattler_conda_types/src/match_spec/parse.rs @@ -1,16 +1,16 @@ use super::matcher::{StringMatcher, StringMatcherParseError}; use super::MatchSpec; use crate::package::ArchiveType; -use crate::version_spec::version_tree::recognize_constraint; +use crate::version_spec::version_tree::{recognize_constraint, recognize_version}; use crate::version_spec::{is_start_of_version_constraint, ParseVersionSpecError}; use crate::{NamelessMatchSpec, ParseChannelError, VersionSpec}; use nom::branch::alt; use nom::bytes::complete::{tag, take_till1, take_until, take_while, take_while1}; use nom::character::complete::{char, multispace0, one_of}; use nom::combinator::{opt, recognize}; -use nom::error::{context, ParseError}; +use nom::error::{context, ContextError, ParseError}; use nom::multi::{separated_list0, separated_list1}; -use nom::sequence::{delimited, separated_pair, terminated}; +use nom::sequence::{delimited, preceded, separated_pair, terminated}; use nom::{Finish, IResult}; use smallvec::SmallVec; use std::borrow::Cow; @@ -95,13 +95,17 @@ fn is_package_file(input: &str) -> bool { type BracketVec<'a> = SmallVec<[(&'a str, &'a str); 2]>; /// A parse combinator to filter whitespace if front and after another parser. -fn whitespace_enclosed<'a, F: 'a, O, E: ParseError<&'a str>>( - inner: F, +fn whitespace_enclosed<'a, F, O, E: ParseError<&'a str>>( + mut inner: F, ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> where F: FnMut(&'a str) -> IResult<&'a str, O, E>, { - delimited(multispace0, inner, multispace0) + move |input: &'a str| { + let (input, _) = multispace0(input)?; + let (input, o2) = inner(input)?; + multispace0(input).map(|(i, _)| (i, o2)) + } } /// Parses the contents of a bracket list `[version="1,2,3", bla=3]` @@ -199,22 +203,50 @@ fn strip_package_name(input: &str) -> Result<(&str, &str), ParseMatchSpecError> /// Splits a string into version and build constraints. fn split_version_and_build(input: &str) -> Result<(&str, Option<&str>), ParseMatchSpecError> { - fn parse_version_constraint_or_group(input: &str) -> IResult<&str, &str> { + fn parse_version_constraint_or_group<'a, E: ParseError<&'a str> + ContextError<&'a str>>( + input: &'a str, + ) -> IResult<&'a str, &'a str, E> { alt(( delimited(tag("("), parse_version_group, tag(")")), recognize_constraint, ))(input) } - fn parse_version_group(input: &str) -> IResult<&str, &str> { + fn parse_version_group<'a, E: ParseError<&'a str> + ContextError<&'a str>>( + input: &'a str, + ) -> IResult<&'a str, &'a str, E> { recognize(separated_list1( whitespace_enclosed(one_of(",|")), parse_version_constraint_or_group, ))(input) } - fn parse_version_and_build_seperator(input: &str) -> IResult<&str, &str> { - terminated(parse_version_group, opt(one_of(" =")))(input) + // Special case handling of `=*`, `=1.2.3`, or `=1*` + fn parse_special_equality<'a, E: ParseError<&'a str> + ContextError<&'a str>>( + input: &'a str, + ) -> IResult<&'a str, &'a str, E> { + // Matches ".*" or "*" but not "." + let version_glob = terminated(opt(tag(".")), tag("*")); + + // Matches a version followed by an optional version glob + let version_followed_by_glob = terminated(recognize_version, opt(version_glob)); + + // Just matches the glob operator ("*") + let just_star = tag("*"); + + recognize(preceded( + tag("="), + alt((version_followed_by_glob, just_star)), + ))(input) + } + + fn parse_version_and_build_seperator<'a, E: ParseError<&'a str> + ContextError<&'a str>>( + input: &'a str, + ) -> IResult<&'a str, &'a str, E> { + terminated( + alt((parse_special_equality, parse_version_group)), + opt(one_of(" =")), + )(input) } match parse_version_and_build_seperator(input).finish() { @@ -229,9 +261,12 @@ fn split_version_and_build(input: &str) -> Result<(&str, Option<&str>), ParseMat }, )) } - Err(nom::error::Error { .. }) => Err(ParseMatchSpecError::InvalidVersionAndBuild( - input.to_string(), - )), + Err(e @ nom::error::VerboseError { .. }) => { + eprintln!("{}", nom::error::convert_error(input, e)); + Err(ParseMatchSpecError::InvalidVersionAndBuild( + input.to_string(), + )) + } } } @@ -440,6 +475,11 @@ mod tests { #[test] fn test_split_version_and_build() { + assert_eq!( + split_version_and_build("==1.0=py27_0"), + Ok(("==1.0", Some("py27_0"))) + ); + assert_eq!(split_version_and_build("=*=cuda"), Ok(("=*", Some("cuda")))); assert_eq!( split_version_and_build("=1.2.3 0"), Ok(("=1.2.3", Some("0"))) @@ -563,6 +603,7 @@ mod tests { "foo==1.0=py27_0", "python 3.8.* *_cpython", "pytorch=*=cuda*", + "x264 >=1!164.3095,<1!165", ]; #[derive(Serialize)] diff --git a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__parsed matchspecs.snap b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__parsed matchspecs.snap index e457cc479..d89639776 100644 --- a/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__parsed matchspecs.snap +++ b/crates/rattler_conda_types/src/match_spec/snapshots/rattler_conda_types__match_spec__parse__tests__parsed matchspecs.snap @@ -1,5 +1,6 @@ --- source: crates/rattler_conda_types/src/match_spec/parse.rs +assertion_line: 629 expression: evaluated --- blas *.* mkl: @@ -22,4 +23,7 @@ pytorch=*=cuda*: name: pytorch version: "*" build: cuda* +"x264 >=1!164.3095,<1!165": + name: x264 + version: ">=1!164.3095,<1!165" diff --git a/crates/rattler_conda_types/src/version_spec/version_tree.rs b/crates/rattler_conda_types/src/version_spec/version_tree.rs index d21e01306..e29338c70 100644 --- a/crates/rattler_conda_types/src/version_spec/version_tree.rs +++ b/crates/rattler_conda_types/src/version_spec/version_tree.rs @@ -41,18 +41,18 @@ fn parse_operator<'a, E: ParseError<&'a str>>( } /// Recognizes the version epoch -fn parse_version_epoch<'a, E: ParseError<&'a str>>( +fn parse_version_epoch<'a, E: ParseError<&'a str> + ContextError<&'a str>>( input: &'a str, ) -> Result<(&'a str, u32), nom::Err> { terminated(u32, tag("!"))(input) } /// A parser that recognizes a version -fn recognize_version<'a, E: ParseError<&'a str>>( +pub(crate) fn recognize_version<'a, E: ParseError<&'a str> + ContextError<&'a str>>( input: &'a str, ) -> Result<(&'a str, &'a str), nom::Err> { /// Recognizes a single version component (`1`, `a`, `alpha`, `grub`) - fn recognize_version_component<'a, E: ParseError<&'a str>>( + fn recognize_version_component<'a, E: ParseError<&'a str> + ContextError<&'a str>>( input: &'a str, ) -> Result<(&'a str, &'a str), nom::Err> { let ident = alpha1; @@ -61,7 +61,7 @@ fn recognize_version<'a, E: ParseError<&'a str>>( } /// Recognize one or more version components (`1.2.3`) - fn recognize_version_components<'a, E: ParseError<&'a str>>( + fn recognize_version_components<'a, E: ParseError<&'a str> + ContextError<&'a str>>( input: &'a str, ) -> Result<(&'a str, &'a str), nom::Err> { recognize(tuple(( @@ -75,11 +75,14 @@ fn recognize_version<'a, E: ParseError<&'a str>>( recognize(tuple(( // Optional version epoch - opt(parse_version_epoch), + opt(context("epoch", parse_version_epoch)), // Version components - cut(recognize_version_components), + context("components", recognize_version_components), // Local version - opt(preceded(tag("+"), cut(recognize_version_components))), + opt(preceded( + tag("+"), + cut(context("local", recognize_version_components)), + )), )))(input) } @@ -171,6 +174,9 @@ impl<'a> TryFrom<&'a str> for VersionTree<'a> { Err(nom::Err::Error(e)) => { Err(ParseVersionTreeError::ParseError(convert_error(input, e))) } + Err(nom::Err::Failure(e)) => { + Err(ParseVersionTreeError::ParseError(convert_error(input, e))) + } _ => unreachable!("with all_consuming the only error can be Error"), } }