use anyhow::{bail, Result};
use colored::Colorize;
use crate::{config::Config, shell, toolchain, user_version::VersionSpec};
pub fn run(
config: &Config,
version_str: Option<&str>,
unset: bool,
shell_str: Option<&str>,
) -> Result<()> {
if unset && version_str.is_some() {
bail!("Cannot specify a version together with --unset.");
}
if !unset && version_str.is_none() {
bail!(
"Specify a version to activate (e.g. `gvsn shell 1.23`) \
or use --unset to revert."
);
}
let sh = match shell_str {
Some(s) => shell::from_str(s)?,
None => shell::detect()
.ok_or_else(|| anyhow::anyhow!("Could not detect shell. Use --shell <name>."))?,
};
if unset {
eprintln!("{} Reverted to file-based Go version.", "→".cyan());
print!("{}", sh.shell_unset_script());
return Ok(());
}
let spec = VersionSpec::parse(version_str.unwrap())?;
let version = toolchain::resolve_installed(config, &spec)?;
let bin = toolchain::version_bin_path(config, &version)?;
let root = config.version_dir(&version.tag());
eprintln!(
"{} Go {} activated for this session ({}). \
Run {} to revert.",
"→".cyan(),
version.tag().bold(),
"GVSN_SHELL_VERSION".dimmed(),
"gvsn shell --unset".cyan()
);
print!("{}", sh.shell_version_script(&version.tag(), &bin, &root));
Ok(())
}
#[cfg(test)]
mod tests {
use crate::shell::{Bash, PowerShell, ShellConfig};
use std::path::Path;
#[test]
fn bash_shell_version_script_sets_gvsn_shell_version() {
let script = Bash.shell_version_script(
"go1.23.4",
Path::new("/home/user/.gvsn/versions/go1.23.4/bin"),
Path::new("/home/user/.gvsn/versions/go1.23.4"),
);
assert!(
script.contains("GVSN_SHELL_VERSION"),
"Must set GVSN_SHELL_VERSION"
);
assert!(script.contains("go1.23.4"), "Must include the version tag");
assert!(script.contains("GOROOT"), "Must set GOROOT");
assert!(script.contains("PATH"), "Must update PATH");
}
#[test]
fn bash_shell_version_script_includes_bin_path() {
let bin = Path::new("/home/user/.gvsn/versions/go1.23.4/bin");
let script = Bash.shell_version_script(
"go1.23.4",
bin,
Path::new("/home/user/.gvsn/versions/go1.23.4"),
);
assert!(
script.contains(bin.to_str().unwrap()),
"Bin path must appear in the script"
);
}
#[test]
fn bash_shell_unset_script_clears_gvsn_shell_version() {
let script = Bash.shell_unset_script();
assert!(
script.contains("GVSN_SHELL_VERSION"),
"Must reference GVSN_SHELL_VERSION for clearing"
);
}
#[test]
fn bash_shell_unset_script_triggers_hook() {
let script = Bash.shell_unset_script();
assert!(
script.contains("_gvsn_hook"),
"Must call _gvsn_hook to restore env"
);
}
#[test]
fn powershell_shell_version_script_sets_env_var() {
let script = PowerShell.shell_version_script(
"go1.23.4",
Path::new(r"C:\Users\user\.gvsn\versions\go1.23.4\bin"),
Path::new(r"C:\Users\user\.gvsn\versions\go1.23.4"),
);
assert!(script.contains("GVSN_SHELL_VERSION"));
assert!(script.contains("go1.23.4"));
assert!(script.contains("GOROOT"));
}
#[test]
fn powershell_shell_unset_script_clears_env_var() {
let script = PowerShell.shell_unset_script();
assert!(script.contains("GVSN_SHELL_VERSION"));
assert!(script.contains("_gvsn_hook"));
}
#[test]
fn shell_version_script_not_empty_for_all_shells() {
use crate::shell::{Fish, Zsh};
let bin = Path::new("/home/user/.gvsn/versions/go1.23.4/bin");
let root = Path::new("/home/user/.gvsn/versions/go1.23.4");
assert!(!Bash.shell_version_script("go1.23.4", bin, root).is_empty());
assert!(!Zsh.shell_version_script("go1.23.4", bin, root).is_empty());
assert!(!Fish.shell_version_script("go1.23.4", bin, root).is_empty());
assert!(!PowerShell
.shell_version_script("go1.23.4", bin, root)
.is_empty());
}
#[test]
fn shell_unset_script_not_empty_for_all_shells() {
use crate::shell::{Fish, Zsh};
assert!(!Bash.shell_unset_script().is_empty());
assert!(!Zsh.shell_unset_script().is_empty());
assert!(!Fish.shell_unset_script().is_empty());
assert!(!PowerShell.shell_unset_script().is_empty());
}
use super::run;
use crate::config::Config;
use tempfile::tempdir;
fn make_config() -> (tempfile::TempDir, Config) {
let dir = tempdir().unwrap();
let config = Config {
root: dir.path().to_path_buf(),
};
(dir, config)
}
#[test]
fn run_errors_when_version_and_unset_both_given() {
let (_dir, config) = make_config();
let err = run(&config, Some("1.22.4"), true, Some("bash")).unwrap_err();
assert!(err.to_string().contains("Cannot specify a version"));
}
#[test]
fn run_errors_when_neither_version_nor_unset_given() {
let (_dir, config) = make_config();
let err = run(&config, None, false, Some("bash")).unwrap_err();
assert!(err.to_string().contains("Specify a version"));
}
#[test]
fn run_errors_on_unknown_shell() {
let (_dir, config) = make_config();
assert!(run(&config, None, true, Some("not-a-shell")).is_err());
}
#[test]
fn run_unset_succeeds_with_explicit_shell() {
let (_dir, config) = make_config();
run(&config, None, true, Some("bash")).unwrap();
}
#[test]
fn run_errors_when_requested_version_not_installed() {
let (_dir, config) = make_config();
assert!(run(&config, Some("1.22.4"), false, Some("bash")).is_err());
}
#[test]
fn run_succeeds_for_installed_version() {
let (_dir, config) = make_config();
let tag = "go1.22.4";
std::fs::create_dir_all(config.version_bin_dir(tag)).unwrap();
run(&config, Some("1.22.4"), false, Some("bash")).unwrap();
}
}