Commit 8b971247 authored by Andy Barron's avatar Andy Barron
Browse files

Saner API, better docs, bump to 1.0.0

parent e8552d5d
[package]
name = "app_dirs"
version = "0.1.0"
version = "1.0.0"
authors = ["Andy Barron <AndrewLBarron@gmail.com>"]
description = "Access platform-dependent canonical locations for app-specific data"
documentation = "https://andybarron.github.io/app-dirs-rs"
description = "Put your app's data in the right place on every platform"
documentation = "https://docs.rs/app_dirs"
repository = "https://github.com/AndyBarron/app-dirs-rs"
readme = "README.md"
keywords = ["application", "data", "storage", "location", "directory"]
......
# app-dirs-rs
*Access platform-dependent canonical locations for app-specific data*
# app_dirs
*Put your app's data in the right place on every platform*
[![crates.io: app_dirs](https://img.shields.io/crates/v/app_dirs.svg?label=crates.io%3A%20app_dirs)](https://crates.io/crates/app_dirs)
[![Linux & OS X build status](https://img.shields.io/travis/AndyBarron/app-dirs-rs.svg?label=Linux%20%26%20OS%20X%20builds)](https://travis-ci.org/AndyBarron/app-dirs-rs)
[![Windows build status](https://img.shields.io/appveyor/ci/AndyBarron/app-dirs-rs.svg?label=Windows%20builds)](https://ci.appveyor.com/project/AndyBarron/app-dirs-rs)
More info coming soon!
## Documentation & examples
https://docs.rs/app_dirs
## Installation
Add the following to your `Cargo.toml` under `[dependencies]`:
```toml
app_dirs = "^1.0.0"
```
\ No newline at end of file
use std;
/// Data structure that holds information about your app.
/// Struct that holds information about your app.
///
/// It's recommended to create a single `const` instance of `AppInfo`:
///
/// 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.
/// ```
/// use app_dirs::AppInfo;
/// const APP_INFO: AppInfo = AppInfo{name: "Awesome App", author: "Dedicated Dev"};
/// ```
///
/// # Caveats
/// Functions in this library sanitize any characters that could be
/// non-filename-safe from `name` and `author`. The resulting paths will be
/// more human-readable if you stick to **letters, numbers, spaces, hyphens,
/// and underscores** for both properties.
///
/// The `author` property is currently only used by Windows, as macOS and *nix
/// specifications don't require it. Make sure your `name` string is unique!
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct AppInfo {
/// 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 {
/// 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(),
}
}
/// Name of your app (e.g. "Hearthstone").
pub name: &'static str,
/// Author of your app (e.g. "Blizzard").
pub author: &'static str,
}
/// 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
/// **Different platforms are NOT guaranteed to distinguish between each data
/// type.** Keep this in mind when choosing data file paths.
///
/// 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.
/// except `UserCache` return the same path.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum AppDataType {
/// User-specific app configuration data.
......@@ -80,6 +76,3 @@ impl From<std::io::Error> for AppDirsError {
AppDirsError::Io(e)
}
}
/// Result type for any `app_dirs` operation.
pub type AppDirsResult<T> = Result<T, AppDirsError>;
use common::{AppDirsError, AppDirsResult, AppDataType, AppInfo};
use common::{AppDirsError, AppDataType, AppInfo};
use utils;
use std::fs;
use std::path::PathBuf;
......@@ -18,56 +19,97 @@ mod platform {
pub use self::windows::*;
}
/// Creates (if necessary) directory hierarchy for the data type and app info
/// provided.
/// Creates (if necessary) and returns path to **app-specific** data
/// **subdirectory** for provided data type and subdirectory path.
///
/// A result of `Ok` guarantees that the directory located at the returned
/// `PathBuf` was created, including its full parent hierarchy if required.
/// The `path` parameter should be a valid relative path separated by
/// **forward slashes** (`/`).
///
/// 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()),
/// If the directory structure does not exist, this function will recursively
/// create the full hierarchy. Therefore, a result of `Ok` guarantees that the
/// returned path exists.
pub fn app_dir(t: AppDataType, app: &AppInfo, path: &str) -> Result<PathBuf, AppDirsError> {
let path = try!(get_app_dir(t, app, &path));
match fs::create_dir_all(&path) {
Ok(..) => Ok(path),
Err(e) => Err(e.into()),
}
}
/// Returns (but **does not create**) path to **app-specific** data
/// **subdirectory** for provided data type and subdirectory path.
///
/// The `path` parameter should be a valid relative path separated by
/// **forward slashes** (`/`).
///
/// A result of `Ok` means that we determined where the data SHOULD go, but
/// it DOES NOT guarantee that the directory actually exists. (See
/// [`app_dir`](fn.app_dir.html).)
pub fn get_app_dir(t: AppDataType, app: &AppInfo, path: &str) -> Result<PathBuf, AppDirsError> {
if app.author.len() == 0 || app.name.len() == 0 {
return Err(AppDirsError::InvalidAppData);
}
app_root(t, app).map(|mut root| {
for component in path.split("/").filter(|s| s.len() > 0) {
root.push(utils::sanitized(component));
}
root
})
}
/// Gets directory path for the data type and app info provided.
/// Creates (if necessary) and returns path to **app-specific** data
/// directory for provided data type.
///
/// 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.
/// If the directory structure does not exist, this function will recursively
/// create the full hierarchy. Therefore, a result of `Ok` guarantees that the
/// returned path exists.
pub fn app_root(t: AppDataType, app: &AppInfo) -> Result<PathBuf, AppDirsError> {
let path = try!(get_app_root(t, app));
match fs::create_dir_all(&path) {
Ok(..) => Ok(path),
Err(e) => Err(e.into()),
}
}
/// Returns (but **does not create**) path to **app-specific** data directory
/// for provided data type.
///
/// 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> {
/// A result of `Ok` means that we determined where the data SHOULD go, but
/// it DOES NOT guarantee that the directory actually exists. (See
/// [`app_root`](fn.app_root.html).)
pub fn get_app_root(t: AppDataType, app: &AppInfo) -> Result<PathBuf, AppDirsError> {
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
data_root(t).map(|mut root| {
if platform::USE_AUTHOR {
root.push(utils::sanitized(app.author));
}
root.push(utils::sanitized(app.name));
root
})
}
/// Gets path to root app data directory for the data type and app info
/// provided.
/// Creates (if necessary) and returns path to **top-level** data directory
/// for provided data type.
///
/// "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.
/// If the directory structure does not exist, this function will recursively
/// create the full hierarchy. Therefore, a result of `Ok` guarantees that the
/// returned path exists.
pub fn data_root(t: AppDataType) -> Result<PathBuf, AppDirsError> {
let path = try!(platform::get_app_dir(t));
match fs::create_dir_all(&path) {
Ok(..) => Ok(path),
Err(e) => Err(e.into()),
}
}
/// Returns (but **does not create**) path to **top-level** data directory for
/// provided data type.
///
/// 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> {
/// A result of `Ok` means that we determined where the data SHOULD go, but
/// it DOES NOT guarantee that the directory actually exists. (See
/// [`data_root`](fn.data_root.html).)
pub fn get_data_root(t: AppDataType) -> Result<PathBuf, AppDirsError> {
platform::get_app_dir(t)
}
......@@ -3,8 +3,10 @@ use AppDataType::*;
use std::env::home_dir;
use std::path::{Component, PathBuf};
pub fn get_app_dir(t: AppDataType) -> AppDirsResult<PathBuf> {
let dir_base: AppDirsResult<PathBuf> = if t.is_shared() {
pub const USE_AUTHOR: bool = false;
pub fn get_app_dir(t: AppDataType) -> Result<PathBuf, AppDirsError> {
let dir_base: Result<PathBuf, AppDirsError> = if t.is_shared() {
Ok(Component::RootDir.as_ref().into())
} else {
home_dir().ok_or_else(|| AppDirsError::NotSupported)
......
......@@ -4,7 +4,9 @@ use common::*;
use std::path::PathBuf;
use AppDataType::*;
pub fn get_app_dir(t: AppDataType) -> AppDirsResult<PathBuf> {
pub const USE_AUTHOR: bool = false;
pub fn get_app_dir(t: AppDataType) -> Result<PathBuf, AppDirsError> {
Xdg::new()
.ok()
.as_ref()
......
......@@ -3,7 +3,9 @@ use std::path::PathBuf;
use std::env;
use AppDataType::*;
pub fn get_app_dir(t: AppDataType) -> AppDirsResult<PathBuf> {
pub const USE_AUTHOR: bool = true;
pub fn get_app_dir(t: AppDataType) -> Result<PathBuf, AppDirsError> {
match t {
UserConfig | UserData | SharedConfig | SharedData => env::var("APPDATA"),
UserCache => env::var("LOCALAPPDATA"),
......
#![warn(missing_docs)]
//! Access platform-dependent canonical locations for app-specific data.
//! *Put your app's data in the right place on every platform*
//! # Usage
//! ```
//! extern crate app_dirs;
//! use app_dirs::*;
//!
//! const APP_INFO: AppInfo = AppInfo{name: "CoolApp", author: "SuperDev"};
//!
//! fn main () {
//! // Where should I store my app's per-user configuration data?
//! println!("{:?}", get_app_root(AppDataType::UserConfig, &APP_INFO));
//! // Windows: "%APPDATA%\SuperDev\CoolApp"
//! // (e.g.: "C:\Users\Rusty\AppData\Roaming\SuperDev\CoolApp")
//! // macOS: "$HOME/Library/Application Support/CoolApp"
//! // (e.g.: "/Users/Rusty/Library/Application Support/CoolApp")
//! // *nix: "$HOME/.config/CoolApp" (or "$XDG_CONFIG_HOME/CoolApp", if defined)
//! // (e.g.: "/home/rusty/.config/CoolApp")
//!
//! // How about nested cache data?
//! println!("{:?}", get_app_dir(AppDataType::UserCache, &APP_INFO, "cache/images"));
//! // Windows: "%LOCALAPPDATA%\SuperDev\CoolApp\cache\images"
//! // (e.g.: "C:\Users\Rusty\AppData\Local\SuperDev\CoolApp\cache\images")
//! // macOS: "$HOME/Library/Caches/CoolApp/cache/images"
//! // (e.g.: "/Users/Rusty/Library/Caches/CoolApp/cache/images")
//! // *nix: "$HOME/.cache/CoolApp/cache/images"
//! // (or "$XDG_CACHE_HOME/CoolApp/cache/images", if defined)
//! // (e.g.: "/home/rusty/.cache/CoolApp/cache/images")
//!
//! // Remove "get_" prefix to recursively create nonexistent directories:
//! // app_root(AppDataType::UserConfig, &APP_INFO)
//! // app_dir(AppDataType::UserCache, &APP_INFO, "cache/images")
//! }
//! ```
mod common;
pub use common::*;
mod imp;
pub use imp::*;
mod utils;
pub use utils::*;
#[cfg(test)]
mod tests {
......@@ -11,10 +45,16 @@ mod tests {
use AppDataType::*;
#[test]
fn it_works() {
let info = AppInfo::new("Awesome App", "Dedicated Dev");
let info = AppInfo {
name: "Awesome App".into(),
author: "Dedicated Dev".into(),
};
let path = "/subfolder!/with?/unicode/¡Olé!/";
let types = [UserConfig, UserData, UserCache, SharedData, SharedConfig];
for &t in types.iter() {
println!("{:?} = {:?}", t, get_app_dir_path(t, &info))
println!("{:?} data root = {:?}", t, get_data_root(t));
println!("{:?} app root = {:?}", t, get_app_root(t, &info));
println!("{:?} data dir = {:?}", t, get_app_dir(t, &info, &path));
}
}
}
/// Returns a cross-platform-filename-safe version of any string.
///
/// This is used internally to generate app data directories based on app
/// name/author. App developers can use it for consistency when dealing with
/// file system operations.
pub fn sanitized(component: &str) -> String {
let mut buf = String::with_capacity(component.len());
for c in component.chars() {
let n = c as u32;
let is_lower = 'a' as u32 <= n && n <= 'z' as u32;
let is_upper = 'A' as u32 <= n && n <= 'Z' as u32;
let is_letter = is_upper || is_lower;
let is_number = '0' as u32 <= n && n <= '9' as u32;
let is_space = c == ' ';
let is_hyphen = c == '-';
let is_underscore = c == '_';
let is_valid = is_letter || is_number || is_space || is_hyphen || is_underscore;
if is_valid {
buf.push(c);
} else {
buf.push_str(&format!(",{},", n));
}
}
buf
}
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