diff --git a/crates/pep508-rs/src/lib.rs b/crates/pep508-rs/src/lib.rs index f4d8d64a6..a9e072b27 100644 --- a/crates/pep508-rs/src/lib.rs +++ b/crates/pep508-rs/src/lib.rs @@ -1710,4 +1710,10 @@ mod tests { ); } } + + #[test] + fn no_space_after_operator() { + let requirement = Requirement::from_str("pytest;'4.0'>=python_version").unwrap(); + assert_eq!(requirement.to_string(), "pytest ; '4.0' >= python_version"); + } } diff --git a/crates/pep508-rs/src/marker.rs b/crates/pep508-rs/src/marker.rs index 0fce8e3e6..fb723daa0 100644 --- a/crates/pep508-rs/src/marker.rs +++ b/crates/pep508-rs/src/marker.rs @@ -1150,9 +1150,15 @@ impl Display for MarkerTree { /// version_cmp = wsp* <'<=' | '<' | '!=' | '==' | '>=' | '>' | '~=' | '==='> /// marker_op = version_cmp | (wsp* 'in') | (wsp* 'not' wsp+ 'in') /// ``` +/// The `wsp*` has already been consumed by the caller. fn parse_marker_operator(cursor: &mut Cursor) -> Result { - let (start, len) = - cursor.take_while(|char| !char.is_whitespace() && char != '\'' && char != '"'); + let (start, len) = if cursor.peek_char().is_some_and(|c| c.is_alphabetic()) { + // "in" or "not" + cursor.take_while(|char| !char.is_whitespace() && char != '\'' && char != '"') + } else { + // A mathematical operator + cursor.take_while(|char| matches!(char, '<' | '=' | '>' | '~' | '!')) + }; let operator = cursor.slice(start, len); if operator == "not" { // 'not' wsp+ 'in'