mirror of https://github.com/astral-sh/uv
Always treat paths after -e as directories
This commit is contained in:
parent
20ab80ad8f
commit
2688a12609
|
|
@ -40,7 +40,7 @@ pub use crate::marker::{
|
||||||
};
|
};
|
||||||
pub use crate::origin::RequirementOrigin;
|
pub use crate::origin::RequirementOrigin;
|
||||||
#[cfg(feature = "non-pep508-extensions")]
|
#[cfg(feature = "non-pep508-extensions")]
|
||||||
pub use crate::unnamed::{UnnamedRequirement, UnnamedRequirementUrl};
|
pub use crate::unnamed::{PathHint, UnnamedRequirement, UnnamedRequirementUrl};
|
||||||
pub use crate::verbatim_url::{
|
pub use crate::verbatim_url::{
|
||||||
Scheme, VerbatimUrl, VerbatimUrlError, expand_env_vars, looks_like_git_repository,
|
Scheme, VerbatimUrl, VerbatimUrlError, expand_env_vars, looks_like_git_repository,
|
||||||
split_scheme, strip_host,
|
split_scheme, strip_host,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,15 @@ use crate::{
|
||||||
parse_extras_cursor, split_extras, split_scheme, strip_host,
|
parse_extras_cursor, split_extras, split_scheme, strip_host,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A hint about whether a path refers to a file or directory.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum PathHint {
|
||||||
|
/// The path is known to be a file (e.g., a wheel or source distribution).
|
||||||
|
File,
|
||||||
|
/// The path is known to be a directory (e.g., an editable install).
|
||||||
|
Directory,
|
||||||
|
}
|
||||||
|
|
||||||
/// An extension over [`Pep508Url`] that also supports parsing unnamed requirements, namely paths.
|
/// An extension over [`Pep508Url`] that also supports parsing unnamed requirements, namely paths.
|
||||||
///
|
///
|
||||||
/// The error type is fixed to the same as the [`Pep508Url`] impl error.
|
/// The error type is fixed to the same as the [`Pep508Url`] impl error.
|
||||||
|
|
@ -21,9 +30,40 @@ pub trait UnnamedRequirementUrl: Pep508Url {
|
||||||
fn parse_path(path: impl AsRef<Path>, working_dir: impl AsRef<Path>)
|
fn parse_path(path: impl AsRef<Path>, working_dir: impl AsRef<Path>)
|
||||||
-> Result<Self, Self::Err>;
|
-> Result<Self, Self::Err>;
|
||||||
|
|
||||||
|
/// Parse a URL from a relative or absolute path, with a hint about whether the path is a file
|
||||||
|
/// or directory.
|
||||||
|
///
|
||||||
|
/// The hint is used to determine whether the path should be parsed as a file or directory. If
|
||||||
|
/// no such hint is provided, we'll query for the path kind; if the path doesn't exist, it'll be
|
||||||
|
/// treated as a directory if and only if it is extensionless.
|
||||||
|
fn parse_path_with_hint(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
working_dir: impl AsRef<Path>,
|
||||||
|
hint: Option<PathHint>,
|
||||||
|
) -> Result<Self, Self::Err> {
|
||||||
|
// Default implementation ignores the hint.
|
||||||
|
let _ = hint;
|
||||||
|
Self::parse_path(path, working_dir)
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a URL from an absolute path.
|
/// Parse a URL from an absolute path.
|
||||||
fn parse_absolute_path(path: impl AsRef<Path>) -> Result<Self, Self::Err>;
|
fn parse_absolute_path(path: impl AsRef<Path>) -> Result<Self, Self::Err>;
|
||||||
|
|
||||||
|
/// Parse a URL from an absolute path, with a hint about whether the path is a file or
|
||||||
|
/// directory.
|
||||||
|
///
|
||||||
|
/// The hint is used to determine whether the path should be parsed as a file or directory. If
|
||||||
|
/// no such hint is provided, we'll query for the path kind; if the path doesn't exist, it'll be
|
||||||
|
/// treated as a directory if and only if it is extensionless.
|
||||||
|
fn parse_absolute_path_with_hint(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
hint: Option<PathHint>,
|
||||||
|
) -> Result<Self, Self::Err> {
|
||||||
|
// Default implementation ignores the hint.
|
||||||
|
let _ = hint;
|
||||||
|
Self::parse_absolute_path(path)
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a URL from a string.
|
/// Parse a URL from a string.
|
||||||
fn parse_unnamed_url(given: impl AsRef<str>) -> Result<Self, Self::Err>;
|
fn parse_unnamed_url(given: impl AsRef<str>) -> Result<Self, Self::Err>;
|
||||||
|
|
||||||
|
|
@ -110,10 +150,26 @@ impl<Url: UnnamedRequirementUrl> UnnamedRequirement<Url> {
|
||||||
working_dir: impl AsRef<Path>,
|
working_dir: impl AsRef<Path>,
|
||||||
reporter: &mut impl Reporter,
|
reporter: &mut impl Reporter,
|
||||||
) -> Result<Self, Pep508Error<Url>> {
|
) -> Result<Self, Pep508Error<Url>> {
|
||||||
parse_unnamed_requirement(
|
Self::parse_with_hint(input, working_dir, reporter, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a PEP 508-like direct URL requirement without a package name, with a hint about
|
||||||
|
/// whether the path is a file or directory.
|
||||||
|
///
|
||||||
|
/// The hint is used to determine whether the path should be parsed as a file or directory. If
|
||||||
|
/// no such hint is provided, we'll query for the path kind; if the path doesn't exist, it'll be
|
||||||
|
/// treated as a directory if and only if it is extensionless.
|
||||||
|
pub fn parse_with_hint(
|
||||||
|
input: &str,
|
||||||
|
working_dir: impl AsRef<Path>,
|
||||||
|
reporter: &mut impl Reporter,
|
||||||
|
hint: Option<PathHint>,
|
||||||
|
) -> Result<Self, Pep508Error<Url>> {
|
||||||
|
parse_unnamed_requirement_with_hint(
|
||||||
&mut Cursor::new(input),
|
&mut Cursor::new(input),
|
||||||
Some(working_dir.as_ref()),
|
Some(working_dir.as_ref()),
|
||||||
reporter,
|
reporter,
|
||||||
|
hint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -155,11 +211,22 @@ fn parse_unnamed_requirement<Url: UnnamedRequirementUrl>(
|
||||||
cursor: &mut Cursor,
|
cursor: &mut Cursor,
|
||||||
working_dir: Option<&Path>,
|
working_dir: Option<&Path>,
|
||||||
reporter: &mut impl Reporter,
|
reporter: &mut impl Reporter,
|
||||||
|
) -> Result<UnnamedRequirement<Url>, Pep508Error<Url>> {
|
||||||
|
parse_unnamed_requirement_with_hint(cursor, working_dir, reporter, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a PEP 508-like direct URL specifier without a package name, with a hint about whether
|
||||||
|
/// the path is a file or directory.
|
||||||
|
fn parse_unnamed_requirement_with_hint<Url: UnnamedRequirementUrl>(
|
||||||
|
cursor: &mut Cursor,
|
||||||
|
working_dir: Option<&Path>,
|
||||||
|
reporter: &mut impl Reporter,
|
||||||
|
hint: Option<PathHint>,
|
||||||
) -> Result<UnnamedRequirement<Url>, Pep508Error<Url>> {
|
) -> Result<UnnamedRequirement<Url>, Pep508Error<Url>> {
|
||||||
cursor.eat_whitespace();
|
cursor.eat_whitespace();
|
||||||
|
|
||||||
// Parse the URL itself, along with any extras.
|
// Parse the URL itself, along with any extras.
|
||||||
let (url, extras) = parse_unnamed_url::<Url>(cursor, working_dir)?;
|
let (url, extras) = parse_unnamed_url::<Url>(cursor, working_dir, hint)?;
|
||||||
|
|
||||||
// wsp*
|
// wsp*
|
||||||
cursor.eat_whitespace();
|
cursor.eat_whitespace();
|
||||||
|
|
@ -213,6 +280,7 @@ fn preprocess_unnamed_url<Url: UnnamedRequirementUrl>(
|
||||||
cursor: &Cursor,
|
cursor: &Cursor,
|
||||||
start: usize,
|
start: usize,
|
||||||
len: usize,
|
len: usize,
|
||||||
|
#[cfg_attr(not(feature = "non-pep508-extensions"), allow(unused))] hint: Option<PathHint>,
|
||||||
) -> Result<(Url, Vec<ExtraName>), Pep508Error<Url>> {
|
) -> Result<(Url, Vec<ExtraName>), Pep508Error<Url>> {
|
||||||
// Split extras _before_ expanding the URL. We assume that the extras are not environment
|
// Split extras _before_ expanding the URL. We assume that the extras are not environment
|
||||||
// variables. If we parsed the extras after expanding the URL, then the verbatim representation
|
// variables. If we parsed the extras after expanding the URL, then the verbatim representation
|
||||||
|
|
@ -251,7 +319,7 @@ fn preprocess_unnamed_url<Url: UnnamedRequirementUrl>(
|
||||||
|
|
||||||
#[cfg(feature = "non-pep508-extensions")]
|
#[cfg(feature = "non-pep508-extensions")]
|
||||||
if let Some(working_dir) = working_dir {
|
if let Some(working_dir) = working_dir {
|
||||||
let url = Url::parse_path(path.as_ref(), working_dir)
|
let url = Url::parse_path_with_hint(path.as_ref(), working_dir, hint)
|
||||||
.map_err(|err| Pep508Error {
|
.map_err(|err| Pep508Error {
|
||||||
message: Pep508ErrorSource::UrlError(err),
|
message: Pep508ErrorSource::UrlError(err),
|
||||||
start,
|
start,
|
||||||
|
|
@ -262,7 +330,7 @@ fn preprocess_unnamed_url<Url: UnnamedRequirementUrl>(
|
||||||
return Ok((url, extras));
|
return Ok((url, extras));
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = Url::parse_absolute_path(path.as_ref())
|
let url = Url::parse_absolute_path_with_hint(path.as_ref(), hint)
|
||||||
.map_err(|err| Pep508Error {
|
.map_err(|err| Pep508Error {
|
||||||
message: Pep508ErrorSource::UrlError(err),
|
message: Pep508ErrorSource::UrlError(err),
|
||||||
start,
|
start,
|
||||||
|
|
@ -289,7 +357,7 @@ fn preprocess_unnamed_url<Url: UnnamedRequirementUrl>(
|
||||||
// Ex) `C:\Users\ferris\wheel-0.42.0.tar.gz`
|
// Ex) `C:\Users\ferris\wheel-0.42.0.tar.gz`
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(working_dir) = working_dir {
|
if let Some(working_dir) = working_dir {
|
||||||
let url = Url::parse_path(expanded.as_ref(), working_dir)
|
let url = Url::parse_path_with_hint(expanded.as_ref(), working_dir, hint)
|
||||||
.map_err(|err| Pep508Error {
|
.map_err(|err| Pep508Error {
|
||||||
message: Pep508ErrorSource::UrlError(err),
|
message: Pep508ErrorSource::UrlError(err),
|
||||||
start,
|
start,
|
||||||
|
|
@ -300,7 +368,7 @@ fn preprocess_unnamed_url<Url: UnnamedRequirementUrl>(
|
||||||
return Ok((url, extras));
|
return Ok((url, extras));
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = Url::parse_absolute_path(expanded.as_ref())
|
let url = Url::parse_absolute_path_with_hint(expanded.as_ref(), hint)
|
||||||
.map_err(|err| Pep508Error {
|
.map_err(|err| Pep508Error {
|
||||||
message: Pep508ErrorSource::UrlError(err),
|
message: Pep508ErrorSource::UrlError(err),
|
||||||
start,
|
start,
|
||||||
|
|
@ -314,7 +382,7 @@ fn preprocess_unnamed_url<Url: UnnamedRequirementUrl>(
|
||||||
} else {
|
} else {
|
||||||
// Ex) `../editable/`
|
// Ex) `../editable/`
|
||||||
if let Some(working_dir) = working_dir {
|
if let Some(working_dir) = working_dir {
|
||||||
let url = Url::parse_path(expanded.as_ref(), working_dir)
|
let url = Url::parse_path_with_hint(expanded.as_ref(), working_dir, hint)
|
||||||
.map_err(|err| Pep508Error {
|
.map_err(|err| Pep508Error {
|
||||||
message: Pep508ErrorSource::UrlError(err),
|
message: Pep508ErrorSource::UrlError(err),
|
||||||
start,
|
start,
|
||||||
|
|
@ -325,7 +393,7 @@ fn preprocess_unnamed_url<Url: UnnamedRequirementUrl>(
|
||||||
return Ok((url, extras));
|
return Ok((url, extras));
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = Url::parse_absolute_path(expanded.as_ref())
|
let url = Url::parse_absolute_path_with_hint(expanded.as_ref(), hint)
|
||||||
.map_err(|err| Pep508Error {
|
.map_err(|err| Pep508Error {
|
||||||
message: Pep508ErrorSource::UrlError(err),
|
message: Pep508ErrorSource::UrlError(err),
|
||||||
start,
|
start,
|
||||||
|
|
@ -357,6 +425,7 @@ fn preprocess_unnamed_url<Url: UnnamedRequirementUrl>(
|
||||||
fn parse_unnamed_url<Url: UnnamedRequirementUrl>(
|
fn parse_unnamed_url<Url: UnnamedRequirementUrl>(
|
||||||
cursor: &mut Cursor,
|
cursor: &mut Cursor,
|
||||||
working_dir: Option<&Path>,
|
working_dir: Option<&Path>,
|
||||||
|
hint: Option<PathHint>,
|
||||||
) -> Result<(Url, Vec<ExtraName>), Pep508Error<Url>> {
|
) -> Result<(Url, Vec<ExtraName>), Pep508Error<Url>> {
|
||||||
// wsp*
|
// wsp*
|
||||||
cursor.eat_whitespace();
|
cursor.eat_whitespace();
|
||||||
|
|
@ -413,7 +482,7 @@ fn parse_unnamed_url<Url: UnnamedRequirementUrl>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = preprocess_unnamed_url(url, working_dir, cursor, start, len)?;
|
let url = preprocess_unnamed_url(url, working_dir, cursor, start, len, hint)?;
|
||||||
|
|
||||||
Ok(url)
|
Ok(url)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ use uv_cache_key::{CacheKey, CacheKeyHasher};
|
||||||
use uv_distribution_filename::{DistExtension, ExtensionError};
|
use uv_distribution_filename::{DistExtension, ExtensionError};
|
||||||
use uv_git_types::{GitUrl, GitUrlParseError};
|
use uv_git_types::{GitUrl, GitUrlParseError};
|
||||||
use uv_pep508::{
|
use uv_pep508::{
|
||||||
Pep508Url, UnnamedRequirementUrl, VerbatimUrl, VerbatimUrlError, looks_like_git_repository,
|
PathHint, Pep508Url, UnnamedRequirementUrl, VerbatimUrl, VerbatimUrlError,
|
||||||
|
looks_like_git_repository,
|
||||||
};
|
};
|
||||||
use uv_redacted::{DisplaySafeUrl, DisplaySafeUrlError};
|
use uv_redacted::{DisplaySafeUrl, DisplaySafeUrlError};
|
||||||
|
|
||||||
|
|
@ -79,11 +80,26 @@ impl UnnamedRequirementUrl for VerbatimParsedUrl {
|
||||||
fn parse_path(
|
fn parse_path(
|
||||||
path: impl AsRef<Path>,
|
path: impl AsRef<Path>,
|
||||||
working_dir: impl AsRef<Path>,
|
working_dir: impl AsRef<Path>,
|
||||||
|
) -> Result<Self, Self::Err> {
|
||||||
|
Self::parse_path_with_hint(path, working_dir, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_path_with_hint(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
working_dir: impl AsRef<Path>,
|
||||||
|
hint: Option<PathHint>,
|
||||||
) -> Result<Self, Self::Err> {
|
) -> Result<Self, Self::Err> {
|
||||||
let verbatim = VerbatimUrl::from_path(&path, &working_dir)?;
|
let verbatim = VerbatimUrl::from_path(&path, &working_dir)?;
|
||||||
let verbatim_path = verbatim.as_path()?;
|
let verbatim_path = verbatim.as_path()?;
|
||||||
let is_dir = if let Ok(metadata) = verbatim_path.metadata() {
|
let is_dir = if let Ok(metadata) = verbatim_path.metadata() {
|
||||||
metadata.is_dir()
|
metadata.is_dir()
|
||||||
|
} else if DistExtension::from_path(&path).is_ok() {
|
||||||
|
// If the path has a recognized distribution extension (like `.whl` or `.tar.gz`),
|
||||||
|
// treat it as a file regardless of the hint.
|
||||||
|
false
|
||||||
|
} else if let Some(hint) = hint {
|
||||||
|
// Use the hint for ambiguous paths (paths with unrecognized extensions like `.bar`).
|
||||||
|
hint == PathHint::Directory
|
||||||
} else {
|
} else {
|
||||||
verbatim_path.extension().is_none()
|
verbatim_path.extension().is_none()
|
||||||
};
|
};
|
||||||
|
|
@ -112,10 +128,24 @@ impl UnnamedRequirementUrl for VerbatimParsedUrl {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_absolute_path(path: impl AsRef<Path>) -> Result<Self, Self::Err> {
|
fn parse_absolute_path(path: impl AsRef<Path>) -> Result<Self, Self::Err> {
|
||||||
|
Self::parse_absolute_path_with_hint(path, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_absolute_path_with_hint(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
hint: Option<PathHint>,
|
||||||
|
) -> Result<Self, Self::Err> {
|
||||||
let verbatim = VerbatimUrl::from_absolute_path(&path)?;
|
let verbatim = VerbatimUrl::from_absolute_path(&path)?;
|
||||||
let verbatim_path = verbatim.as_path()?;
|
let verbatim_path = verbatim.as_path()?;
|
||||||
let is_dir = if let Ok(metadata) = verbatim_path.metadata() {
|
let is_dir = if let Ok(metadata) = verbatim_path.metadata() {
|
||||||
metadata.is_dir()
|
metadata.is_dir()
|
||||||
|
} else if DistExtension::from_path(&path).is_ok() {
|
||||||
|
// If the path has a recognized distribution extension (like `.whl` or `.tar.gz`),
|
||||||
|
// treat it as a file regardless of the hint.
|
||||||
|
false
|
||||||
|
} else if let Some(hint) = hint {
|
||||||
|
// Use the hint for ambiguous paths (paths with unrecognized extensions like `.bar`).
|
||||||
|
hint == PathHint::Directory
|
||||||
} else {
|
} else {
|
||||||
verbatim_path.extension().is_none()
|
verbatim_path.extension().is_none()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1643,6 +1643,10 @@ mod test {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[test_case(Path::new("bare-url.txt"))]
|
#[test_case(Path::new("bare-url.txt"))]
|
||||||
#[test_case(Path::new("editable.txt"))]
|
#[test_case(Path::new("editable.txt"))]
|
||||||
|
// Note: `semicolon.txt` contains a syntax error (missing whitespace before `;`), but since
|
||||||
|
// it's an editable requirement, we parse it as a directory path. The error will occur later
|
||||||
|
// when the path doesn't exist.
|
||||||
|
#[test_case(Path::new("semicolon.txt"))]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn parse_unix(path: &Path) {
|
async fn parse_unix(path: &Path) {
|
||||||
let working_dir = workspace_test_data_dir().join("requirements-txt");
|
let working_dir = workspace_test_data_dir().join("requirements-txt");
|
||||||
|
|
@ -1662,7 +1666,6 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[test_case(Path::new("semicolon.txt"))]
|
|
||||||
#[test_case(Path::new("hash.txt"))]
|
#[test_case(Path::new("hash.txt"))]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn parse_err(path: &Path) {
|
async fn parse_err(path: &Path) {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ use std::path::Path;
|
||||||
|
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_pep508::{
|
use uv_pep508::{
|
||||||
Pep508Error, Pep508ErrorSource, RequirementOrigin, TracingReporter, UnnamedRequirement,
|
PathHint, Pep508Error, Pep508ErrorSource, RequirementOrigin, TracingReporter,
|
||||||
|
UnnamedRequirement,
|
||||||
};
|
};
|
||||||
use uv_pypi_types::{ParsedDirectoryUrl, ParsedUrl, VerbatimParsedUrl};
|
use uv_pypi_types::{ParsedDirectoryUrl, ParsedUrl, VerbatimParsedUrl};
|
||||||
|
|
||||||
|
|
@ -132,15 +133,26 @@ impl RequirementsTxtRequirement {
|
||||||
working_dir: impl AsRef<Path>,
|
working_dir: impl AsRef<Path>,
|
||||||
editable: bool,
|
editable: bool,
|
||||||
) -> Result<Self, Box<Pep508Error<VerbatimParsedUrl>>> {
|
) -> Result<Self, Box<Pep508Error<VerbatimParsedUrl>>> {
|
||||||
|
// When parsing an editable requirement, hint that the path is a directory. Editable
|
||||||
|
// requirements always refer to local directories (containing a `pyproject.toml` or
|
||||||
|
// `setup.py`), so when the path doesn't exist, we should assume it's a directory rather
|
||||||
|
// than a file with an unrecognized extension (like `foo.bar`).
|
||||||
|
let hint = if editable {
|
||||||
|
Some(PathHint::Directory)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// Attempt to parse as a PEP 508-compliant requirement.
|
// Attempt to parse as a PEP 508-compliant requirement.
|
||||||
match uv_pep508::Requirement::parse(input, &working_dir) {
|
match uv_pep508::Requirement::parse(input, &working_dir) {
|
||||||
Ok(requirement) => {
|
Ok(requirement) => {
|
||||||
// As a special-case, interpret `dagster` as `./dagster` if we're in editable mode.
|
// As a special-case, interpret `dagster` as `./dagster` if we're in editable mode.
|
||||||
if editable && requirement.version_or_url.is_none() {
|
if editable && requirement.version_or_url.is_none() {
|
||||||
Ok(Self::Unnamed(UnnamedRequirement::parse(
|
Ok(Self::Unnamed(UnnamedRequirement::parse_with_hint(
|
||||||
input,
|
input,
|
||||||
&working_dir,
|
&working_dir,
|
||||||
&mut TracingReporter,
|
&mut TracingReporter,
|
||||||
|
hint,
|
||||||
)?))
|
)?))
|
||||||
} else {
|
} else {
|
||||||
Ok(Self::Named(requirement))
|
Ok(Self::Named(requirement))
|
||||||
|
|
@ -149,10 +161,11 @@ impl RequirementsTxtRequirement {
|
||||||
Err(err) => match err.message {
|
Err(err) => match err.message {
|
||||||
Pep508ErrorSource::UnsupportedRequirement(_) => {
|
Pep508ErrorSource::UnsupportedRequirement(_) => {
|
||||||
// If that fails, attempt to parse as a direct URL requirement.
|
// If that fails, attempt to parse as a direct URL requirement.
|
||||||
Ok(Self::Unnamed(UnnamedRequirement::parse(
|
Ok(Self::Unnamed(UnnamedRequirement::parse_with_hint(
|
||||||
input,
|
input,
|
||||||
&working_dir,
|
&working_dir,
|
||||||
&mut TracingReporter,
|
&mut TracingReporter,
|
||||||
|
hint,
|
||||||
)?))
|
)?))
|
||||||
}
|
}
|
||||||
_ => Err(err),
|
_ => Err(err),
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,67 @@
|
||||||
source: crates/uv-requirements-txt/src/lib.rs
|
source: crates/uv-requirements-txt/src/lib.rs
|
||||||
expression: actual
|
expression: actual
|
||||||
---
|
---
|
||||||
RequirementsTxtFileError {
|
RequirementsTxt {
|
||||||
file: "<REQUIREMENTS_DIR>/semicolon.txt",
|
requirements: [],
|
||||||
error: Pep508 {
|
constraints: [],
|
||||||
source: Pep508Error {
|
editables: [
|
||||||
message: UrlError(
|
RequirementEntry {
|
||||||
MissingExtensionPath(
|
requirement: Unnamed(
|
||||||
"./editable;python_version >= \"3.9\" and os_name == \"posix\"",
|
UnnamedRequirement {
|
||||||
Dist,
|
url: VerbatimParsedUrl {
|
||||||
),
|
parsed_url: Directory(
|
||||||
|
ParsedDirectoryUrl {
|
||||||
|
url: DisplaySafeUrl {
|
||||||
|
scheme: "file",
|
||||||
|
cannot_be_a_base: false,
|
||||||
|
username: "",
|
||||||
|
password: None,
|
||||||
|
host: None,
|
||||||
|
port: None,
|
||||||
|
path: "<REQUIREMENTS_DIR>/editable;python_version%20%3E=%20%223.9%22%20and%20os_name%20==%20%22posix%22",
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
},
|
||||||
|
install_path: "<REQUIREMENTS_DIR>/editable;python_version >= \"3.9\" and os_name == \"posix\"",
|
||||||
|
editable: Some(
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
virtual: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
verbatim: VerbatimUrl {
|
||||||
|
url: DisplaySafeUrl {
|
||||||
|
scheme: "file",
|
||||||
|
cannot_be_a_base: false,
|
||||||
|
username: "",
|
||||||
|
password: None,
|
||||||
|
host: None,
|
||||||
|
port: None,
|
||||||
|
path: "<REQUIREMENTS_DIR>/editable;python_version%20%3E=%20%223.9%22%20and%20os_name%20==%20%22posix%22",
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
},
|
||||||
|
given: Some(
|
||||||
|
"./editable;python_version >= \"3.9\" and os_name == \"posix\"",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extras: [],
|
||||||
|
marker: true,
|
||||||
|
origin: Some(
|
||||||
|
File(
|
||||||
|
"<REQUIREMENTS_DIR>/semicolon.txt",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
start: 0,
|
hashes: [],
|
||||||
len: 57,
|
|
||||||
input: "./editable;python_version >= \"3.9\" and os_name == \"posix\"",
|
|
||||||
},
|
},
|
||||||
start: 50,
|
],
|
||||||
end: 107,
|
index_url: None,
|
||||||
},
|
extra_index_urls: [],
|
||||||
|
find_links: [],
|
||||||
|
no_index: false,
|
||||||
|
no_binary: None,
|
||||||
|
only_binary: None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue