diff --git a/crates/uv/src/commands/tool/install.rs b/crates/uv/src/commands/tool/install.rs index 0f4b8c02409a..ace541728629 100644 --- a/crates/uv/src/commands/tool/install.rs +++ b/crates/uv/src/commands/tool/install.rs @@ -117,7 +117,7 @@ pub(crate) async fn install( .unwrap() } // Ex) `ruff@0.6.0` - Target::Version(name, ref version) => { + Target::Version(name, ref version) | Target::FromVersion(_, name, ref version) => { if editable { bail!("`--editable` is only supported for local packages"); } @@ -136,7 +136,7 @@ pub(crate) async fn install( } } // Ex) `ruff@latest` - Target::Latest(name) => { + Target::Latest(name) | Target::FromLatest(_, name) => { if editable { bail!("`--editable` is only supported for local packages"); } @@ -153,7 +153,7 @@ pub(crate) async fn install( } } // Ex) `ruff>=0.6.0` - Target::UserDefined(package, from) => { + Target::From(package, from) => { // Parse the positional name. If the user provided more than a package name, it's an error // (e.g., `uv install foo==1.0 --from foo`). let Ok(package) = PackageName::from_str(package) else { diff --git a/crates/uv/src/commands/tool/mod.rs b/crates/uv/src/commands/tool/mod.rs index b01a4c65ea00..96d65088cca7 100644 --- a/crates/uv/src/commands/tool/mod.rs +++ b/crates/uv/src/commands/tool/mod.rs @@ -22,15 +22,49 @@ pub(crate) enum Target<'a> { Version(&'a str, Version), /// e.g., `ruff@latest` Latest(&'a str), - /// e.g., `--from ruff==0.6.0` - UserDefined(&'a str, &'a str), + /// e.g., `ruff --from ruff>=0.6.0` + From(&'a str, &'a str), + /// e.g., `ruff --from ruff@0.6.0` + FromVersion(&'a str, &'a str, Version), + /// e.g., `ruff --from ruff@latest` + FromLatest(&'a str, &'a str), } impl<'a> Target<'a> { /// Parse a target into a command name and a requirement. pub(crate) fn parse(target: &'a str, from: Option<&'a str>) -> Self { if let Some(from) = from { - return Self::UserDefined(target, from); + // e.g. `--from ruff`, no special handling + let Some((name, version)) = from.split_once('@') else { + return Self::From(target, from); + }; + + // e.g. `--from ruff@`, warn and treat the whole thing as the command + if version.is_empty() { + debug!("Ignoring empty version request in `--from`"); + return Self::From(target, from); + } + + // e.g., ignore `git+https://github.com/astral-sh/ruff.git@main` + if PackageName::from_str(name).is_err() { + debug!("Ignoring non-package name `{name}` in `--from`"); + return Self::From(target, from); + } + + match version { + // e.g., `ruff@latest` + "latest" => return Self::FromLatest(target, from), + // e.g., `ruff@0.6.0` + version => { + if let Ok(version) = Version::from_str(version) { + return Self::FromVersion(target, from, version); + } + } + }; + + // e.g. `--from ruff@invalid`, warn and treat the whole thing as the command + debug!("Ignoring invalid version request `{version}` in `--from`"); + return Self::From(target, from); } // e.g. `ruff`, no special handling @@ -72,7 +106,9 @@ impl<'a> Target<'a> { Self::Unspecified(name) => name, Self::Version(name, _) => name, Self::Latest(name) => name, - Self::UserDefined(name, _) => name, + Self::FromVersion(name, _, _) => name, + Self::FromLatest(name, _) => name, + Self::From(name, _) => name, } } diff --git a/crates/uv/src/commands/tool/run.rs b/crates/uv/src/commands/tool/run.rs index 6a7f27522181..4500f04123d7 100644 --- a/crates/uv/src/commands/tool/run.rs +++ b/crates/uv/src/commands/tool/run.rs @@ -352,7 +352,7 @@ async fn get_or_create_environment( origin: None, }, // Ex) `ruff@0.6.0` - Target::Version(name, version) => Requirement { + Target::Version(name, version) | Target::FromVersion(_, name, version) => Requirement { name: PackageName::from_str(name)?, extras: vec![], marker: MarkerTree::default(), @@ -365,7 +365,7 @@ async fn get_or_create_environment( origin: None, }, // Ex) `ruff@latest` - Target::Latest(name) => Requirement { + Target::Latest(name) | Target::FromLatest(_, name) => Requirement { name: PackageName::from_str(name)?, extras: vec![], marker: MarkerTree::default(), @@ -376,7 +376,7 @@ async fn get_or_create_environment( origin: None, }, // Ex) `ruff>=0.6.0` - Target::UserDefined(_, from) => resolve_names( + Target::From(_, from) => resolve_names( vec![RequirementsSpecification::parse_package(from)?], &interpreter, settings,