appdirs 0.2.0

Rust crate for determining platform-specific directories
Documentation
#[cfg(target_os = "windows")]
extern crate shell32;
#[cfg(target_os = "windows")]
extern crate winapi;
#[cfg(target_os = "windows")]
extern crate ole32;

#[cfg(not(target_os = "windows"))]
use std::env;
use std::path::PathBuf;


#[cfg(target_os = "macos")]
fn home_dir_relative(rel: &str, app: Option<&str>) -> Result<PathBuf, ()> {
    let data_dir_res = env::home_dir();
    if data_dir_res.is_none() {
        return Err(());
    }
    let mut data_dir = data_dir_res.unwrap();
    data_dir.push(rel);
    if app.is_some() {
        data_dir.push(app.unwrap());
    }
    Ok(data_dir)
}

#[cfg(target_os = "macos")]
pub fn user_data_dir(app: Option<&str>, _: Option<&str>, _: bool) -> Result<PathBuf, ()> {
    home_dir_relative("Library/Application Support", app)
}

#[cfg(target_os = "macos")]
pub fn site_data_dir(app: Option<&str>, _: Option<&str>) -> Result<PathBuf, ()> {
    let mut data_dir = PathBuf::new();
    data_dir.push("/Library/Application Support");
    if app.is_some() {
        data_dir.push(app.unwrap());
    }
    Ok(data_dir)
}

#[cfg(target_os = "macos")]
pub fn user_config_dir(app: Option<&str>, author: Option<&str>, roaming: bool)
                       -> Result<PathBuf, ()> {
    user_data_dir(app, author, roaming)
}

#[cfg(target_os = "macos")]
pub fn site_config_dir(app: Option<&str>, author: Option<&str>) -> Result<PathBuf, ()> {
    site_data_dir(app, author)
}

#[cfg(target_os = "macos")]
pub fn user_cache_dir(app: Option<&str>, _: Option<&str>) -> Result<PathBuf, ()> {
    home_dir_relative("Library/Caches", app)
}

#[cfg(target_os = "macos")]
pub fn user_log_dir(app: Option<&str>, _: Option<&str>) -> Result<PathBuf, ()> {
    home_dir_relative("Library/Logs", app)
}

#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
fn home_dir_relative(xdg_key: &str, rel: &str, app: Option<&str>) -> Result<PathBuf, ()> {
    let mut data_dir = PathBuf::new();
    match env::var_os(xdg_key) {
        Some(dir) => { data_dir.push(dir); },
        None => {
            let home_res = env::home_dir();
            if home_res.is_none() {
                return Err(());
            }
            data_dir.push(home_res.unwrap());
            data_dir.push(rel);
        },
    };
    if app.is_some() {
        data_dir.push(app.unwrap());
    }
    Ok(data_dir)
}

#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
pub fn user_data_dir(app: Option<&str>, _: Option<&str>, _: bool) -> Result<PathBuf, ()> {
    home_dir_relative("XDG_DATA_HOME", ".local/share", app)
}

#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
pub fn site_data_dir(app: Option<&str>, _: Option<&str>) -> Result<PathBuf, ()> {
    let mut data_dir = PathBuf::new();
    let default = "/usr/local/share";
    match env::var_os("XDG_DATA_DIRS") {
        Some(joined) => {
            let first = env::split_paths(&joined).next();
            match first {
                Some(dir) => { data_dir.push(dir); },
                None => { data_dir.push(default); },
            };
        },
        None => { data_dir.push(default); },
    };
    if app.is_some() {
        data_dir.push(app.unwrap());
    }
    Ok(data_dir)
}

#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
pub fn user_config_dir(app: Option<&str>, _: Option<&str>, _: bool) -> Result<PathBuf, ()> {
    home_dir_relative("XDG_CONFIG_HOME", ".config", app)
}

#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
pub fn site_config_dir(app: Option<&str>, _: Option<&str>) -> Result<PathBuf, ()> {
    let mut data_dir = PathBuf::new();
    let default = "/etc/xdg";
    match env::var_os("XDG_CONFIG_DIRS") {
        Some(joined) => {
            let first = env::split_paths(&joined).next();
            match first {
                Some(dir) => { data_dir.push(dir); },
                None => { data_dir.push(default); },
            };
        },
        None => { data_dir.push(default); },
    };
    if app.is_some() {
        data_dir.push(app.unwrap());
    }
    Ok(data_dir)
}

#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
pub fn user_cache_dir(app: Option<&str>, _: Option<&str>) -> Result<PathBuf, ()> {
    home_dir_relative("XDG_CACHE_HOME", ".cache", app)
}

#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
pub fn user_log_dir(app: Option<&str>, author: Option<&str>) -> Result<PathBuf, ()> {
    let log_dir = user_cache_dir(app, author);
    match log_dir {
        Ok(mut log_dir) => { log_dir.push("log"); Ok(log_dir) },
        Err(err) => Err(err),
    }
}

#[cfg(target_os = "windows")]
static APPDATA_GUID: winapi::shtypes::KNOWNFOLDERID = winapi::shtypes::KNOWNFOLDERID {
    Data1: 1052149211,
    Data2: 26105,
    Data3: 19702,
    Data4: [160, 58, 227, 239, 101, 114, 159, 61],
};

