Commit 96555a5f authored by Andy Barron's avatar Andy Barron
Browse files

Update to version 0.1.0 with full documentation and app-specific paths

parent a3ac82d0
[package]
name = "app_dirs"
version = "0.0.2"
version = "0.1.0"
authors = ["Andy Barron <AndrewLBarron@gmail.com>"]
description = "Access platform-dependent canonical locations for app-specific data"
......@@ -10,6 +10,5 @@ readme = "README.md"
keywords = ["application", "data", "storage", "location", "directory"]
license = "MIT"
[dependencies]
# TODO conditional dependencies when Rust 1.8.0 is released
[target.'cfg(all(unix, not(target_os="macos")))'.dependencies]
xdg = "^2.0.0"
use std;
#[derive(Clone, Debug)]
/// Data structure that holds information about your app.
///
/// This is used to pinpoint a specific location for your app's data on the
/// file system, relative to where app data must be stored. Therefore, the
/// attributes `name` and `author` MUST be valid directory names! It's HIGHLY
/// recommended that you only use letters, numbers, spaces, hyphens, and
/// underscores.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct AppInfo {
name: String,
author: String,
safe_name: String,
safe_author: String,
/// Filename-safe name of your app (e.g. "Hearthstone").
pub name: String,
/// Filename-safe author of your app (e.g. "Blizzard").
pub author: String,
}
impl AppInfo {
pub fn new(name: &str, author: &str) -> Self {
/// Convenience constructor to automatically convert e.g. static `&str`
/// into `String`.
pub fn new<A, B>(name: A, author: B) -> Self
where A: Into<String>,
B: Into<String>
{
AppInfo {
name: name.into(),
author: author.into(),
safe_name: name.into(), // TODO
safe_author: author.into(), // TODO
}
}
pub fn safe_name(&self) -> &str {
&self.safe_name
}
pub fn safe_author(&self) -> &str {
&self.safe_author
}
pub fn get_name(&self) -> &str {
&self.name
}
pub fn get_author(&self) -> &str {
&self.author
}
}
/// Enum specifying the type of app data you want to store.
///
/// Note that different platforms are not guaranteed or required
/// to provide different locations for different variants. For example,
/// Windows does not supported shared application data and does not
/// distinguish between config and data. Therefore, on Windows, all variants
/// except `UserCache` return the same path! Keep this in mind when choosing
/// data file names and paths.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum AppDirType {
pub enum AppDataType {
/// User-specific app configuration data.
UserConfig,
/// User-specific arbitrary app data.
UserData,
/// User-specific app cache data.
UserCache,
/// System-wide arbitrary app data.
SharedData,
/// System-wide app configuration data.
SharedConfig,
}
impl AppDirType {
impl AppDataType {
/// Returns `true` for non-user-specific data types.
pub fn is_shared(&self) -> bool {
use AppDirType::*;
use AppDataType::*;
match *self {
SharedData | SharedConfig => true,
_ => false,
......@@ -50,17 +62,24 @@ impl AppDirType {
}
}
/// Error type for any `app_dirs` operation.
#[derive(Debug)]
pub enum AppDirError {
pub enum AppDirsError {
/// An I/O error occurred during the operation.
Io(std::io::Error),
// NotFound(PathBuf),
/// App-specific directories are not properly supported by the system
/// (e.g. required environment variables don't exist).
NotSupported,
/// App info given to this library was invalid (e.g. app name or author
/// were empty).
InvalidAppData,
}
impl From<std::io::Error> for AppDirError {
impl From<std::io::Error> for AppDirsError {
fn from(e: std::io::Error) -> Self {
AppDirError::Io(e)
AppDirsError::Io(e)
}
}
pub type AppDirResult<T> = Result<T, AppDirError>;
\ No newline at end of file
/// Result type for any `app_dirs` operation.
pub type AppDirsResult<T> = Result<T, AppDirsError>;
use common::{AppDirsError, AppDirsResult, AppDataType, AppInfo};
use std::fs;
use std::path::PathBuf;
#[cfg(target_os="macos")]
mod platform {
mod macos;
......@@ -14,6 +18,56 @@ mod platform {
pub use self::windows::*;
}
pub use self::platform::get_app_dir;
// TODO function to create dir (by calling create_dir_all)
// TODO helper functions (optionally using AppInfo) to read/write from actual files
\ No newline at end of file
/// Creates (if necessary) directory hierarchy for the data type and app info
/// provided.
///
/// A result of `Ok` guarantees that the directory located at the returned
/// `PathBuf` was created, including its full parent hierarchy if required.
///
/// Different `AppDataType` variants are NOT GUARANTEED to return different
/// directories (e.g. on Windows, everything except `UserCache` goes in
/// `%APPDATA%`).
pub fn create_app_dir(t: AppDataType, app: &AppInfo) -> AppDirsResult<PathBuf> {
get_app_dir_path(t, app).and_then(|path| {
match fs::create_dir_all(&path) {
Ok(..) => Ok(path),
Err(e) => Err(e.into()),
}
})
}
/// Gets directory path for the data type and app info provided.
///
/// A result of `Ok` DOES NOT necessarily mean that the directory actually
/// exists -- just that we were able to determine what the path SHOULD be.
///
/// Different `AppDataType` variants are NOT GUARANTEED to return different
/// directories (e.g. on Windows, everything except `UserCache` goes in
/// `%APPDATA%`).
pub fn get_app_dir_path(t: AppDataType, app: &AppInfo) -> AppDirsResult<PathBuf> {
if app.author.len() == 0 || app.name.len() == 0 {
return Err(AppDirsError::InvalidAppData);
}
get_app_data_root(t).map(|mut path| {
path.push(app.author.clone());
path.push(app.name.clone());
path
})
}
/// Gets path to root app data directory for the data type and app info
/// provided.
///
/// "Root" in this case means that this function will return the top-level
/// directory for all app data of type `t`. Generally, you should prefer to
/// call `get_app_dir_path` with an instance of `AppInfo`.
///
/// A result of `Ok` DOES NOT necessarily mean that the directory actually
/// exists -- just that we were able to determine what the path SHOULD be.
///
/// Different `AppDataType` variants are NOT GUARANTEED to return different
/// directories (e.g. on Windows, everything except `UserCache` goes in
/// `%APPDATA%`).
pub fn get_app_data_root(t: AppDataType) -> AppDirsResult<PathBuf> {
platform::get_app_dir(t)
}
use common::*;
use AppDirType::*;
use AppDataType::*;
use std::env::home_dir;
use std::path::{Component, PathBuf};
pub fn get_app_dir(t: AppDirType) -> AppDirResult<PathBuf> {
let dir_base: AppDirResult<PathBuf> = if t.is_shared() {
pub fn get_app_dir(t: AppDataType) -> AppDirsResult<PathBuf> {
let dir_base: AppDirsResult<PathBuf> = if t.is_shared() {
Ok(Component::RootDir.as_ref().into())
} else {
home_dir().ok_or_else(|| AppDirError::NotSupported)
home_dir().ok_or_else(|| AppDirsError::NotSupported)
};
dir_base.map(|mut path| {
match t {
......
......@@ -2,14 +2,18 @@ extern crate xdg;
use self::xdg::BaseDirectories as Xdg;
use common::*;
use std::path::PathBuf;
use AppDirType::*;
use AppDataType::*;
pub fn get_app_dir(t: AppDirType) -> AppDirResult<PathBuf> {
Xdg::new().ok().as_ref().and_then(|x| match t {
UserConfig => Some(x.get_config_home()),
UserData => Some(x.get_data_home()),
UserCache => Some(x.get_cache_home()),
SharedData => x.get_data_dirs().into_iter().next(),
SharedConfig => x.get_config_dirs().into_iter().next(),
}).ok_or_else(|| AppDirError::NotSupported)
pub fn get_app_dir(t: AppDataType) -> AppDirsResult<PathBuf> {
Xdg::new()
.ok()
.as_ref()
.and_then(|x| match t {
UserConfig => Some(x.get_config_home()),
UserData => Some(x.get_data_home()),
UserCache => Some(x.get_cache_home()),
SharedData => x.get_data_dirs().into_iter().next(),
SharedConfig => x.get_config_dirs().into_iter().next(),
})
.ok_or_else(|| AppDirsError::NotSupported)
}
use common::*;
use std::path::PathBuf;
use std::env;
use AppDirType::*;
use AppDataType::*;
pub fn get_app_dir(t: AppDirType) -> AppDirResult<PathBuf> {
pub fn get_app_dir(t: AppDataType) -> AppDirsResult<PathBuf> {
match t {
UserConfig | UserData | SharedConfig | SharedData =>
env::var("APPDATA"),
UserCache =>
env::var("LOCALAPPDATA"),
}.and_then(|x| Ok(PathBuf::from(x))).or_else(|_| Err(AppDirError::NotSupported))
UserConfig | UserData | SharedConfig | SharedData => env::var("APPDATA"),
UserCache => env::var("LOCALAPPDATA"),
}
.and_then(|x| Ok(PathBuf::from(x)))
.or_else(|_| Err(AppDirsError::NotSupported))
}
#![warn(missing_docs)]
//! Access platform-dependent canonical locations for app-specific data.
mod common;
pub use common::*;
mod imp;
......@@ -7,19 +8,13 @@ pub use imp::*;
#[cfg(test)]
mod tests {
use super::*;
use AppDirType::*;
use AppDataType::*;
#[test]
fn it_works() {
// let info = AppInfo::new("Fancy Dev", "Cool App");
let types = [
UserConfig,
UserData,
UserCache,
SharedData,
SharedConfig,
];
let info = AppInfo::new("Awesome App", "Dedicated Dev");
let types = [UserConfig, UserData, UserCache, SharedData, SharedConfig];
for &t in types.iter() {
println!("{:?} = {:?}", t, get_app_dir(t))
println!("{:?} = {:?}", t, get_app_dir(t, &info))
}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment