1
use std::env;
2

            
3
mod bash;
4
mod fish;
5
mod pwsh;
6
mod zsh;
7

            
8
/// Shell integration script
9
#[derive(Debug, PartialEq)]
10
pub struct Shell {
11
    /// Alias to use for the shell function
12
    alias: String,
13
    /// Integration script template
14
    script: String,
15
}
16

            
17
impl Shell {
18
    /// Compile the shell integration template
19
10
    pub fn compile_script(&self) -> String {
20
10
        self.script.replace("{alias}", &self.alias)
21
10
    }
22

            
23
    /// Create a new shell integration script
24
10
    pub fn new(alias: String, script: String) -> Self {
25
10
        Shell { alias, script }
26
10
    }
27
}
28

            
29
/// Return the shell integration script for shell given in `hint`. If `hint` is
30
/// `None`, try to detect the current shell. Use `bash` as fallback.
31
///
32
/// `alias` is the name of the shell function to use in the integration script.
33
///
34
/// # Errors
35
///
36
/// If `hint` is `None` and the `SHELL` environment variable is not set, an
37
/// error is returned.
38
12
pub fn initialize(alias: String, hint: &Option<String>) -> Result<Shell, &'static str> {
39
12
    let shell = match hint {
40
8
        Some(hint) => hint.clone(),
41
4
        None => match detect() {
42
2
            Some(shell) => shell,
43
2
            None => return Err("No hint given and SHELL environment variable not found."),
44
        },
45
    };
46
10
    match shell.as_str() {
47
10
        s if s.contains("bash") => Ok(Shell::new(alias, bash::SCRIPT.to_string())),
48
8
        s if s.contains("fish") => Ok(Shell::new(alias, fish::SCRIPT.to_string())),
49
6
        s if s.contains("powershell") => Ok(Shell::new(alias, pwsh::SCRIPT.to_string())),
50
6
        s if s.contains("pwsh") => Ok(Shell::new(alias, pwsh::SCRIPT.to_string())),
51
4
        s if s.contains("zsh") => Ok(Shell::new(alias, zsh::SCRIPT.to_string())),
52
2
        _ => Ok(Shell::new(alias, bash::SCRIPT.to_string())),
53
    }
54
12
}
55

            
56
/// Detect the current shell
57
fn detect() -> Option<String> {
58
4
    if let Ok(shell) = env::var("SHELL") {
59
2
        Some(shell)
60
    } else {
61
2
        None
62
    }
63
4
}
64

            
65
#[cfg(test)]
66
macro_rules! initialize_shell {
67
    ($($name: ident: $value:expr,)*) => {
68
    $(
69
        #[test]
70
        fn $name() {
71
            // GIVEN a shell hint
72
8
            let hint = Some($value.to_string());
73
8

            
74
8
            // WHEN trying to initialize the hinted shell
75
8
            let alias = String::from("up");
76
8
            let actual = initialize(alias, &hint);
77
8

            
78
8
            // THEN we should get the initialization function for the expected
79
8
            // shell
80
8
            let expected = format!("init {}", &hint.unwrap());
81
8
            assert!(actual.unwrap().compile_script().contains(&expected))
82
8
        }
83
    )*
84
    }
85
}
86

            
87
#[cfg(test)]
88
mod tests {
89
    use super::*;
90

            
91
2
    #[test]
92
2
    fn initialize_from_env() {
93
2
        // GIVEN non-existing shell
94
2
        env::set_var("SHELL", "radish");
95
2

            
96
2
        // WHEN trying initialize without a hint
97
2
        let alias = String::from("up");
98
2
        let actual = initialize(alias, &None);
99
2

            
100
2
        // THEN we should get the initialization function for the default shell
101
2
        let expected = "init bash";
102
2
        assert!(actual.unwrap().compile_script().contains(&expected))
103
2
    }
104

            
105
2
    #[test]
106
2
    fn initialize_non_existant() {
107
2
        // GIVEN empty SHELL variable
108
2
        env::remove_var("SHELL");
109
2

            
110
2
        // WHEN trying initialize without a hint
111
2
        let alias = String::from("up");
112
2
        let actual = initialize(alias, &None);
113
2

            
114
2
        // THEN we should get an error
115
2
        let expected = Err("No hint given and SHELL environment variable not found.");
116
2
        assert_eq!(actual, expected);
117
2
    }
118

            
119
8
    initialize_shell! {
120
8
        initialize_bash: ("bash"),
121
8
        initialize_fish: ("fish"),
122
8
        initialize_pwsh: ("pwsh"),
123
8
        initialize_zsh: ("zsh"),
124
8
    }
125
}