#[cfg(target_os = "windows")]
static COMMON_APPDATA_GUID: winapi::shtypes::KNOWNFOLDERID = winapi::shtypes::KNOWNFOLDERID {
    Data1: 1655397762,
    Data2: 64961,
    Data3: 19907,
    Data4: [169, 221, 7, 13, 29, 73, 93, 151],
};

#[cfg(target_os = "windows")]
static LOCAL_APPDATA_GUID: winapi::shtypes::KNOWNFOLDERID = winapi::shtypes::KNOWNFOLDERID {
    Data1: 4055050117,
    Data2: 28602,
    Data3: 20431,
    Data4: [157, 85, 123, 142, 127, 21, 112, 145],
};

#[cfg(target_os = "windows")]
use std::ptr;

#[cfg(target_os = "windows")]
use std::ffi::OsString;

#[cfg(target_os = "windows")]
use std::os::windows::ffi::OsStringExt;

#[cfg(target_os = "windows")]
use std::slice;

#[cfg(target_os = "windows")]
fn get_dir(id: &winapi::shtypes::KNOWNFOLDERID) -> Result<OsString, ()> {
    let mut result: winapi::PWSTR = ptr::null_mut();
    let error;
    unsafe {
        error = shell32::SHGetKnownFolderPath(id, 0, ptr::null_mut(),
                                              &mut result);
    }
    if error != winapi::S_OK {
        //let _ = io::stderr().write(format!("error: {}\n", error));
        return Err(());
    }
    unsafe {
        let mut len = 0;
        let mut cur = result;
        while *cur != 0 {
            len += 1;
            cur = cur.offset(1);
        }
        let os_string: OsString =
            OsStringExt::from_wide(slice::from_raw_parts(result, len));
        ole32::CoTaskMemFree(result as *mut _);
        Ok(os_string)
    }
}

#[cfg(target_os = "windows")]
pub fn user_data_dir(app: Option<&str>, author: Option<&str>, roaming: bool)
                     -> Result<PathBuf, ()> {
    let dir_id = if roaming { APPDATA_GUID } else { LOCAL_APPDATA_GUID };
    let mut data_dir = PathBuf::new();
    data_dir.push(try!(get_dir(&dir_id)));
    if author.is_some() {
        data_dir.push(author.unwrap());
    }
    if app.is_some() {
        data_dir.push(app.unwrap());
    }
    Ok(data_dir)
}

#[cfg(target_os = "windows")]
pub fn site_data_dir(app: Option<&str>, author: Option<&str>) -> Result<PathBuf, ()> {
    let mut data_dir = PathBuf::new();
    data_dir.push(try!(get_dir(&COMMON_APPDATA_GUID)));
    if author.is_some() {
        data_dir.push(author.unwrap());
    }
    if app.is_some() {
        data_dir.push(app.unwrap());
    }
    Ok(data_dir)
}

#[cfg(target_os = "windows")]
pub fn user_config_dir(app: Option<&str>, author: Option<&str>, roaming: bool)
                       -> Result<PathBuf, ()> {
    user_data_dir(app, author, roaming)
}

#[cfg(target_os = "windows")]
pub fn site_config_dir(app: Option<&str>, author: Option<&str>) -> Result<PathBuf, ()> {
    site_data_dir(app, author)
}

#[cfg(target_os = "windows")]
pub fn user_cache_dir(app: Option<&str>, author: Option<&str>) -> Result<PathBuf, ()> {
    let cache_dir = user_data_dir(app, author, false);
    match cache_dir {
        Ok(mut cache_dir) => { cache_dir.push("Cache"); Ok(cache_dir) },
        Err(err) => Err(err),
    }
}

#[cfg(target_os = "windows")]
pub fn user_log_dir(app: Option<&str>, author: Option<&str>) -> Result<PathBuf, ()> {
    let mut log_dir = user_data_dir(app, author, false);
    match log_dir {
        Ok(mut log_dir) => { log_dir.push("Logs"); Ok(log_dir) },
        Err(err) => Err(err),
    }
}


#[cfg(test)]
mod tests {

    use std::io::{self, Write};
    use std::path::PathBuf;

    fn to_stderr(name: &str, value: PathBuf) {
        let _ = io::stderr().write(format!("{}: {}\n", name,
                                           value.to_str().unwrap()).as_bytes());
    }

    #[test]
    fn output_dirs() {
        to_stderr("user data dir",
                  super::user_data_dir(Some("AppDirs"), Some("djc"), false).unwrap());
        to_stderr("site data dir",
                  super::site_data_dir(Some("AppDirs"), Some("djc")).unwrap());
        to_stderr("user config dir",
                  super::user_config_dir(Some("AppDirs"), Some("djc"), false).unwrap());
        to_stderr("site config dir",
                  super::site_config_dir(Some("AppDirs"), Some("djc")).unwrap());
        to_stderr("user cache dir",
                  super::user_cache_dir(Some("AppDirs"), Some("djc")).unwrap());
        to_stderr("user log dir",
                  super::user_log_dir(Some("AppDirs"), Some("djc")).unwrap());
    }

}