Add some text decoration to toolchain CLI (#4882)

## Summary

Attempts to make the CLI output a little more consistent with the `pip`
interface. I opted to make the Python versions, requests, and filenames
blue, and the keys green, but open to opinions on that. (We use blue for
filenames elsewhere.)

Closes #4813.
Closes https://github.com/astral-sh/uv/issues/4814.

![Screenshot 2024-07-07 at 9 18
48 PM](8518b559-196b-4cd0-bc16-8e79e66460bb)
This commit is contained in:
Charlie Marsh 2024-07-08 09:03:38 -05:00 committed by GitHub
parent 57cfe1e229
commit 2d651fe264
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 48 additions and 46 deletions

View file

@ -5,6 +5,7 @@ use anyhow::Result;
use fs_err as fs;
use futures::StreamExt;
use itertools::Itertools;
use owo_colors::OwoColorize;
use uv_cache::Cache;
use uv_client::Connectivity;
use uv_configuration::PreviewMode;
@ -15,7 +16,7 @@ use uv_python::{requests_from_version_file, PythonRequest};
use uv_warnings::warn_user_once;
use crate::commands::reporters::PythonDownloadReporter;
use crate::commands::ExitStatus;
use crate::commands::{elapsed, ExitStatus};
use crate::printer::Printer;
/// Download and install Python versions.
@ -62,7 +63,8 @@ pub(crate) async fn install(
for (request, download_request) in requests.iter().zip(download_requests) {
writeln!(
printer.stderr(),
"Looking for installation {request} ({download_request})"
"Searching for Python versions matching: {}",
request.cyan()
)?;
if let Some(installation) = installed_installations
.iter()
@ -70,14 +72,15 @@ pub(crate) async fn install(
{
writeln!(
printer.stderr(),
"Found existing installation `{}` that satisfies {request}",
installation.key()
"Found existing installation for {}: {}",
request.cyan(),
installation.key().green(),
)?;
if force {
writeln!(
printer.stderr(),
"Removing existing installation `{}`",
installation.key()
"Uninstalling {}",
installation.key().green()
)?;
fs::remove_dir_all(installation.path())?;
unfilled_requests.push(download_request);
@ -94,12 +97,7 @@ pub(crate) async fn install(
"Python is already available. Use `uv python install <request>` to install a specific version.",
)?;
} else if requests.len() > 1 {
writeln!(
printer.stderr(),
"All requested versions already installed."
)?;
} else {
writeln!(printer.stderr(), "Requested versions already installed.")?;
writeln!(printer.stderr(), "All requested versions already installed")?;
}
return Ok(ExitStatus::Success);
}
@ -117,13 +115,6 @@ pub(crate) async fn install(
.unique_by(|download| download.key())
.collect::<Vec<_>>();
let s = if downloads.len() == 1 { "" } else { "s" };
writeln!(
printer.stderr(),
"Found {} version{s} requiring installation",
downloads.len()
)?;
// Construct a client
let client = uv_client::BaseClientBuilder::new()
.connectivity(connectivity)
@ -150,8 +141,9 @@ pub(crate) async fn install(
DownloadResult::Fetched(path) => {
writeln!(
printer.stderr(),
"Installed Python {version} to {}",
path.user_display()
"Installed {} to: {}",
format!("Python {version}").cyan(),
path.user_display().cyan()
)?;
path
}
@ -165,9 +157,13 @@ pub(crate) async fn install(
let s = if downloads.len() == 1 { "" } else { "s" };
writeln!(
printer.stderr(),
"Installed {} version{s} in {}s",
downloads.len(),
start.elapsed().as_secs()
"{}",
format!(
"Installed {} {}",
format!("{} version{s}", downloads.len()).bold(),
format!("in {}", elapsed(start.elapsed())).dimmed()
)
.dimmed()
)?;
Ok(ExitStatus::Success)

View file

@ -79,7 +79,7 @@ pub(crate) async fn list(
.collect::<Result<Vec<Result<PythonInstallation, PythonNotFound>>, DiscoveryError>>()?
.into_iter()
// Drop any "missing" installations
.filter_map(std::result::Result::ok);
.filter_map(Result::ok);
let mut output = BTreeSet::new();
for installation in installed {

View file

@ -4,6 +4,7 @@ use std::fmt::Write;
use anyhow::Result;
use futures::StreamExt;
use itertools::Itertools;
use owo_colors::OwoColorize;
use uv_configuration::PreviewMode;
use uv_python::downloads::{self, PythonDownloadRequest};
@ -11,7 +12,7 @@ use uv_python::managed::ManagedPythonInstallations;
use uv_python::PythonRequest;
use uv_warnings::warn_user_once;
use crate::commands::ExitStatus;
use crate::commands::{elapsed, ExitStatus};
use crate::printer::Printer;
/// Uninstall managed Python versions.
@ -24,6 +25,8 @@ pub(crate) async fn uninstall(
warn_user_once!("`uv python uninstall` is experimental and may change without warning.");
}
let start = std::time::Instant::now();
let installations = ManagedPythonInstallations::from_settings()?.init()?;
let _lock = installations.acquire_lock()?;
@ -43,7 +46,8 @@ pub(crate) async fn uninstall(
for (request, download_request) in requests.iter().zip(download_requests) {
writeln!(
printer.stderr(),
"Looking for Python installations matching {request} ({download_request})"
"Searching for Python versions matching: {}",
request.cyan()
)?;
let mut found = false;
for installation in installed_installations
@ -54,31 +58,28 @@ pub(crate) async fn uninstall(
if matching_installations.insert(installation.clone()) {
writeln!(
printer.stderr(),
"Found installation `{}` that matches {request}",
installation.key()
"Found existing installation for {}: {}",
request.cyan(),
installation.key().green(),
)?;
}
}
if !found {
writeln!(
printer.stderr(),
"No installations found matching {request}"
"No existing installations found for: {}",
request.cyan()
)?;
}
}
if matching_installations.is_empty() {
if matches!(requests.as_slice(), [PythonRequest::Any]) {
writeln!(printer.stderr(), "No installed installations found")?;
writeln!(printer.stderr(), "No Python installations found")?;
} else if requests.len() > 1 {
writeln!(
printer.stderr(),
"No installations found matching the requests"
)?;
} else {
writeln!(
printer.stderr(),
"No installations found matching the request"
"No Python installations found matching the requests"
)?;
}
return Ok(ExitStatus::Failure);
@ -98,18 +99,19 @@ pub(crate) async fn uninstall(
for (key, result) in results.iter().sorted_by_key(|(key, _)| key) {
if let Err(err) = result {
failed = true;
writeln!(printer.stderr(), "Failed to uninstall `{key}`: {err}")?;
writeln!(
printer.stderr(),
"Failed to uninstall {}: {err}",
key.green()
)?;
} else {
writeln!(printer.stderr(), "Uninstalled `{key}`")?;
writeln!(printer.stderr(), "Uninstalled {}", key.green())?;
}
}
if failed {
if matching_installations.len() > 1 {
writeln!(
printer.stderr(),
"Failed to remove some Python installations"
)?;
writeln!(printer.stderr(), "Failed to uninstall some Python versions")?;
}
return Ok(ExitStatus::Failure);
}
@ -119,11 +121,15 @@ pub(crate) async fn uninstall(
} else {
"s"
};
writeln!(
printer.stderr(),
"Removed {} Python installation{s}",
matching_installations.len()
"{}",
format!(
"Uninstalled {} {}",
format!("{} version{s}", matching_installations.len()).bold(),
format!("in {}", elapsed(start.elapsed())).dimmed()
)
.dimmed()
)?;
Ok(ExitStatus::Success)