use std::borrow::Cow;
use std::fmt;
mod bash;
mod fish;
mod nushell;
mod pwsh;
mod zsh;
pub(crate) fn posix_quote(value: &str) -> Cow<'_, str> {
shlex::try_quote(value).unwrap_or_else(|_| {
let cleaned = value.replace('\0', "");
Cow::Owned(format!("'{}'", cleaned.replace('\'', "'\\''")))
})
}
pub use bash::Bash;
pub use fish::Fish;
pub use nushell::Nushell;
pub use pwsh::Pwsh;
pub use zsh::Zsh;
#[derive(Debug, Clone)]
pub struct ActivateOptions {
pub exe: std::path::PathBuf,
pub no_hook_env: bool,
}
pub trait Shell: fmt::Display + Send + Sync {
fn activate(&self, opts: ActivateOptions) -> String;
fn deactivate(&self) -> String;
fn set_env(&self, key: &str, value: &str) -> String;
fn unset_env(&self, key: &str) -> String;
fn hook_env_output(
&self,
added: &[(String, String)],
removed: &[String],
session_encoded: &str,
) -> String {
let mut output = String::new();
for (key, value) in added {
output.push_str(&self.set_env(key, value));
}
for key in removed {
output.push_str(&self.unset_env(key));
}
output.push_str(&self.set_env("__FNOX_SESSION", session_encoded));
output
}
fn deactivate_output(&self, secret_keys: &[String]) -> String {
let mut output = String::new();
for key in secret_keys {
output.push_str(&self.unset_env(key));
}
output.push_str(&self.deactivate());
output
}
}
pub fn get_shell(name: Option<&str>) -> anyhow::Result<Box<dyn Shell>> {
let shell_name = match name {
Some(n) => n.to_string(),
None => detect_shell().ok_or_else(|| anyhow::anyhow!("Could not detect shell"))?,
};
match shell_name.as_str() {
"bash" => Ok(Box::new(Bash)),
"zsh" => Ok(Box::new(Zsh)),
"fish" => Ok(Box::new(Fish)),
"nu" => Ok(Box::new(Nushell)),
"pwsh" | "powershell" => Ok(Box::new(Pwsh)),
_ => anyhow::bail!("unsupported shell: {}", shell_name),
}
}
pub fn detect_shell() -> Option<String> {
if let Ok(shell) = std::env::var("FNOX_SHELL") {
return Some(shell);
}
std::env::var("SHELL")
.ok()
.and_then(|s| s.rsplit('/').next().map(String::from))
}