1
use std::env;
2
use std::str::FromStr;
3

            
4
use crate::error::Error;
5

            
6
pub mod cd;
7
pub mod error;
8
pub mod hint;
9
pub mod matcher;
10
pub mod shell;
11

            
12
use matcher::Matcher;
13

            
14
/// Number or String input
15
#[derive(Clone, Debug)]
16
pub enum NumberOrString {
17
    Number(u8),
18
    Str(String),
19
}
20

            
21
/// Create instance from string argument
22
impl FromStr for NumberOrString {
23
    type Err = &'static str;
24

            
25
    fn from_str(s: &str) -> Result<Self, Self::Err> {
26
        Ok(s.parse::<u8>()
27
            .map(NumberOrString::Number)
28
            .unwrap_or_else(|_| NumberOrString::Str(s.to_string())))
29
    }
30
}
31

            
32
/// Run cd command
33
4
pub fn run_cd(target: &NumberOrString) -> Result<String, Error> {
34
4
    let cwd = env::current_dir().map_err(Error::InvalidCwd)?;
35
4
    let matcher = Matcher::default();
36
4
    let new_path = match target {
37
2
        NumberOrString::Number(level) => cd::up_by_count(&cwd, *level as usize),
38
2
        NumberOrString::Str(component) => cd::up_to_target(&matcher, &cwd, component)?,
39
    };
40

            
41
2
    Ok(format!("{}", new_path.display()))
42
4
}
43

            
44
/// Run hint command
45
4
pub fn run_hint(term: Option<&str>) -> Result<String, Error> {
46
4
    let cwd = env::current_dir().map_err(Error::InvalidCwd)?;
47
4
    let matcher = Matcher::default();
48
4
    Ok(hint::completion_for(&matcher, &cwd, term))
49
4
}
50

            
51
/// Run init command
52
4
pub fn run_init(alias: &String, hint: &Option<String>) -> Result<String, Error> {
53
4
    let shell = shell::initialize(alias.to_string(), hint)?;
54
2
    Ok(shell.compile_script())
55
4
}
56

            
57
#[cfg(test)]
58
mod tests {
59
    use super::*;
60

            
61
    #[test]
62
2
    fn run_cd_by_count() {
63
        // GIVEN a numeric target
64
2
        let target = NumberOrString::Number(1);
65

            
66
        // WHEN running cd
67
2
        let actual = run_cd(&target);
68

            
69
        // THEN we should get a valid path
70
2
        assert!(actual.is_ok());
71
2
    }
72

            
73
    #[test]
74
2
    fn run_cd_by_name_no_match() {
75
        // GIVEN a non-matching target name
76
2
        let target = NumberOrString::Str("zzz_nonexistent_zzz".to_string());
77

            
78
        // WHEN running cd
79
2
        let actual = run_cd(&target);
80

            
81
        // THEN we should get a NoMatch error
82
2
        assert!(matches!(actual, Err(Error::NoMatch)));
83
2
    }
84

            
85
    #[test]
86
2
    fn run_hint_without_term() {
87
        // GIVEN no search term
88

            
89
        // WHEN running hint
90
2
        let actual = run_hint(None);
91

            
92
        // THEN we should get a non-empty list of components
93
2
        assert!(!actual.unwrap().is_empty());
94
2
    }
95

            
96
    #[test]
97
2
    fn run_hint_with_term() {
98
        // GIVEN a search term
99
2
        let term = Some("nonexistent");
100

            
101
        // WHEN running hint
102
2
        let actual = run_hint(term);
103

            
104
        // THEN we should get a result (empty string for no match)
105
2
        assert!(actual.is_ok());
106
2
    }
107

            
108
    #[test]
109
2
    fn run_init_bash() {
110
        // GIVEN a bash shell hint and alias
111
2
        let alias = String::from("up");
112
2
        let hint = Some("bash".to_string());
113

            
114
        // WHEN running init
115
2
        let actual = run_init(&alias, &hint);
116

            
117
        // THEN we should get a script containing the alias
118
2
        assert!(actual.unwrap().contains("up"));
119
2
    }
120

            
121
    #[test]
122
2
    fn run_init_no_shell_no_env() {
123
        // GIVEN no shell hint and no SHELL environment variable
124
2
        env::remove_var("SHELL");
125
2
        let alias = String::from("up");
126

            
127
        // WHEN running init
128
2
        let actual = run_init(&alias, &None);
129

            
130
        // THEN we should get a ShellNotDetected error
131
2
        assert!(matches!(actual, Err(Error::ShellNotDetected)));
132
2
    }
133
}