#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#[cfg(all(feature = "async", feature = "async-tokio"))]
compile_error!("Cannot enable both async and async-tokio features");
use std::{fmt, path::Path};
use directory::Directory;
use errors::{CreateError, RemoveError};
mod directory;
mod errors;
#[derive(Clone)]
pub struct Config {
pub cache: Directory,
pub config: Directory,
pub data: Directory,
}
impl Config {
fn _init(app_name: &str, os: &str) -> Config {
Config {
cache: Directory::new(match std::env::var("XDG_CACHE_HOME") {
Ok(dir) => Path::new(&dir).join(app_name),
Err(_) => match os {
"windows" => Path::new(&std::env::var("APPDATA").unwrap_or(".".to_string()))
.join(app_name)
.join("Cache"),
"macos" => Path::new(&std::env::var("HOME").unwrap_or(".".to_string()))
.join("Library")
.join("Caches")
.join(app_name),
_ => Path::new(&std::env::var("HOME").unwrap_or(".".to_string()))
.join(".cache")
.join(app_name),
},
}),
config: Directory::new(match std::env::var("XDG_CONFIG_HOME") {
Ok(dir) => Path::new(&dir).join(app_name),
Err(_) => match os {
"windows" => Path::new(&std::env::var("APPDATA").unwrap_or(".".to_string()))
.join(app_name)
.join("Config"),
"macos" => Path::new(&std::env::var("HOME").unwrap_or(".".to_string()))
.join("Library")
.join("Preferences")
.join(app_name),
_ => Path::new(&std::env::var("HOME").unwrap_or(".".to_string()))
.join(".config")
.join(app_name),
},
}),
data: Directory::new(match std::env::var("XDG_DATA_HOME") {
Ok(dir) => Path::new(&dir).join(app_name),
Err(_) => match os {
"windows" => Path::new(&std::env::var("APPDATA").unwrap_or(".".to_string()))
.join(app_name)
.join("Data"),
"macos" => Path::new(&std::env::var("HOME").unwrap_or(".".to_string()))
.join("Library")
.join(app_name),
_ => Path::new(&std::env::var("HOME").unwrap_or(".".to_string()))
.join(".local")
.join("share")
.join(app_name),
},
}),
}
}
pub fn new(app_name: &str) -> Config {
Config::_init(app_name, std::env::consts::OS)
}
pub fn create_all(&self) -> Result<(), CreateError> {
self.cache.create().map_err(CreateError::Cache)?;
self.config.create().map_err(CreateError::Config)?;
self.data.create().map_err(CreateError::Data)?;
Ok(())
}
pub fn remove_all(&self) -> Result<(), RemoveError> {
self.cache.remove().map_err(RemoveError::Cache)?;
self.config.remove().map_err(RemoveError::Config)?;
self.data.remove().map_err(RemoveError::Data)?;
Ok(())
}
}
#[cfg(any(feature = "async", feature = "async-tokio"))]
impl Config {
pub async fn create_all_async(&self) -> Result<(), CreateError> {
self.cache
.create_async()
.await
.map_err(CreateError::Cache)?;
self.config
.create_async()
.await
.map_err(CreateError::Config)?;
self.data.create_async().await.map_err(CreateError::Data)?;
Ok(())
}
pub async fn remove_all_async(&self) -> Result<(), RemoveError> {
self.cache
.remove_async()
.await
.map_err(RemoveError::Cache)?;
self.config
.remove_async()
.await
.map_err(RemoveError::Config)?;
self.data.remove_async().await.map_err(RemoveError::Data)?;
Ok(())
}
}
impl fmt::Debug for Config {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Config {{ cache_dir: {:?}, config_dir: {:?}, data_dir: {:?} }}",
self.cache.path, self.config.path, self.data.path
)
}
}
#[cfg(test)]
mod tests {
use rand::{distributions::Alphanumeric, Rng};
use crate::Config;
macro_rules! path {
($path:expr) => {
$path.replace("/", &std::path::MAIN_SEPARATOR.to_string())
};
}
fn get_random_string() -> String {
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(12)
.map(char::from)
.collect()
}
fn with_windows_config<F>(closure: F)
where
F: FnOnce(Config) + Copy,
{
temp_env::with_vars(
[
("APPDATA", Some(&path!("/appdata"))),
("XDG_CONFIG_HOME", None),
("XDG_CACHE_HOME", None),
("XDG_DATA_HOME", None),
],
|| closure(Config::_init("app-name", "windows")),
);
}
fn with_macos_config<F>(closure: F)
where
F: FnOnce(Config) + Copy,
{
temp_env::with_vars(
[
("HOME", Some(&path!("/home/user"))),
("XDG_CONFIG_HOME", None),
("XDG_CACHE_HOME", None),
("XDG_DATA_HOME", None),
],
|| closure(Config::_init("app-name", "macos")),
);
}
fn with_linux_config<F>(closure: F)
where
F: FnOnce(Config) + Copy,
{
temp_env::with_vars(
[
("HOME", Some(&path!("/home/user"))),
("XDG_CONFIG_HOME", None),
("XDG_CACHE_HOME", None),
("XDG_DATA_HOME", None),
],
|| closure(Config::_init("app-name", "linux")),
);
}
#[test]
fn it_respects_xdg_cache_home() {
temp_env::with_vars([("XDG_CACHE_HOME", Some(path!("/tmp/cache")))], || {
let config = Config::_init("app-name", "any");
assert_eq!(config.cache.to_string(), path!("/tmp/cache/app-name"));
});
}
#[test]
fn it_uses_expected_windows_cache_path() {
with_windows_config(|c| {
assert_eq!(c.cache.to_string(), path!("/appdata/app-name/Cache"));
});
}
#[test]
fn it_uses_expected_macos_cache_path() {
with_macos_config(|c| {
assert_eq!(
c.cache.to_string(),
path!("/home/user/Library/Caches/app-name")
);
});
}
#[test]
fn it_uses_expected_linux_cache_path() {
with_linux_config(|c| {
assert_eq!(c.cache.to_string(), path!("/home/user/.cache/app-name"));
});
}
#[test]
fn it_respects_xdg_config_home() {
temp_env::with_vars([("XDG_CONFIG_HOME", Some(path!("/tmp/config")))], || {
let config = Config::_init("app-name", "any");
assert_eq!(config.config.to_string(), path!("/tmp/config/app-name"));
});
}
#[test]
fn it_uses_expected_windows_config_path() {
with_windows_config(|c| {
assert_eq!(c.config.to_string(), path!("/appdata/app-name/Config"));
});
}
#[test]
fn it_uses_expected_macos_config_path() {
with_macos_config(|c| {
assert_eq!(
c.config.to_string(),
path!("/home/user/Library/Preferences/app-name")
);
});
}
#[test]
fn it_uses_expected_linux_config_path() {
with_linux_config(|c| {
assert_eq!(c.config.to_string(), path!("/home/user/.config/app-name"));
});
}
#[test]
fn it_respects_xdg_data_home() {
temp_env::with_vars([("XDG_DATA_HOME", Some(path!("/tmp/data")))], || {
let config = Config::_init("app-name", "any");
assert_eq!(config.data.to_string(), path!("/tmp/data/app-name"));
});
}
#[test]
fn it_uses_expected_windows_data_path() {
with_windows_config(|c| {
assert_eq!(c.data.to_string(), path!("/appdata/app-name/Data"));
});
}
#[test]
fn it_uses_expected_macos_data_path() {
with_macos_config(|c| {
assert_eq!(c.data.to_string(), path!("/home/user/Library/app-name"));
});
}
#[test]
fn it_uses_expected_linux_data_path() {
with_linux_config(|c| {
assert_eq!(
c.data.to_string(),
path!("/home/user/.local/share/app-name")
);
});
}
#[test]
#[cfg(feature = "sync")]
fn it_creates_and_removes_directories() {
temp_env::with_vars_unset(
["XDG_CACHE_HOME", "XDG_CONFIG_HOME", "XDG_DATA_HOME"],
|| {
let config = Config::new(&get_random_string());
let create_result = config.create_all();
assert!(create_result.is_ok());
let remove_result = config.remove_all();
assert!(remove_result.is_ok());
},
);
}
#[test]
#[cfg(any(feature = "async", feature = "async-tokio"))]
fn it_creates_and_removes_directories_async() {
temp_env::with_vars_unset(
["XDG_CACHE_HOME", "XDG_CONFIG_HOME", "XDG_DATA_HOME"],
|| {
let config = Config::new(&get_random_string());
let create_result = tokio_test::block_on(config.create_all_async());
assert!(create_result.is_ok());
let remove_result = tokio_test::block_on(config.remove_all_async());
assert!(remove_result.is_ok());
},
);
}
}