1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
use std::borrow::Cow;

fn handle_dots_push(string: &mut String, count: u8) {
    if count < 1 {
        return;
    }

    if count == 1 {
        string.push('.');
        return;
    }

    for _ in 0..(count - 1) {
        string.push_str("../");
    }

    string.pop(); // remove last '/'
}

pub fn expand_ndots(path: &str) -> Cow<'_, str> {
    let mut dots_count = 0u8;
    let ndots_present = {
        for chr in path.chars() {
            if chr == '.' {
                dots_count += 1;
            } else {
                if dots_count > 2 {
                    break;
                }

                dots_count = 0;
            }
        }

        dots_count > 2
    };

    if !ndots_present {
        return path.into();
    }

    let mut dots_count = 0u8;
    let mut expanded = String::new();
    for chr in path.chars() {
        if chr != '.' {
            handle_dots_push(&mut expanded, dots_count);
            dots_count = 0;
            expanded.push(chr);
        } else {
            dots_count += 1;
        }
    }

    handle_dots_push(&mut expanded, dots_count);

    expanded.into()
}

pub fn expand_path<'a>(path: &'a str) -> Cow<'a, str> {
    let tilde_expansion: Cow<'a, str> = shellexpand::tilde(path);
    let ndots_expansion: Cow<'a, str> = match tilde_expansion {
        Cow::Borrowed(b) => expand_ndots(b),
        Cow::Owned(o) => expand_ndots(&o).to_string().into(),
    };

    ndots_expansion
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn string_without_ndots() {
        assert_eq!("../hola", &expand_ndots("../hola").to_string());
    }

    #[test]
    fn string_with_three_ndots() {
        assert_eq!("../..", &expand_ndots("...").to_string());
    }

    #[test]
    fn string_with_three_ndots_and_final_slash() {
        assert_eq!("../../", &expand_ndots(".../").to_string());
    }

    #[test]
    fn string_with_three_ndots_and_garbage() {
        assert_eq!(
            "ls ../../ garbage.*[",
            &expand_ndots("ls .../ garbage.*[").to_string(),
        );
    }
}