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
//! Path related utilities

use std::path::{Path, PathBuf};
use std::io;
use std::fs;

/// Denotes value as having path on a file system
pub trait HasPath {
    /// Returns a reference to a path
    fn path(&self) -> &Path;
}

/// Allows for directory resolution
pub trait ResolvePath {
    /// Resolves to an actual directory
    ///
    /// It will interpret common conventions for resolving a directory
    /// (so, for example, a file with a textual link in it will be resolved
    /// to the directory it points to)
    fn resolve_dir(&self) -> Result<PathBuf, io::Error>;
}

impl<T> ResolvePath for T where T: AsRef<Path> {
    fn resolve_dir(&self) -> Result<PathBuf, io::Error> {
        let mut path: PathBuf = self.as_ref().into();
        if path.is_dir() {
            Ok(path)
        } else if path.is_file() {
            fs::File::open(&path)
                .and_then(|mut f| {
                    use std::io::Read;
                    let mut s = String::new();
                    f.read_to_string(&mut s).map(|_| s)
                })
                .and_then(|s| {
                    #[cfg(windows)]
                    let s = s.replace("/", "\\");
                    let trimmed_path = s.trim();
                    path.pop(); // remove the file name
                    path.join(PathBuf::from(trimmed_path)).resolve_dir()
                })
        } else {
            let total_components = path.components().count();
            let mut components = path.components();
            let mut rebuilt_path = components.next().unwrap().resolve_dir()?;
            for (i, component) in components.enumerate() {
                rebuilt_path.push(component);
                if rebuilt_path.exists() && i + 2 < total_components {
                    rebuilt_path = rebuilt_path.resolve_dir()?;
                } else if !rebuilt_path.exists() {
                    return Err(io::ErrorKind::NotFound.into())
                }
            }
            Ok(rebuilt_path)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::ResolvePath;
    use std::fs;
    use std::io::Write;
    use tempdir::TempDir;

    #[test]
    fn resolve_dir() {
        let tmp = TempDir::new("sit").unwrap().into_path();
        assert_eq!(tmp.resolve_dir().unwrap(), tmp);
    }

    #[test]
    fn resolve_link() {
        let tmp = TempDir::new("sit").unwrap().into_path();
        fs::create_dir_all(tmp.join("dir")).unwrap();
        let mut f = fs::File::create(tmp.join("1")).unwrap();
        f.write(b"dir").unwrap();
        assert_eq!(tmp.join("1").resolve_dir().unwrap(), tmp.join("dir"));
    }

    #[test]
    fn resolve_broken_link() {
        let tmp = TempDir::new("sit").unwrap().into_path();
        let mut f = fs::File::create(tmp.join("1")).unwrap();
        f.write(b"dir").unwrap();
        assert!(tmp.join("1").resolve_dir().is_err());
    }

    #[test]
    fn resolve_link_nested() {
        let tmp = TempDir::new("sit").unwrap().into_path();
        fs::create_dir_all(tmp.join("dir")).unwrap();
        let mut f = fs::File::create(tmp.join("1")).unwrap();
        f.write(b"dir").unwrap();
        let mut f = fs::File::create(tmp.join("2")).unwrap();
        f.write(b"1").unwrap();
        assert_eq!(tmp.join("2").resolve_dir().unwrap(), tmp.join("dir"));
    }

    #[test]
    fn resolve_path_with_a_link_inside() {
        let tmp = TempDir::new("sit").unwrap().into_path();
        fs::create_dir_all(tmp.join("dir")).unwrap();
        let mut f = fs::File::create(tmp.join("1")).unwrap();
        f.write(b"dir").unwrap();
        let mut f = fs::File::create(tmp.join("dir").join("2")).unwrap();
        f.write(b"not a link").unwrap();
        assert_eq!(tmp.join("1").join("2").resolve_dir().unwrap(), tmp.join("dir").join("2"));
        // this path is not found
        assert!(tmp.join("1").join("3").resolve_dir().is_err());
    }

}