Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: >=1!164.3095,<1!165 #220

Merged
merged 2 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 59 additions & 37 deletions crates/rattler_conda_types/src/match_spec/parse.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
use super::matcher::{StringMatcher, StringMatcherParseError};
use super::MatchSpec;
use crate::package::ArchiveType;
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, pair, separated_pair, terminated};
use nom::sequence::{delimited, preceded, separated_pair, terminated};
use nom::{Finish, IResult};
use smallvec::SmallVec;
use std::borrow::Cow;
Expand Down Expand Up @@ -94,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]`
Expand Down Expand Up @@ -198,46 +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_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> {
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(")")),
parse_constraint,
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() {
Expand All @@ -252,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(),
))
}
}
}

Expand Down Expand Up @@ -463,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")))
Expand All @@ -486,6 +503,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]
Expand Down Expand Up @@ -582,6 +603,7 @@ mod tests {
"foo==1.0=py27_0",
"python 3.8.* *_cpython",
"pytorch=*=cuda*",
"x264 >=1!164.3095,<1!165",
];

#[derive(Serialize)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: crates/rattler_conda_types/src/match_spec/parse.rs
assertion_line: 629
expression: evaluated
---
blas *.* mkl:
Expand All @@ -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"

2 changes: 1 addition & 1 deletion crates/rattler_conda_types/src/version_spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 19 additions & 9 deletions crates/rattler_conda_types/src/version_spec/version_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<E>> {
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<E>> {
/// 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<E>> {
let ident = alpha1;
Expand All @@ -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<E>> {
recognize(tuple((
Expand All @@ -75,16 +75,19 @@ 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)
}

/// 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<E>> {
alt((
Expand All @@ -95,7 +98,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("*")))),
Expand Down Expand Up @@ -167,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"),
}
}
Expand Down