mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-28 04:54:47 +00:00
Add explicit error message for URLs without package names (#669)
`pip` supports installing packages without names (e.g., `git+https://github.com/pallets/flask.git`), but it doesn't adhere to the PEP grammar, and we don't yet support it (and may never) (#313). This PR adds a dedicated error path for such cases, to ensure that we can give meaningful feedback to the user: ``` error: Couldn't parse requirement in requirements.in position 0 to 18 Caused by: URL requirement is missing a package name; expected: `package_name @ https://google.com` https://google.com ^^^^^^^^^^^^^^^^^^ ``` Closes https://github.com/astral-sh/puffin/issues/650.
This commit is contained in:
parent
71964ec7a8
commit
f62458f600
1 changed files with 46 additions and 20 deletions
|
@ -415,6 +415,15 @@ impl<'a> Cursor<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a new cursor starting at the given position.
|
||||||
|
pub fn at(self, pos: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
input: self.input,
|
||||||
|
chars: self.input[pos..].chars(),
|
||||||
|
pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the current byte position of the cursor.
|
/// Returns the current byte position of the cursor.
|
||||||
fn pos(&self) -> usize {
|
fn pos(&self) -> usize {
|
||||||
self.pos
|
self.pos
|
||||||
|
@ -778,6 +787,8 @@ fn parse_version_specifier_parentheses(
|
||||||
|
|
||||||
/// Parse a [dependency specifier](https://packaging.python.org/en/latest/specifications/dependency-specifiers)
|
/// Parse a [dependency specifier](https://packaging.python.org/en/latest/specifications/dependency-specifiers)
|
||||||
fn parse(cursor: &mut Cursor) -> Result<Requirement, Pep508Error> {
|
fn parse(cursor: &mut Cursor) -> Result<Requirement, Pep508Error> {
|
||||||
|
let start = cursor.pos();
|
||||||
|
|
||||||
// Technically, the grammar is:
|
// Technically, the grammar is:
|
||||||
// ```text
|
// ```text
|
||||||
// name_req = name wsp* extras? wsp* versionspec? wsp* quoted_marker?
|
// name_req = name wsp* extras? wsp* versionspec? wsp* quoted_marker?
|
||||||
|
@ -810,27 +821,30 @@ fn parse(cursor: &mut Cursor) -> Result<Requirement, Pep508Error> {
|
||||||
Some('(') => parse_version_specifier_parentheses(cursor)?,
|
Some('(') => parse_version_specifier_parentheses(cursor)?,
|
||||||
Some('<' | '=' | '>' | '~' | '!') => parse_version_specifier(cursor)?,
|
Some('<' | '=' | '>' | '~' | '!') => parse_version_specifier(cursor)?,
|
||||||
Some(';') | None => None,
|
Some(';') | None => None,
|
||||||
// Ex) `https://...` or `git+https://...`
|
|
||||||
Some(':') | Some('+') => {
|
|
||||||
return Err(Pep508Error {
|
|
||||||
message: Pep508ErrorSource::String(
|
|
||||||
"URL requirement is missing a package name; expected: `package_name @`"
|
|
||||||
.to_string(),
|
|
||||||
),
|
|
||||||
start: cursor.pos(),
|
|
||||||
len: 1,
|
|
||||||
input: cursor.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Some(other) => {
|
Some(other) => {
|
||||||
return Err(Pep508Error {
|
// Rewind to the start of the version specifier, to see if the user added a URL without
|
||||||
message: Pep508ErrorSource::String(format!(
|
// a package name. pip supports this in `requirements.txt`, but it doesn't adhere to
|
||||||
"Expected one of `@`, `(`, `<`, `=`, `>`, `~`, `!`, `;`, found `{other}`"
|
// the PEP 508 grammar.
|
||||||
)),
|
let mut clone = cursor.clone().at(start);
|
||||||
start: cursor.pos(),
|
return if let Ok(url) = parse_url(&mut clone) {
|
||||||
len: other.len_utf8(),
|
Err(Pep508Error {
|
||||||
input: cursor.to_string(),
|
message: Pep508ErrorSource::String(format!(
|
||||||
})
|
"URL requirement is missing a package name; expected: `package_name @ {url}`",
|
||||||
|
)),
|
||||||
|
start,
|
||||||
|
len: clone.pos() - start,
|
||||||
|
input: clone.to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Pep508Error {
|
||||||
|
message: Pep508ErrorSource::String(format!(
|
||||||
|
"Expected one of `@`, `(`, `<`, `=`, `>`, `~`, `!`, `;`, found `{other}`"
|
||||||
|
)),
|
||||||
|
start: cursor.pos(),
|
||||||
|
len: other.len_utf8(),
|
||||||
|
input: cursor.to_string(),
|
||||||
|
})
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1286,6 +1300,18 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_bare_url() {
|
||||||
|
assert_err(
|
||||||
|
r#"git+https://github.com/pallets/flask.git"#,
|
||||||
|
indoc! {"
|
||||||
|
URL requirement is missing a package name; expected: `package_name @ git+https://github.com/pallets/flask.git`
|
||||||
|
git+https://github.com/pallets/flask.git
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn error_no_comma_between_extras() {
|
fn error_no_comma_between_extras() {
|
||||||
assert_err(
|
assert_err(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue