diff --git a/crates/uv/src/commands/tool/list.rs b/crates/uv/src/commands/tool/list.rs index 793beba59a5e..8e5e0192836d 100644 --- a/crates/uv/src/commands/tool/list.rs +++ b/crates/uv/src/commands/tool/list.rs @@ -20,12 +20,10 @@ pub(crate) async fn list( printer: Printer, ) -> Result { let installed_tools = InstalledTools::from_settings()?; - let no_tools_installed_msg = - "No tools installed.\n\nSee `uv tool install --help` for more information."; let _lock = match installed_tools.lock().await { Ok(lock) => lock, Err(uv_tool::Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => { - writeln!(printer.stderr(), "{no_tools_installed_msg}")?; + writeln!(printer.stderr(), "No tools installed")?; return Ok(ExitStatus::Success); } Err(err) => return Err(err.into()), @@ -35,16 +33,10 @@ pub(crate) async fn list( tools.sort_by_key(|(name, _)| name.clone()); if tools.is_empty() { - writeln!(printer.stderr(), "{no_tools_installed_msg}")?; + writeln!(printer.stderr(), "No tools installed")?; return Ok(ExitStatus::Success); } - writeln!( - printer.stdout(), - "Provide a command to invoke with `uvx ` or `uvx --from `.\n\n\ - The following tools are already installed:\n" - )?; - for (name, tool) in tools { // Skip invalid tools let Ok(tool) = tool else { @@ -107,6 +99,5 @@ pub(crate) async fn list( } } - writeln!(printer.stdout(), "\nSee `uvx --help` for more information.")?; Ok(ExitStatus::Success) } diff --git a/crates/uv/src/commands/tool/run.rs b/crates/uv/src/commands/tool/run.rs index 1a120f633abc..5442a5f49aa6 100644 --- a/crates/uv/src/commands/tool/run.rs +++ b/crates/uv/src/commands/tool/run.rs @@ -36,9 +36,7 @@ use crate::commands::pip::operations; use crate::commands::project::{resolve_names, EnvironmentSpecification, ProjectError}; use crate::commands::reporters::PythonDownloadReporter; use crate::commands::tool::Target; -use crate::commands::{ - project::environment::CachedEnvironment, tool::common::matching_packages, tool_list, -}; +use crate::commands::{project::environment::CachedEnvironment, tool::common::matching_packages}; use crate::commands::{ExitStatus, SharedState}; use crate::printer::Printer; use crate::settings::ResolverInstallerSettings; @@ -79,9 +77,10 @@ pub(crate) async fn run( cache: Cache, printer: Printer, ) -> anyhow::Result { - // treat empty command as `uv tool list` + // treat empty command similar to `uv tool list`, list available tools + // but without propagating malformed tool errors. let Some(command) = command else { - return tool_list(false, false, &cache, printer).await; + return list_available_tools(invocation_source, &cache, printer).await; }; let (target, args) = command.split(); @@ -262,6 +261,77 @@ fn get_entrypoints( )?) } +/// Display a list of tools that provide the executable. +/// +/// If there is no package providing the executable, we will display a message to how to install a package. +async fn list_available_tools( + invocation_source: ToolRunCommand, + cache: &Cache, + printer: Printer, +) -> anyhow::Result { + writeln!( + printer.stdout(), + "Provide a command to invoke with `{invocation_source} ` \ + or `{invocation_source} --from `.\n" + )?; + + let installed_tools = InstalledTools::from_settings()?; + let no_tools_installed_msg = + "No tools installed. See `uv tool install --help` for more information."; + let _lock = match installed_tools.lock().await { + Ok(lock) => lock, + Err(uv_tool::Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => { + writeln!(printer.stdout(), "{no_tools_installed_msg}")?; + return Ok(ExitStatus::Success); + } + Err(err) => return Err(err.into()), + }; + + let mut tools = installed_tools.tools()?.into_iter().collect::>(); + tools.sort_by_key(|(name, _)| name.clone()); + + if tools.is_empty() { + writeln!(printer.stdout(), "{no_tools_installed_msg}")?; + return Ok(ExitStatus::Success); + } + + let mut buf = String::new(); + for (name, tool) in tools { + // Skip invalid tools. + let Ok(tool) = tool else { + continue; + }; + + // Output tool name and version. + let Ok(version) = installed_tools.version(&name, cache) else { + continue; + }; + writeln!(buf, "{}", format!("{name} v{version}").bold())?; + + // Output tool entrypoints. + for entrypoint in tool.entrypoints() { + writeln!(buf, "- {}", entrypoint.name)?; + } + } + + // Installed tools were malformed or failed fetching versions. + if buf.is_empty() { + writeln!(printer.stderr(), "{no_tools_installed_msg}")?; + return Ok(ExitStatus::Success); + } + + writeln!( + printer.stdout(), + "The following tools are already installed:\n" + )?; + writeln!(printer.stdout(), "{buf}")?; + writeln!( + printer.stdout(), + "See `{invocation_source} --help` for more information." + )?; + Ok(ExitStatus::Success) +} + /// Display a warning if an executable is not provided by package. /// /// If found in a dependency of the requested package instead of the requested package itself, we will hint to use that instead. diff --git a/crates/uv/tests/tool_list.rs b/crates/uv/tests/tool_list.rs index ac4f427009b0..3f0c26d90e6e 100644 --- a/crates/uv/tests/tool_list.rs +++ b/crates/uv/tests/tool_list.rs @@ -30,16 +30,10 @@ fn tool_list() { success: true exit_code: 0 ----- stdout ----- - Provide a command to invoke with `uvx ` or `uvx --from `. - - The following tools are already installed: - black v24.2.0 - black - blackd - See `uvx --help` for more information. - ----- stderr ----- "###); } @@ -65,16 +59,10 @@ fn tool_list_paths() { success: true exit_code: 0 ----- stdout ----- - Provide a command to invoke with `uvx ` or `uvx --from `. - - The following tools are already installed: - black v24.2.0 ([TEMP_DIR]/tools/black) - black ([TEMP_DIR]/bin/black) - blackd ([TEMP_DIR]/bin/blackd) - See `uvx --help` for more information. - ----- stderr ----- "###); } @@ -93,9 +81,7 @@ fn tool_list_empty() { ----- stdout ----- ----- stderr ----- - No tools installed. - - See `uv tool install --help` for more information. + No tools installed "###); } @@ -122,12 +108,6 @@ fn tool_list_missing_receipt() { success: true exit_code: 0 ----- stdout ----- - Provide a command to invoke with `uvx ` or `uvx --from `. - - The following tools are already installed: - - - See `uvx --help` for more information. ----- stderr ----- warning: Ignoring malformed tool `black` (run `uv tool uninstall black` to remove) @@ -175,15 +155,9 @@ fn tool_list_bad_environment() -> Result<()> { success: true exit_code: 0 ----- stdout ----- - Provide a command to invoke with `uvx ` or `uvx --from `. - - The following tools are already installed: - ruff v0.3.4 - ruff - See `uvx --help` for more information. - ----- stderr ----- Invalid environment at `tools/black`: missing Python executable at `tools/black/[BIN]/python` "### @@ -244,16 +218,10 @@ fn tool_list_deprecated() -> Result<()> { success: true exit_code: 0 ----- stdout ----- - Provide a command to invoke with `uvx ` or `uvx --from `. - - The following tools are already installed: - black v24.2.0 - black - blackd - See `uvx --help` for more information. - ----- stderr ----- "###); @@ -277,12 +245,6 @@ fn tool_list_deprecated() -> Result<()> { success: true exit_code: 0 ----- stdout ----- - Provide a command to invoke with `uvx ` or `uvx --from `. - - The following tools are already installed: - - - See `uvx --help` for more information. ----- stderr ----- warning: Ignoring malformed tool `black` (run `uv tool uninstall black` to remove) @@ -312,16 +274,10 @@ fn tool_list_show_version_specifiers() { success: true exit_code: 0 ----- stdout ----- - Provide a command to invoke with `uvx ` or `uvx --from `. - - The following tools are already installed: - black v24.2.0 [required: <24.3.0] - black - blackd - See `uvx --help` for more information. - ----- stderr ----- "###); @@ -332,16 +288,10 @@ fn tool_list_show_version_specifiers() { success: true exit_code: 0 ----- stdout ----- - Provide a command to invoke with `uvx ` or `uvx --from `. - - The following tools are already installed: - black v24.2.0 [required: <24.3.0] ([TEMP_DIR]/tools/black) - black ([TEMP_DIR]/bin/black) - blackd ([TEMP_DIR]/bin/blackd) - See `uvx --help` for more information. - ----- stderr ----- "###); } diff --git a/crates/uv/tests/tool_run.rs b/crates/uv/tests/tool_run.rs index 07f089859ff2..209d64bcb969 100644 --- a/crates/uv/tests/tool_run.rs +++ b/crates/uv/tests/tool_run.rs @@ -753,11 +753,11 @@ fn tool_run_list_installed() { success: true exit_code: 0 ----- stdout ----- + Provide a command to invoke with `uv tool run ` or `uv tool run --from `. - ----- stderr ----- - No tools installed. + No tools installed. See `uv tool install --help` for more information. - See `uv tool install --help` for more information. + ----- stderr ----- "###); // Install `black`. @@ -776,7 +776,7 @@ fn tool_run_list_installed() { success: true exit_code: 0 ----- stdout ----- - Provide a command to invoke with `uvx ` or `uvx --from `. + Provide a command to invoke with `uv tool run ` or `uv tool run --from `. The following tools are already installed: @@ -784,7 +784,7 @@ fn tool_run_list_installed() { - black - blackd - See `uvx --help` for more information. + See `uv tool run --help` for more information. ----- stderr ----- "###); diff --git a/crates/uv/tests/tool_uninstall.rs b/crates/uv/tests/tool_uninstall.rs index 36013128db5b..9a9a3e7fb31a 100644 --- a/crates/uv/tests/tool_uninstall.rs +++ b/crates/uv/tests/tool_uninstall.rs @@ -42,9 +42,7 @@ fn tool_uninstall() { ----- stdout ----- ----- stderr ----- - No tools installed. - - See `uv tool install --help` for more information. + No tools installed "###); // After uninstalling the tool, we should be able to reinstall it. @@ -113,9 +111,7 @@ fn tool_uninstall_multiple_names() { ----- stdout ----- ----- stderr ----- - No tools installed. - - See `uv tool install --help` for more information. + No tools installed "###); }