pub mod errors;
use self::errors::Error;
use crate::env;
use std::{
path::{Path, PathBuf},
sync::OnceLock,
};
use whattheshell::Shell;
static SHELL: OnceLock<Shell> = OnceLock::new();
pub fn get_shell() -> Shell {
SHELL
.get_or_init(|| Shell::infer().expect("failed inferring current shell"))
.clone()
}
pub trait ShellEnv {
fn get_setenv_command(&self, key: &str, val: &str) -> Result<String, Error>;
fn append_to_path(&self, curr: &str, new: &str) -> Result<String, Error>;
fn get_profile_dir(&self) -> Result<PathBuf, Error>;
fn path_to_string<T: AsRef<Path>>(&self, path: T) -> Result<String, Error>;
fn get_apply_env_command(&self) -> Result<&'static str, Error>;
fn is_env_applied(&self) -> Result<bool, Error>;
}
impl ShellEnv for Shell {
fn get_setenv_command(&self, key: &str, val: &str) -> Result<String, Error> {
match self {
Self::Bash | Self::Sh | Self::Zsh => Ok(format!("export {key}=\"{val}\"")),
Self::Cmd | Self::PowerShell => Ok(format!("$env:{key} = \"{val}\"")),
Self::Nushell => Ok(format!("{key} = '''{val}'''")),
Self::Fish => Ok(format!("set -gx {key} {val}")),
}
}
fn append_to_path(&self, curr: &str, new: &str) -> Result<String, Error> {
match self {
Self::Bash | Self::Sh | Self::Zsh => {
#[cfg(not(windows))]
return Ok(format!("{new}:{curr}"));
#[cfg(windows)]
return Ok(format!("{}:{}", env::to_gitbash_path_var(curr), new));
}
Self::Nushell => {
#[cfg(not(windows))]
return Ok(format!("{new}:{curr}"));
#[cfg(windows)]
return Ok(format!("{new};{curr}"));
}
Self::Cmd | Self::PowerShell => Ok(format!("{new};{curr}")),
Self::Fish => Ok(format!("{new} $PATH")),
}
}
fn get_profile_dir(&self) -> Result<PathBuf, Error> {
let home = env::get_home_dir()?;
match self {
Self::Bash | Self::Sh => Ok(home.join(".profile")),
Self::Zsh => Ok(home.join(".zshenv")),
Self::PowerShell | Self::Cmd => Ok(home
.join("Documents")
.join("WindowsPowerShell")
.join("Microsoft.PowerShell_profile.ps1")),
#[cfg(not(windows))]
Self::Nushell => Ok(home.join(".config").join("nushell").join("config.nu")),
#[cfg(windows)]
Self::Nushell => Ok(home
.join("AppData")
.join("Roaming")
.join("nushell")
.join("config.nu")),
Self::Fish => Ok(home
.join(".config")
.join("fish")
.join("conf.d")
.join("goup.fish")),
}
}
fn path_to_string<T: AsRef<Path>>(&self, path: T) -> Result<String, Error> {
match self {
Self::Bash | Self::Sh | Self::Zsh => {
#[cfg(not(windows))]
return Ok(path.as_ref().to_string_lossy().to_string());
#[cfg(windows)]
return Ok(env::to_gitbash_path(&path.as_ref().to_string_lossy()));
}
Self::Cmd | Self::PowerShell | Self::Nushell => {
Ok(path.as_ref().to_string_lossy().to_string())
}
Self::Fish => Ok(path.as_ref().to_string_lossy().to_string()),
}
}
fn get_apply_env_command(&self) -> Result<&'static str, Error> {
match self {
Self::Bash | Self::Sh | Self::Zsh | Self::Fish => Ok(r#"eval "$(goup env)""#),
Self::Cmd | Self::PowerShell => Ok("goup env | Out-String | Invoke-Expression"),
Self::Nushell => {
#[cfg(not(windows))]
return Ok("load-env (\
goup env \
| from toml \
| update PATH {do $env.ENV_CONVERSIONS.PATH.from_string $in}\
)");
#[cfg(windows)]
return Ok("load-env (\
goup env \
| from toml \
| rename -c {PATH: Path} \
| update Path {do $env.ENV_CONVERSIONS.Path.from_string $in}\
)");
}
}
}
fn is_env_applied(&self) -> Result<bool, Error> {
let current_install_dir = env::get_current_install_dir()?;
let current_install_dir = self.path_to_string(current_install_dir)?;
let set_install_dir = self.path_to_string(std::env::var("GOROOT").unwrap_or_default())?;
Ok(set_install_dir == current_install_dir)
}
}