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

            
3
use crate::error::Error;
4
use crate::matcher::Matcher;
5

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

            
27
14
    match target_index {
28
10
        Some(target_index) => {
29
10
            let path_components: Vec<_> = path.components().collect();
30
10
            Ok(path_components[..=target_index].iter().collect::<PathBuf>())
31
        }
32
4
        None => Err(Error::NoMatch),
33
    }
34
14
}
35

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

            
59
12
    path_components[..end_component].iter().collect::<PathBuf>()
60
12
}
61

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

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

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

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

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

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

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

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

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

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

            
128
        // THEN we should get a NoMatch error
129
2
        assert!(matches!(actual, Err(Error::NoMatch)));
130
2
    }
131

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