1
use std::path::{Path, PathBuf};
2

            
3
use crate::matcher::Matcher;
4

            
5
/// Try to find the best fuzzy match for the target component in the path.
6
/// Return a new path that ends at the target component. If the target component
7
/// is not found, return an Error.
8
///
9
/// # Examples
10
///
11
/// ```
12
/// let matcher = cdup::matcher::Matcher::default();
13
/// let cwd = std::path::Path::new("/home/rsprta/cdup");
14
/// let target = String::from("home");
15
/// let expected = Ok(std::path::Path::new("/home").to_path_buf());
16
///
17
/// assert_eq!(expected, cdup::cd::up_to_target(&matcher, &cwd, &target));
18
/// ```
19
12
pub fn up_to_target(matcher: &Matcher, path: &Path, target: &str) -> Result<PathBuf, &'static str> {
20
12
    let path_components: Vec<&str> = path
21
12
        .iter()
22
54
        .map(|component| component.to_str().unwrap())
23
12
        .collect();
24
12
    let target_index = matcher.best_match(&path_components, target);
25
12

            
26
12
    match target_index {
27
10
        Some(target_index) => {
28
10
            let path_components: Vec<_> = path.components().collect();
29
10
            Ok(path_components[..=target_index].iter().collect::<PathBuf>())
30
        }
31
2
        None => Err("No match found for target."),
32
    }
33
12
}
34

            
35
/// Return path up by given number of components. If the level is greater than
36
/// the number of components in the path, return the first component of the
37
/// path.
38
///
39
/// # Examples
40
///
41
/// ```
42
/// let cwd = std::path::Path::new("/home/rsprta/cdup");
43
/// let target: usize = 3;
44
/// let expected = Ok(std::path::Path::new("/").to_path_buf());
45
///
46
/// assert_eq!(expected, cdup::cd::up_by_count(&cwd, target));
47
/// ```
48
10
pub fn up_by_count(path: &Path, level: usize) -> Result<PathBuf, &'static str> {
49
10
    let path_components: Vec<_> = path.components().collect();
50
10
    let path_length = path_components.len();
51
    // If we jump by too much, return the first component of a path
52
10
    let end_component = if level < path_length {
53
8
        path_length - level
54
    } else {
55
2
        1
56
    };
57

            
58
10
    Ok(path_components[..end_component].iter().collect::<PathBuf>())
59
10
}
60

            
61
#[cfg(test)]
62
macro_rules! up_to_target {
63
    ($($name: ident: $value:expr,)*) => {
64
    $(
65
        #[test]
66
        fn $name() {
67
            // GIVEN a path and target component to jump to
68
10
            let (original_path, target, expected) = $value;
69
10
            let original_path = Path::new(original_path);
70
10
            let target = String::from(target);
71
10
            let expected = Ok(PathBuf::from(expected));
72
10
            let matcher = Matcher::default();
73
10

            
74
10
            // WHEN jumping up to target component
75
10
            let actual = up_to_target(&matcher, &original_path, &target);
76
10

            
77
10
            // THEN the new path should end there
78
10
            assert_eq!(expected, actual);
79
10
        }
80
    )*
81
    }
82
}
83

            
84
#[cfg(test)]
85
macro_rules! up_by_count {
86
    ($($name: ident: $value:expr,)*) => {
87
    $(
88
        #[test]
89
        fn $name() {
90
            // GIVEN a path and level to jump up by
91
10
            let (original_path, level, expected) = $value;
92
10
            let original_path = Path::new(original_path);
93
10
            let expected = Ok(PathBuf::from(expected));
94
10

            
95
10
            // WHEN jumping up the path by given level
96
10
            let actual = up_by_count(&original_path, level);
97
10

            
98
10
            // THEN the new path should be shortened by that number of components
99
10
            assert_eq!(expected, actual);
100
10
        }
101
    )*
102
    }
103
}
104

            
105
#[cfg(test)]
106
mod tests {
107
    use super::*;
108

            
109
10
    up_to_target! {
110
10
        up_to_target_directory: ("/home/user/directory", "directory", "/home/user/directory"),
111
10
        up_to_target_user: ("/home/user/directory", "user", "/home/user"),
112
10
        up_to_target_home: ("/home/user/directory", "home", "/home"),
113
10
        up_to_target_multiple: ("/multiple/multiple/dirs", "multiple", "/multiple/multiple"),
114
10
        up_to_target_fuzzy: ("/home/user/directory", "usr", "/home/user"),
115
10
    }
116

            
117
2
    #[test]
118
2
    fn up_to_target_no_match() {
119
2
        // GIVEN a path and non-existing target component to jump to
120
2
        let original_path = Path::new("/home/user/directory");
121
2
        let target = String::from("no_match");
122
2
        let matcher = Matcher::default();
123
2

            
124
2
        // WHEN jumping up to target component
125
2
        let actual = up_to_target(&matcher, &original_path, &target);
126
2

            
127
2
        // THEN we should get an error
128
2
        let expected = Err("No match found for target.");
129
2
        assert_eq!(expected, actual);
130
2
    }
131

            
132
10
    up_by_count! {
133
10
        up_by_count_0: ("/home/user/directory", 0, "/home/user/directory"),
134
10
        up_by_count_1: ("/home/user/directory", 1, "/home/user"),
135
10
        up_by_count_2: ("/home/user/directory", 2, "/home"),
136
10
        up_by_count_3: ("/home/user/directory", 3, "/"),
137
10
        up_by_count_4: ("/home/user/directory", 4, "/"),
138
10
    }
139
}