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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// org id l+m3DECe179CylswisBkgJHs8Kb25EVmZSnj3a+UnHw=
//! Treating strings as paths. For portability reasons, paths must
//! internally be treated as strings, and converted to paths only by
//! the backend, if required (in-memory backends will typically not
//! need that conversion).

/// Returns the parent of the path, if it exists. This function tries
/// to replicate the behaviour of `std::path::Path::parent`, but with
/// `&str` instead of `Path`.
///
/// ```ignore
/// use libanu::path::parent;
/// assert_eq!(parent("/foo/bar"), Some("/foo"));
/// assert_eq!(parent("foo"), Some(""));
/// assert_eq!(parent("/"), None);
/// assert_eq!(parent(""), None);
/// ```
pub fn parent(mut path: &str) -> Option<&str> {
    loop {
        if path == "/" || path.is_empty() {
            return None;
        } else if let Some(i) = path.rfind('/') {
            let (a, b) = path.split_at(i);
            if b == "/" {
                path = a
            } else {
                return Some(a);
            }
        } else {
            return Some("");
        }
    }
}

/// Returns the file name of the path. if it exists. This function
/// tries to replicate the behaviour of `std::path::Path::file_name`,
/// but with `&str` instead of `Path`.
///
/// Like the original, returns `None` if the path terminates in `..`.
///
/// ```ignore
/// use libanu::path::file_name;
/// assert_eq!(file_name("/usr/bin/"), Some("bin"));
/// assert_eq!(file_name("tmp/foo.txt"), Some("foo.txt"));
/// assert_eq!(file_name("foo.txt/."), Some("foo.txt"));
/// assert_eq!(file_name("foo.txt/.//"), Some("foo.txt"));
/// assert_eq!(file_name("foo.txt/.."), None);
/// assert_eq!(file_name("/"), None);
/// ```
pub fn file_name(mut path: &str) -> Option<&str> {
    if path == "/" || path.is_empty() {
        None
    } else {
        while let Some(i) = path.rfind('/') {
            let (_, f) = path.split_at(i + 1);
            if f == ".." {
                return None;
            } else if f.is_empty() || f == "." {
                path = path.split_at(i).0
            } else {
                return Some(f);
            }
        }
        Some(path)
    }
}

#[test]
fn test_file_name() {
    assert_eq!(file_name("/usr/bin/"), Some("bin"));
    assert_eq!(file_name("tmp/foo.txt"), Some("foo.txt"));
    assert_eq!(file_name("foo.txt/."), Some("foo.txt"));
    assert_eq!(file_name("foo.txt/.//"), Some("foo.txt"));
    assert_eq!(file_name("foo.txt/.."), None);
    assert_eq!(file_name("/"), None);
}

/// Returns an iterator of the non-empty components of a path,
/// delimited by `/`. Note that `.` and `..` are treated as
/// components.
pub fn components<'a>(path: &'a str) -> Components<'a> {
    Components(path.split('/'))
}

#[derive(Clone)]
pub struct Components<'a>(std::str::Split<'a, char>);

impl<'a> std::fmt::Debug for Components<'a> {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(fmt, "Components {{ .. }}")
    }
}

impl<'a> Iterator for Components<'a> {
    type Item = &'a str;
    fn next(&mut self) -> Option<Self::Item> {
        loop {
            if let Some(n) = self.0.next() {
                if !n.is_empty() {
                    return Some(n);
                }
            } else {
                return None;
            }
        }
    }
}

/// Push a path component on an existing path. Only works if `extra`
/// is a relative path.
/// ```ignore
/// use libanu::path::push;
/// let mut s = "a".to_string();
/// push(&mut s, "b");
/// assert_eq!(s, "a/b");
/// push(&mut s, "c");
/// assert_eq!(s, "a/b/c");
/// ```
pub fn push(path: &mut String, extra: &str) {
    assert!(!extra.starts_with("/")); // Make sure the extra path is relative.
    if !path.ends_with("/") && !path.is_empty() {
        path.push('/');
    }
    path.push_str(extra)
}

/// Pop the last component off an existing path.
/// ```ignore
/// use libanu::path::pop;
/// let mut s = "a/b/c".to_string();
/// pop(&mut s);
/// assert_eq!(s, "a/b");
/// pop(&mut s);
/// assert_eq!(s, "a");
/// pop(&mut s);
/// assert_eq!(s, "");
/// ```
pub fn pop(path: &mut String) {
    if let Some(i) = path.rfind('/') {
        path.truncate(i)
    } else {
        path.clear()
    }
}