diff --git a/crates/uv-tool/src/tool.rs b/crates/uv-tool/src/tool.rs index 1b12910d0..4cbe4cb9c 100644 --- a/crates/uv-tool/src/tool.rs +++ b/crates/uv-tool/src/tool.rs @@ -1,3 +1,4 @@ +use std::fmt::{self, Display, Formatter}; use std::path::PathBuf; use serde::Deserialize; @@ -6,7 +7,7 @@ use toml_edit::Table; use toml_edit::Value; use toml_edit::{Array, Item}; -use uv_fs::PortablePath; +use uv_fs::{PortablePath, Simplified}; use uv_pypi_types::{Requirement, VerbatimParsedUrl}; use uv_settings::ToolOptions; @@ -98,6 +99,32 @@ pub struct ToolEntrypoint { pub install_path: PathBuf, } +impl Display for ToolEntrypoint { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + #[cfg(windows)] + { + write!( + f, + "{} ({})", + self.name, + self.install_path + .simplified_display() + .to_string() + .replace('/', "\\") + ) + } + #[cfg(unix)] + { + write!( + f, + "{} ({})", + self.name, + self.install_path.simplified_display() + ) + } + } +} + /// Format an array so that each element is on its own line and has a trailing comma. /// /// Example: diff --git a/crates/uv/src/commands/tool/list.rs b/crates/uv/src/commands/tool/list.rs index 721ed9de2..3af75ae5c 100644 --- a/crates/uv/src/commands/tool/list.rs +++ b/crates/uv/src/commands/tool/list.rs @@ -98,12 +98,7 @@ pub(crate) async fn list( // Output tool entrypoints for entrypoint in tool.entrypoints() { if show_paths { - writeln!( - printer.stdout(), - "- {} ({})", - entrypoint.name, - entrypoint.install_path.simplified_display().cyan() - )?; + writeln!(printer.stdout(), "- {}", entrypoint.to_string().cyan())?; } else { writeln!(printer.stdout(), "- {}", entrypoint.name)?; } diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index f55a6e963..fb432c27c 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -250,6 +250,19 @@ impl TestContext { self } + /// Add a filter that ignores temporary directory in path. + pub fn with_filtered_windows_temp_dir(mut self) -> Self { + let pattern = regex::escape( + &self + .temp_dir + .simplified_display() + .to_string() + .replace('/', "\\"), + ); + self.filters.push((pattern, "[TEMP_DIR]".to_string())); + self + } + /// Add extra directories and configuration for managed Python installations. #[must_use] pub fn with_managed_python_dirs(mut self) -> Self { @@ -267,6 +280,12 @@ impl TestContext { self } + /// Clear filters on `TestContext`. + pub fn clear_filters(mut self) -> Self { + self.filters.clear(); + self + } + /// Discover the path to the XDG state directory. We use this, rather than the OS-specific /// temporary directory, because on macOS (and Windows on GitHub Actions), they involve /// symlinks. (On macOS, the temporary directory is, like `/var/...`, which resolves to @@ -988,6 +1007,14 @@ impl TestContext { .collect() } + /// Only the filters added to this test context. + pub fn filters_without_standard_filters(&self) -> Vec<(&str, &str)> { + self.filters + .iter() + .map(|(p, r)| (p.as_str(), r.as_str())) + .collect() + } + /// For when we add pypy to the test suite. #[allow(clippy::unused_self)] pub fn python_kind(&self) -> &'static str { diff --git a/crates/uv/tests/it/tool_list.rs b/crates/uv/tests/it/tool_list.rs index d0b0d6b93..3326bb7be 100644 --- a/crates/uv/tests/it/tool_list.rs +++ b/crates/uv/tests/it/tool_list.rs @@ -64,6 +64,38 @@ fn tool_list_paths() { "###); } +#[cfg(windows)] +#[test] +fn tool_list_paths_windows() { + let context = TestContext::new("3.12") + .clear_filters() + .with_filtered_windows_temp_dir(); + let tool_dir = context.temp_dir.child("tools"); + let bin_dir = context.temp_dir.child("bin"); + + // Install `black` + context + .tool_install() + .arg("black==24.2.0") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) + .assert() + .success(); + + uv_snapshot!(context.filters_without_standard_filters(), context.tool_list().arg("--show-paths") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + black v24.2.0 ([TEMP_DIR]\tools\black) + - black.exe ([TEMP_DIR]\bin\black.exe) + - blackd.exe ([TEMP_DIR]\bin\blackd.exe) + + ----- stderr ----- + "###); +} + #[test] fn tool_list_empty() { let context = TestContext::new("3.12").with_filtered_exe_suffix();