Add command-separation for Python discovery display (#4916)

## Summary

Closes https://github.com/astral-sh/uv/issues/4915.

```
error: No interpreter found for Python 3.12.4 in virtual environments, managed installations, or system path
```
This commit is contained in:
Charlie Marsh 2024-07-09 01:51:30 -04:00 committed by GitHub
parent f862457f05
commit 72982c1bff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1490,45 +1490,55 @@ impl fmt::Display for PythonSource {
} }
} }
impl fmt::Display for PythonPreference { impl PythonPreference {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { /// Return the sources that are considered when searching for a Python interpreter.
let s = match self { fn sources(self) -> &'static [&'static str] {
Self::OnlyManaged => "managed installations", match self {
Self::OnlyManaged => &["managed installations"],
Self::Managed | Self::Installed | Self::System => { Self::Managed | Self::Installed | Self::System => {
if cfg!(windows) { if cfg!(windows) {
"managed installations, system path, or `py` launcher" &["managed installations", "system path", "`py` launcher"]
} else { } else {
"managed installations or system path" &["managed installations", "system path"]
} }
} }
Self::OnlySystem => { Self::OnlySystem => {
if cfg!(windows) { if cfg!(windows) {
"system path or `py` launcher" &["system path", "`py` launcher"]
} else { } else {
"system path" &["system path"]
} }
} }
}; }
f.write_str(s) }
}
impl fmt::Display for PythonPreference {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", conjunction(self.sources()))
} }
} }
impl fmt::Display for PythonNotFound { impl fmt::Display for PythonNotFound {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let sources = match self.environment_preference { let sources = match self.environment_preference {
EnvironmentPreference::Any => { EnvironmentPreference::Any => conjunction(
format!("virtual environments or {}", self.python_preference) &["virtual environments"]
} .into_iter()
.chain(self.python_preference.sources().iter().copied())
.collect::<Vec<_>>(),
),
EnvironmentPreference::ExplicitSystem => { EnvironmentPreference::ExplicitSystem => {
if self.request.is_explicit_system() { if self.request.is_explicit_system() {
"virtual or system environment".to_string() conjunction(&["virtual", "system environment"])
} else { } else {
"virtual environment".to_string() conjunction(&["virtual environment"])
} }
} }
EnvironmentPreference::OnlySystem => self.python_preference.to_string(), EnvironmentPreference::OnlySystem => conjunction(self.python_preference.sources()),
EnvironmentPreference::OnlyVirtual => "virtual environments".to_string(), EnvironmentPreference::OnlyVirtual => conjunction(&["virtual environments"]),
}; };
match self.request { match self.request {
PythonRequest::Any => { PythonRequest::Any => {
write!(f, "No interpreter found in {sources}") write!(f, "No interpreter found in {sources}")
@ -1540,6 +1550,22 @@ impl fmt::Display for PythonNotFound {
} }
} }
/// Join a series of items with `or` separators, making use of commas when necessary.
fn conjunction(items: &[&str]) -> String {
match items.len() {
1 => items[0].to_string(),
2 => format!("{} or {}", items[0], items[1]),
_ => {
let last = items.last().unwrap();
format!(
"{}, or {}",
items.iter().take(items.len() - 1).join(", "),
last
)
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{path::PathBuf, str::FromStr}; use std::{path::PathBuf, str::FromStr};