Lines
100 %
Functions
85.19 %
Branches
use std::env;
use crate::error::Error;
mod bash;
mod fish;
mod nushell;
mod pwsh;
mod zsh;
/// Supported shell types
#[derive(Debug, PartialEq)]
enum ShellType {
Bash,
Fish,
Nushell,
Powershell,
Zsh,
}
impl ShellType {
/// Detect shell type from a hint string (shell name or full path like
/// `/usr/bin/fish`). Falls back to Bash for unknown shells.
fn detect(hint: &str) -> Self {
match hint {
s if s.contains("bash") => ShellType::Bash,
s if s.contains("fish") => ShellType::Fish,
s if s.contains("powershell") => ShellType::Powershell,
s if s.contains("pwsh") => ShellType::Powershell,
s if s.contains("nushell") => ShellType::Nushell,
s if s.contains("nu") => ShellType::Nushell,
s if s.contains("zsh") => ShellType::Zsh,
_ => ShellType::Bash,
/// Return the integration script template for this shell type
fn script(&self) -> &'static str {
match self {
ShellType::Bash => bash::SCRIPT,
ShellType::Fish => fish::SCRIPT,
ShellType::Nushell => nushell::SCRIPT,
ShellType::Powershell => pwsh::SCRIPT,
ShellType::Zsh => zsh::SCRIPT,
/// Shell integration script
pub struct Shell {
/// Alias to use for the shell function
alias: String,
/// Integration script template
script: String,
impl Shell {
/// Compile the shell integration template
pub fn compile_script(&self) -> String {
self.script.replace("{alias}", &self.alias)
/// Return the shell integration script for shell given in `hint`. If `hint` is
/// `None`, try to detect the current shell. Use `bash` as fallback.
///
/// `alias` is the name of the shell function to use in the integration script.
/// # Errors
/// If `hint` is `None` and the `SHELL` environment variable is not set, an
/// error is returned.
pub fn initialize(alias: String, hint: &Option<String>) -> Result<Shell, Error> {
let shell_hint = match hint {
Some(hint) => hint.clone(),
None => env::var("SHELL").map_err(|_| Error::ShellNotDetected)?,
};
let shell_type = ShellType::detect(&shell_hint);
Ok(Shell {
alias,
script: shell_type.script().to_string(),
})
#[cfg(test)]
macro_rules! initialize_shell {
($($name: ident: $value:expr,)*) => {
$(
#[test]
fn $name() {
// GIVEN a shell hint
let hint = Some($value.to_string());
// WHEN trying to initialize the hinted shell
let alias = String::from("up");
let actual = initialize(alias, &hint);
// THEN we should get the initialization function for the expected
// shell
let expected = format!("init {}", &hint.unwrap());
assert!(actual.unwrap().compile_script().contains(&expected))
)*
macro_rules! detect_shell {
($($name: ident: ($hint:expr, $expected:expr),)*) => {
// GIVEN a shell hint string
let hint = $hint;
// WHEN detecting the shell type
let actual = ShellType::detect(hint);
// THEN the detected type should match
assert_eq!(actual, $expected);
mod tests {
use super::*;
fn initialize_from_env() {
// GIVEN non-existing shell
env::set_var("SHELL", "radish");
// WHEN trying initialize without a hint
let actual = initialize(alias, &None);
// THEN we should get the initialization function for the default shell
let expected = "init bash";
fn initialize_non_existant() {
// GIVEN empty SHELL variable
env::remove_var("SHELL");
// THEN we should get a ShellNotDetected error
assert!(matches!(actual, Err(Error::ShellNotDetected)));
initialize_shell! {
initialize_bash: ("bash"),
initialize_fish: ("fish"),
initialize_nushell: ("nushell"),
initialize_pwsh: ("pwsh"),
initialize_zsh: ("zsh"),
detect_shell! {
detect_bash: ("bash", ShellType::Bash),
detect_fish: ("fish", ShellType::Fish),
detect_nushell: ("nushell", ShellType::Nushell),
detect_nu: ("nu", ShellType::Nushell),
detect_pwsh: ("pwsh", ShellType::Powershell),
detect_powershell: ("powershell", ShellType::Powershell),
detect_zsh: ("zsh", ShellType::Zsh),
detect_unknown_falls_back_to_bash: ("radish", ShellType::Bash),
detect_full_path_bash: ("/usr/bin/bash", ShellType::Bash),
detect_full_path_fish: ("/usr/bin/fish", ShellType::Fish),
detect_full_path_nu: ("/usr/bin/nu", ShellType::Nushell),
detect_full_path_zsh: ("/usr/bin/zsh", ShellType::Zsh),