use crate::env;
use crate::hook_env;
use itertools::Itertools;
use std::fmt::{Display, Formatter};
use std::path::PathBuf;
use std::str::FromStr;
mod bash;
mod elvish;
mod fish;
mod nushell;
mod pwsh;
mod xonsh;
mod zsh;
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum ShellType {
Bash,
Elvish,
Fish,
Nu,
Xonsh,
Zsh,
Pwsh,
}
impl ShellType {
pub fn as_shell(&self) -> Box<dyn Shell> {
match self {
Self::Bash => Box::<bash::Bash>::default(),
Self::Elvish => Box::<elvish::Elvish>::default(),
Self::Fish => Box::<fish::Fish>::default(),
Self::Nu => Box::<nushell::Nushell>::default(),
Self::Xonsh => Box::<xonsh::Xonsh>::default(),
Self::Zsh => Box::<zsh::Zsh>::default(),
Self::Pwsh => Box::<pwsh::Pwsh>::default(),
}
}
}
impl Display for ShellType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Bash => write!(f, "bash"),
Self::Elvish => write!(f, "elvish"),
Self::Fish => write!(f, "fish"),
Self::Nu => write!(f, "nu"),
Self::Xonsh => write!(f, "xonsh"),
Self::Zsh => write!(f, "zsh"),
Self::Pwsh => write!(f, "pwsh"),
}
}
}
impl FromStr for ShellType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
let s = s.rsplit_once('/').map(|(_, s)| s).unwrap_or(&s);
match s {
"bash" | "sh" => Ok(Self::Bash),
"elvish" => Ok(Self::Elvish),
"fish" => Ok(Self::Fish),
"nu" => Ok(Self::Nu),
"xonsh" => Ok(Self::Xonsh),
"zsh" => Ok(Self::Zsh),
"pwsh" => Ok(Self::Pwsh),
_ => Err(format!("unsupported shell type: {s}")),
}
}
}
pub trait Shell: Display {
fn activate(&self, opts: ActivateOptions) -> String;
fn deactivate(&self) -> String;
fn set_env(&self, k: &str, v: &str) -> String;
fn prepend_env(&self, k: &str, v: &str) -> String;
fn unset_env(&self, k: &str) -> String;
fn set_alias(&self, name: &str, cmd: &str) -> String {
let _ = (name, cmd);
String::new()
}
fn unset_alias(&self, name: &str) -> String {
let _ = name;
String::new()
}
fn format_activate_prelude(&self, prelude: &[ActivatePrelude]) -> String {
prelude
.iter()
.map(|p| match p {
ActivatePrelude::SetEnv(k, v) => self.set_env(k, v),
ActivatePrelude::PrependEnv(k, v) => self.prepend_env(k, v),
})
.join("")
}
}
pub enum ActivatePrelude {
SetEnv(String, String),
PrependEnv(String, String),
}
pub struct ActivateOptions {
pub exe: PathBuf,
pub flags: String,
pub no_hook_env: bool,
pub prelude: Vec<ActivatePrelude>,
}
pub fn build_deactivation_script(shell: &dyn Shell) -> String {
if !env::is_activated() {
return String::new();
}
let mut out = hook_env::clear_old_env(shell);
out.push_str(&hook_env::clear_aliases(shell));
out.push_str(&shell.deactivate());
out
}
pub fn get_shell(shell: Option<ShellType>) -> Option<Box<dyn Shell>> {
shell.or(*env::MISE_SHELL).map(|st| st.as_shell())
}