feat: add tool version to list command (#4674)

<!--
Thank you for contributing to uv! To help us out with reviewing, please
consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->
Closes #4653

## Summary
Adds the tool version to the list command right beside the tool name

```
$ uv tool list
black v24.2.0
```

Following the proposed format discussed in #4653


## Test Plan
`cargo test tool_list`

<!-- How was it tested? -->

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
Caíque Porfirio 2024-07-03 15:24:37 -03:00 committed by GitHub
parent d24b075b2d
commit c17761904e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 100 additions and 8 deletions

View file

@ -23,6 +23,7 @@ uv-state = { workspace = true }
uv-python = { workspace = true }
uv-virtualenv = { workspace = true }
uv-warnings = { workspace = true }
uv-installer = { workspace = true }
dirs-sys = { workspace = true }
fs-err = { workspace = true }

View file

@ -1,19 +1,25 @@
use core::fmt;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use fs_err as fs;
use pep440_rs::Version;
use pep508_rs::{InvalidNameError, PackageName};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use fs_err::File;
use thiserror::Error;
use tracing::debug;
use install_wheel_rs::read_record_file;
use pep440_rs::Version;
use pep508_rs::PackageName;
pub use receipt::ToolReceipt;
pub use tool::{Tool, ToolEntrypoint};
use uv_cache::Cache;
use uv_fs::{LockedFile, Simplified};
use uv_installer::SitePackages;
use uv_python::{Interpreter, PythonEnvironment};
use uv_state::{StateBucket, StateStore};
use uv_warnings::warn_user_once;
@ -38,9 +44,15 @@ pub enum Error {
#[error("Failed to find a directory for executables")]
NoExecutableDirectory,
#[error(transparent)]
ToolName(#[from] InvalidNameError),
#[error(transparent)]
EnvironmentError(#[from] uv_python::Error),
#[error("Failed to find a receipt for tool `{0}` at {1}")]
MissingToolReceipt(String, PathBuf),
#[error("Failed to read tool environment packages at `{0}`: {1}")]
EnvironmentRead(PathBuf, String),
#[error("Failed find tool package `{0}` at `{1}`")]
MissingToolPackage(PackageName, PathBuf),
}
/// A collection of uv-managed tools installed on the current system.
@ -230,6 +242,19 @@ impl InstalledTools {
))
}
pub fn version(&self, name: &str, cache: &Cache) -> Result<Version, Error> {
let environment_path = self.root.join(name);
let package_name = PackageName::from_str(name)?;
let environment = PythonEnvironment::from_root(&environment_path, cache)?;
let site_packages = SitePackages::from_environment(&environment)
.map_err(|err| Error::EnvironmentRead(environment_path.clone(), err.to_string()))?;
let packages = site_packages.get_packages(&package_name);
let package = packages
.first()
.ok_or_else(|| Error::MissingToolPackage(package_name, environment_path))?;
Ok(package.version().clone())
}
/// Initialize the tools directory.
///
/// Ensures the directory is created.