mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
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.

This commit is contained in:
parent
57cfe1e229
commit
2d651fe264
3 changed files with 48 additions and 46 deletions
|
@ -5,6 +5,7 @@ use anyhow::Result;
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use owo_colors::OwoColorize;
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::Connectivity;
|
use uv_client::Connectivity;
|
||||||
use uv_configuration::PreviewMode;
|
use uv_configuration::PreviewMode;
|
||||||
|
@ -15,7 +16,7 @@ use uv_python::{requests_from_version_file, PythonRequest};
|
||||||
use uv_warnings::warn_user_once;
|
use uv_warnings::warn_user_once;
|
||||||
|
|
||||||
use crate::commands::reporters::PythonDownloadReporter;
|
use crate::commands::reporters::PythonDownloadReporter;
|
||||||
use crate::commands::ExitStatus;
|
use crate::commands::{elapsed, ExitStatus};
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
|
|
||||||
/// Download and install Python versions.
|
/// Download and install Python versions.
|
||||||
|
@ -62,7 +63,8 @@ pub(crate) async fn install(
|
||||||
for (request, download_request) in requests.iter().zip(download_requests) {
|
for (request, download_request) in requests.iter().zip(download_requests) {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"Looking for installation {request} ({download_request})"
|
"Searching for Python versions matching: {}",
|
||||||
|
request.cyan()
|
||||||
)?;
|
)?;
|
||||||
if let Some(installation) = installed_installations
|
if let Some(installation) = installed_installations
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -70,14 +72,15 @@ pub(crate) async fn install(
|
||||||
{
|
{
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"Found existing installation `{}` that satisfies {request}",
|
"Found existing installation for {}: {}",
|
||||||
installation.key()
|
request.cyan(),
|
||||||
|
installation.key().green(),
|
||||||
)?;
|
)?;
|
||||||
if force {
|
if force {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"Removing existing installation `{}`",
|
"Uninstalling {}",
|
||||||
installation.key()
|
installation.key().green()
|
||||||
)?;
|
)?;
|
||||||
fs::remove_dir_all(installation.path())?;
|
fs::remove_dir_all(installation.path())?;
|
||||||
unfilled_requests.push(download_request);
|
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.",
|
"Python is already available. Use `uv python install <request>` to install a specific version.",
|
||||||
)?;
|
)?;
|
||||||
} else if requests.len() > 1 {
|
} else if requests.len() > 1 {
|
||||||
writeln!(
|
writeln!(printer.stderr(), "All requested versions already installed")?;
|
||||||
printer.stderr(),
|
|
||||||
"All requested versions already installed."
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
writeln!(printer.stderr(), "Requested versions already installed.")?;
|
|
||||||
}
|
}
|
||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
|
@ -117,13 +115,6 @@ pub(crate) async fn install(
|
||||||
.unique_by(|download| download.key())
|
.unique_by(|download| download.key())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let s = if downloads.len() == 1 { "" } else { "s" };
|
|
||||||
writeln!(
|
|
||||||
printer.stderr(),
|
|
||||||
"Found {} version{s} requiring installation",
|
|
||||||
downloads.len()
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Construct a client
|
// Construct a client
|
||||||
let client = uv_client::BaseClientBuilder::new()
|
let client = uv_client::BaseClientBuilder::new()
|
||||||
.connectivity(connectivity)
|
.connectivity(connectivity)
|
||||||
|
@ -150,8 +141,9 @@ pub(crate) async fn install(
|
||||||
DownloadResult::Fetched(path) => {
|
DownloadResult::Fetched(path) => {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"Installed Python {version} to {}",
|
"Installed {} to: {}",
|
||||||
path.user_display()
|
format!("Python {version}").cyan(),
|
||||||
|
path.user_display().cyan()
|
||||||
)?;
|
)?;
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
@ -165,9 +157,13 @@ pub(crate) async fn install(
|
||||||
let s = if downloads.len() == 1 { "" } else { "s" };
|
let s = if downloads.len() == 1 { "" } else { "s" };
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"Installed {} version{s} in {}s",
|
"{}",
|
||||||
downloads.len(),
|
format!(
|
||||||
start.elapsed().as_secs()
|
"Installed {} {}",
|
||||||
|
format!("{} version{s}", downloads.len()).bold(),
|
||||||
|
format!("in {}", elapsed(start.elapsed())).dimmed()
|
||||||
|
)
|
||||||
|
.dimmed()
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
|
|
|
@ -79,7 +79,7 @@ pub(crate) async fn list(
|
||||||
.collect::<Result<Vec<Result<PythonInstallation, PythonNotFound>>, DiscoveryError>>()?
|
.collect::<Result<Vec<Result<PythonInstallation, PythonNotFound>>, DiscoveryError>>()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
// Drop any "missing" installations
|
// Drop any "missing" installations
|
||||||
.filter_map(std::result::Result::ok);
|
.filter_map(Result::ok);
|
||||||
|
|
||||||
let mut output = BTreeSet::new();
|
let mut output = BTreeSet::new();
|
||||||
for installation in installed {
|
for installation in installed {
|
||||||
|
|
|
@ -4,6 +4,7 @@ use std::fmt::Write;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use owo_colors::OwoColorize;
|
||||||
|
|
||||||
use uv_configuration::PreviewMode;
|
use uv_configuration::PreviewMode;
|
||||||
use uv_python::downloads::{self, PythonDownloadRequest};
|
use uv_python::downloads::{self, PythonDownloadRequest};
|
||||||
|
@ -11,7 +12,7 @@ use uv_python::managed::ManagedPythonInstallations;
|
||||||
use uv_python::PythonRequest;
|
use uv_python::PythonRequest;
|
||||||
use uv_warnings::warn_user_once;
|
use uv_warnings::warn_user_once;
|
||||||
|
|
||||||
use crate::commands::ExitStatus;
|
use crate::commands::{elapsed, ExitStatus};
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
|
|
||||||
/// Uninstall managed Python versions.
|
/// 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.");
|
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 installations = ManagedPythonInstallations::from_settings()?.init()?;
|
||||||
let _lock = installations.acquire_lock()?;
|
let _lock = installations.acquire_lock()?;
|
||||||
|
|
||||||
|
@ -43,7 +46,8 @@ pub(crate) async fn uninstall(
|
||||||
for (request, download_request) in requests.iter().zip(download_requests) {
|
for (request, download_request) in requests.iter().zip(download_requests) {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"Looking for Python installations matching {request} ({download_request})"
|
"Searching for Python versions matching: {}",
|
||||||
|
request.cyan()
|
||||||
)?;
|
)?;
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
for installation in installed_installations
|
for installation in installed_installations
|
||||||
|
@ -54,31 +58,28 @@ pub(crate) async fn uninstall(
|
||||||
if matching_installations.insert(installation.clone()) {
|
if matching_installations.insert(installation.clone()) {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"Found installation `{}` that matches {request}",
|
"Found existing installation for {}: {}",
|
||||||
installation.key()
|
request.cyan(),
|
||||||
|
installation.key().green(),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"No installations found matching {request}"
|
"No existing installations found for: {}",
|
||||||
|
request.cyan()
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if matching_installations.is_empty() {
|
if matching_installations.is_empty() {
|
||||||
if matches!(requests.as_slice(), [PythonRequest::Any]) {
|
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 {
|
} else if requests.len() > 1 {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"No installations found matching the requests"
|
"No Python installations found matching the requests"
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
writeln!(
|
|
||||||
printer.stderr(),
|
|
||||||
"No installations found matching the request"
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
return Ok(ExitStatus::Failure);
|
return Ok(ExitStatus::Failure);
|
||||||
|
@ -98,18 +99,19 @@ pub(crate) async fn uninstall(
|
||||||
for (key, result) in results.iter().sorted_by_key(|(key, _)| key) {
|
for (key, result) in results.iter().sorted_by_key(|(key, _)| key) {
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
failed = true;
|
failed = true;
|
||||||
writeln!(printer.stderr(), "Failed to uninstall `{key}`: {err}")?;
|
writeln!(
|
||||||
|
printer.stderr(),
|
||||||
|
"Failed to uninstall {}: {err}",
|
||||||
|
key.green()
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
writeln!(printer.stderr(), "Uninstalled `{key}`")?;
|
writeln!(printer.stderr(), "Uninstalled {}", key.green())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if failed {
|
if failed {
|
||||||
if matching_installations.len() > 1 {
|
if matching_installations.len() > 1 {
|
||||||
writeln!(
|
writeln!(printer.stderr(), "Failed to uninstall some Python versions")?;
|
||||||
printer.stderr(),
|
|
||||||
"Failed to remove some Python installations"
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
return Ok(ExitStatus::Failure);
|
return Ok(ExitStatus::Failure);
|
||||||
}
|
}
|
||||||
|
@ -119,11 +121,15 @@ pub(crate) async fn uninstall(
|
||||||
} else {
|
} else {
|
||||||
"s"
|
"s"
|
||||||
};
|
};
|
||||||
|
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stderr(),
|
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)
|
Ok(ExitStatus::Success)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue