Skip to content

Commit

Permalink
fix: >=1!164.3095,<1!165 (#220)
Browse files Browse the repository at this point in the history
* fix: >=1!164.3095,<1!165
* fix: splitting of build string and version component
  • Loading branch information
baszalmstra authored Jun 19, 2023
1 parent f5eb8fd commit f1ee61a
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 47 deletions.
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

0 comments on commit f1ee61a

Please sign in to comment.