mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-02 15:01:16 +00:00
Show appropriate activation command based on shell detection (#2221)
## Summary Closes https://github.com/astral-sh/uv/issues/2174. ## Test Plan On Nushell: ``` (uv) ~/workspace/uv> cargo run venv Using Python 3.12.0 interpreter at: /Users/crmarsh/workspace/uv/.venv/bin/python3 Creating virtualenv at: .venv Activate with: overlay use .venv/bin/activate.nu ``` On Bash: ``` ❯ cargo run venv "foo bar" Using Python 3.12.0 interpreter at: /Users/crmarsh/.local/share/rtx/installs/python/3.12.0/bin/python3 Creating virtualenv at: foo bar Activate with: source 'foo bar/bin/activate' ```
This commit is contained in:
parent
9e41f73e41
commit
9f1bb4dee2
3 changed files with 116 additions and 21 deletions
|
@ -25,6 +25,7 @@ use uv_traits::{BuildContext, ConfigSettings, InFlight, NoBuild, SetupPyStrategy
|
|||
|
||||
use crate::commands::ExitStatus;
|
||||
use crate::printer::Printer;
|
||||
use crate::shell::Shell;
|
||||
|
||||
/// Create a virtual environment.
|
||||
#[allow(clippy::unnecessary_wraps, clippy::too_many_arguments)]
|
||||
|
@ -210,29 +211,53 @@ async fn venv_impl(
|
|||
}
|
||||
}
|
||||
|
||||
if cfg!(windows) {
|
||||
writeln!(
|
||||
printer,
|
||||
// This should work whether the user is on CMD or PowerShell:
|
||||
"Activate with: {}",
|
||||
path.join("Scripts")
|
||||
.join("activate")
|
||||
.simplified_display()
|
||||
.green()
|
||||
)
|
||||
.into_diagnostic()?;
|
||||
} else {
|
||||
writeln!(
|
||||
printer,
|
||||
"Activate with: {}",
|
||||
format!(
|
||||
"source {}",
|
||||
path.join("bin").join("activate").simplified_display()
|
||||
// Determine the appropriate activation command.
|
||||
let activation = match Shell::from_env() {
|
||||
None => None,
|
||||
Some(Shell::Bash | Shell::Zsh) => Some(format!(
|
||||
"source {}",
|
||||
shlex(path.join("bin").join("activate"))
|
||||
)),
|
||||
Some(Shell::Fish) => Some(format!(
|
||||
"source {}",
|
||||
shlex(path.join("bin").join("activate.fish"))
|
||||
)),
|
||||
Some(Shell::Nushell) => Some(format!(
|
||||
"overlay use {}",
|
||||
shlex(path.join("bin").join("activate.nu"))
|
||||
)),
|
||||
Some(Shell::Csh) => Some(format!(
|
||||
"source {}",
|
||||
shlex(path.join("bin").join("activate.csh"))
|
||||
)),
|
||||
Some(Shell::Powershell) => {
|
||||
// No need to quote the path for PowerShell.
|
||||
Some(
|
||||
path.join("Scripts")
|
||||
.join("activate")
|
||||
.simplified_display()
|
||||
.to_string(),
|
||||
)
|
||||
.green()
|
||||
)
|
||||
.into_diagnostic()?;
|
||||
}
|
||||
};
|
||||
if let Some(act) = activation {
|
||||
writeln!(printer, "Activate with: {}", act.green()).into_diagnostic()?;
|
||||
}
|
||||
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
||||
/// Quote a path, if necessary, for safe use in a shell command.
|
||||
fn shlex(executable: impl AsRef<Path>) -> String {
|
||||
// Convert to a display path.
|
||||
let executable = executable.as_ref().simplified_display().to_string();
|
||||
|
||||
// Like Python's `shlex.quote`:
|
||||
// > Use single quotes, and put single quotes into double quotes
|
||||
// > The string $'b is then quoted as '$'"'"'b'
|
||||
if executable.contains(' ') {
|
||||
format!("'{}'", executable.replace('\'', r#"'"'"'"#))
|
||||
} else {
|
||||
executable
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ mod confirm;
|
|||
mod logging;
|
||||
mod printer;
|
||||
mod requirements;
|
||||
mod shell;
|
||||
mod version;
|
||||
|
||||
const DEFAULT_VENV_NAME: &str = ".venv";
|
||||
|
|
69
crates/uv/src/shell.rs
Normal file
69
crates/uv/src/shell.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use std::path::Path;
|
||||
|
||||
/// Shells for which virtualenv activation scripts are available.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub(crate) enum Shell {
|
||||
/// Bourne Again SHell (bash)
|
||||
Bash,
|
||||
/// Friendly Interactive SHell (fish)
|
||||
Fish,
|
||||
/// PowerShell
|
||||
Powershell,
|
||||
/// Z SHell (zsh)
|
||||
Zsh,
|
||||
/// Nushell
|
||||
Nushell,
|
||||
/// C SHell (csh)
|
||||
Csh,
|
||||
}
|
||||
|
||||
impl Shell {
|
||||
/// Determine the user's current shell from the environment.
|
||||
///
|
||||
/// This will read the `SHELL` environment variable and try to determine which shell is in use
|
||||
/// from that.
|
||||
///
|
||||
/// If `SHELL` is not set, then on windows, it will default to powershell, and on
|
||||
/// other `OSes` it will return `None`.
|
||||
///
|
||||
/// If `SHELL` is set, but contains a value that doesn't correspond to one of the supported
|
||||
/// shell types, then return `None`.
|
||||
pub(crate) fn from_env() -> Option<Shell> {
|
||||
if std::env::var_os("NU_VERSION").is_some() {
|
||||
Some(Shell::Nushell)
|
||||
} else if let Some(env_shell) = std::env::var_os("SHELL") {
|
||||
Shell::from_shell_path(env_shell)
|
||||
} else if cfg!(windows) {
|
||||
Some(Shell::Powershell)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a shell from a path to the executable for the shell.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use crate::shells::Shell;
|
||||
///
|
||||
/// assert_eq!(Shell::from_shell_path("/bin/bash"), Some(Shell::Bash));
|
||||
/// assert_eq!(Shell::from_shell_path("/usr/bin/zsh"), Some(Shell::Zsh));
|
||||
/// assert_eq!(Shell::from_shell_path("/opt/my_custom_shell"), None);
|
||||
/// ```
|
||||
pub(crate) fn from_shell_path<P: AsRef<Path>>(path: P) -> Option<Shell> {
|
||||
parse_shell_from_path(path.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_shell_from_path(path: &Path) -> Option<Shell> {
|
||||
let name = path.file_stem()?.to_str()?;
|
||||
match name {
|
||||
"bash" => Some(Shell::Bash),
|
||||
"zsh" => Some(Shell::Zsh),
|
||||
"fish" => Some(Shell::Fish),
|
||||
"csh" => Some(Shell::Csh),
|
||||
"powershell" | "powershell_ise" => Some(Shell::Powershell),
|
||||
_ => None,
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue