mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-23 16:51:40 +00:00
Add dedicated error message for direct filesystem paths in requirements (#2369)
## Summary This is analogous to #669, but for cases in which the package name is a filesystem path. In such cases, we'll fail when parsing the _package name_, since it doesn't start with a valid character, as opposed to failing when we go to parse the remaining version specifier. Inspired by https://github.com/astral-sh/uv/issues/2356.
This commit is contained in:
parent
6d67c93e0b
commit
ebca3197dd
1 changed files with 61 additions and 6 deletions
|
@ -564,19 +564,33 @@ impl Display for Cursor<'_> {
|
||||||
fn parse_name(cursor: &mut Cursor) -> Result<PackageName, Pep508Error> {
|
fn parse_name(cursor: &mut Cursor) -> Result<PackageName, Pep508Error> {
|
||||||
// https://peps.python.org/pep-0508/#names
|
// https://peps.python.org/pep-0508/#names
|
||||||
// ^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$ with re.IGNORECASE
|
// ^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$ with re.IGNORECASE
|
||||||
|
let start = cursor.pos();
|
||||||
let mut name = String::new();
|
let mut name = String::new();
|
||||||
|
|
||||||
if let Some((index, char)) = cursor.next() {
|
if let Some((index, char)) = cursor.next() {
|
||||||
if matches!(char, 'A'..='Z' | 'a'..='z' | '0'..='9') {
|
if matches!(char, 'A'..='Z' | 'a'..='z' | '0'..='9') {
|
||||||
name.push(char);
|
name.push(char);
|
||||||
} else {
|
} else {
|
||||||
return Err(Pep508Error {
|
// Check if the user added a filesystem path without a package name. pip supports this
|
||||||
|
// in `requirements.txt`, but it doesn't adhere to the PEP 508 grammar.
|
||||||
|
let mut clone = cursor.clone().at(start);
|
||||||
|
return if looks_like_file_path(&mut clone) {
|
||||||
|
Err(Pep508Error {
|
||||||
|
message: Pep508ErrorSource::UnsupportedRequirement("URL requirement must be preceded by a package name. Add the name of the package before the URL (e.g., `package_name @ /path/to/file`).".to_string()),
|
||||||
|
start,
|
||||||
|
len: clone.pos() - start,
|
||||||
|
input: clone.to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Pep508Error {
|
||||||
message: Pep508ErrorSource::String(format!(
|
message: Pep508ErrorSource::String(format!(
|
||||||
"Expected package name starting with an alphanumeric character, found '{char}'"
|
"Expected package name starting with an alphanumeric character, found '{char}'"
|
||||||
)),
|
)),
|
||||||
start: index,
|
start: index,
|
||||||
len: char.len_utf8(),
|
len: char.len_utf8(),
|
||||||
input: cursor.to_string(),
|
input: cursor.to_string(),
|
||||||
});
|
})
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(Pep508Error {
|
return Err(Pep508Error {
|
||||||
|
@ -752,6 +766,35 @@ fn parse_url(cursor: &mut Cursor, working_dir: Option<&Path>) -> Result<Verbatim
|
||||||
Ok(url)
|
Ok(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a filesystem path from the [`Cursor`], advancing the [`Cursor`] to the end of the path.
|
||||||
|
///
|
||||||
|
/// Returns `false` if the path is not a clear and unambiguous filesystem path.
|
||||||
|
fn looks_like_file_path(cursor: &mut Cursor) -> bool {
|
||||||
|
let Some((_, first_char)) = cursor.next() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ex) `/bin/ls`
|
||||||
|
if first_char == '\\' || first_char == '/' || first_char == '.' {
|
||||||
|
// Read until the end of the path.
|
||||||
|
cursor.take_while(|char| !char.is_whitespace());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ex) `C:`
|
||||||
|
if first_char.is_alphabetic() {
|
||||||
|
if let Some((_, second_char)) = cursor.next() {
|
||||||
|
if second_char == ':' {
|
||||||
|
// Read until the end of the path.
|
||||||
|
cursor.take_while(|char| !char.is_whitespace());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a `VerbatimUrl` to represent the requirement.
|
/// Create a `VerbatimUrl` to represent the requirement.
|
||||||
fn preprocess_url(
|
fn preprocess_url(
|
||||||
url: &str,
|
url: &str,
|
||||||
|
@ -1491,6 +1534,18 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_bare_file_path() {
|
||||||
|
assert_snapshot!(
|
||||||
|
parse_err(r"/path/to/flask.tar.gz"),
|
||||||
|
@r###"
|
||||||
|
URL requirement must be preceded by a package name. Add the name of the package before the URL (e.g., `package_name @ /path/to/file`).
|
||||||
|
/path/to/flask.tar.gz
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn error_no_comma_between_extras() {
|
fn error_no_comma_between_extras() {
|
||||||
assert_snapshot!(
|
assert_snapshot!(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue