diff --git a/crates/uv-requirements-txt/src/lib.rs b/crates/uv-requirements-txt/src/lib.rs index c4893f22b..a1e171c71 100644 --- a/crates/uv-requirements-txt/src/lib.rs +++ b/crates/uv-requirements-txt/src/lib.rs @@ -536,7 +536,19 @@ fn parse_entry( end, } } else if s.eat_if("-e") || s.eat_if("--editable") { - s.eat_whitespace(); + if s.eat_if('=') { + // Explicit equals sign. + } else if s.eat_if(char::is_whitespace) { + // Key and value are separated by whitespace instead. + s.eat_whitespace(); + } else { + let (line, column) = calculate_row_column(content, s.cursor()); + return Err(RequirementsTxtParserError::Parser { + message: format!("Expected '=' or whitespace, found {:?}", s.peek()), + line, + column, + }); + } let source = if requirements_txt == Path::new("-") { None @@ -851,10 +863,10 @@ fn parse_value<'a, T>( while_pattern: impl Pattern, ) -> Result<&'a str, RequirementsTxtParserError> { if s.eat_if('=') { - // Explicit equals sign + // Explicit equals sign. Ok(s.eat_while(while_pattern).trim_end()) } else if s.eat_if(char::is_whitespace) { - // Key and value are separated by whitespace instead + // Key and value are separated by whitespace instead. s.eat_whitespace(); Ok(s.eat_while(while_pattern).trim_end()) } else { diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 990865c0f..f7920dc65 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -1302,6 +1302,60 @@ fn install_editable_pep_508_requirements_txt() -> Result<()> { "### ); + requirements_txt.write_str(&indoc::formatdoc! {r" + --editable black[d] @ file://{workspace_root}/scripts/packages/black_editable + ", + workspace_root = context.workspace_root.simplified_display(), + })?; + + uv_snapshot!(context.filters(), context.pip_install() + .arg("-r") + .arg("requirements.txt"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Audited 1 package in [TIME] + "### + ); + + requirements_txt.write_str(&indoc::formatdoc! {r" + --editable=black[d] @ file://{workspace_root}/scripts/packages/black_editable + ", + workspace_root = context.workspace_root.simplified_display(), + })?; + + uv_snapshot!(context.filters(), context.pip_install() + .arg("-r") + .arg("requirements.txt"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Audited 1 package in [TIME] + "### + ); + + requirements_txt.write_str(&indoc::formatdoc! {r" + --editable= black[d] @ file://{workspace_root}/scripts/packages/black_editable + ", + workspace_root = context.workspace_root.simplified_display(), + })?; + + uv_snapshot!(context.filters(), context.pip_install() + .arg("-r") + .arg("requirements.txt"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Audited 1 package in [TIME] + "### + ); + Ok(()) }