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
use std::env;
use std::error;
use std::fmt;
use std::fs;
use std::io;
use std::path::{Component, Path, PathBuf, Prefix};
use std::result;

pub type Result<T> = result::Result<T, Error>;

#[derive(Debug)]
pub enum Error {
    CurrentIsRelative,
    UnsupportedPrefix,
    IoError(io::Error),
}

impl fmt::Display for Error {
    fn fmt(&self, b: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::CurrentIsRelative => write!(
                b,
                "the path specified as current directory was relative path."
            ),
            Error::UnsupportedPrefix => {
                write!(b, "the path specified has the prefix that isn't supported.")
            }
            Error::IoError(ref e) => write!(b, "io::Error happened: {}", e),
        }
    }
}

impl error::Error for Error {}

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Error {
        Error::IoError(e)
    }
}

/// get the absolute path for specified file.
/// Note: the file must exist.
pub fn to_absolute(current: impl AsRef<Path>, relative: impl AsRef<Path>) -> Result<PathBuf> {
    let current = current.as_ref();
    let relative = relative.as_ref();
    if relative.is_absolute() {
        return Ok(relative.to_path_buf());
    }
    if !current.is_absolute() {
        return Err(Error::CurrentIsRelative);
    }

    // here: current is absolute path, relative is relative path.
    let joined = current.join(relative);

    canonicalize(joined)
}

/// get the absolute path for specified file, relative to current working
/// directory.
pub fn to_absolute_from_current_dir(relative: impl AsRef<Path>) -> Result<PathBuf> {
    let current_dir = env::current_dir()?;
    to_absolute(current_dir, relative)
}

pub fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf> {
    let canonicalized = fs::canonicalize(path.as_ref())?;
    let components = canonicalized.components().map(|component| match component {
        Component::Prefix(prefix) => match prefix.kind() {
            Prefix::Disk(disk) | Prefix::VerbatimDisk(disk) => {
                let disk = disk as char;
                Ok(format!("{}:", disk).into())
            }
            _ => Err(Error::UnsupportedPrefix),
        },
        other => Ok(other.as_os_str().to_os_string()),
    });

    components.collect()
}

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

    fn toabs(cur: &str, rel: &str) -> Result<String> {
        to_absolute(cur, rel).map(|x| x.display().to_string())
    }

    #[test]
    fn test_supported() {
        if cfg!(windows) {
            assert_eq!(
                r#"C:\Windows\System32"#,
                toabs(r#"C:\"#, r#".\Windows\System32"#).unwrap()
            );

            assert_eq!(
                r#"C:\Windows\System32"#,
                toabs(r#"C:\Program Files"#, r#"..\Windows\System32"#).unwrap()
            );

            assert_eq!(
                r#"C:\Windows\System32"#,
                toabs(
                    r#"C:\Program Files\..\Windows\Fonts"#,
                    r#"..\..\Windows\System32"#
                )
                .unwrap()
            );
        } else {
            assert_eq!("/usr/share", toabs("/", "./usr/share").unwrap());
            assert_eq!("/usr/share", toabs("/opt", "../usr/share").unwrap());
            assert_eq!(
                "/usr/share",
                toabs("/opt/../usr/local/bin", "../../share").unwrap()
            );
        }
    }

    #[test]
    fn test_unsupported() {
        if cfg!(windows) {
            assert!(toabs(r#"\\?\pictures"#, r#".\Windows\System32"#).is_err());

            // DOS Device Path Syntax must not have `.` or `..` or something...
            assert!(toabs(r#"\\?\C:\"#, r#".\Windows\System32"#).is_err());
        }
    }
}