use std::env;
use std::path::{Path, PathBuf};
pub(crate) fn default_cache_dir() -> PathBuf {
#[cfg(windows)]
{
windows_cache_dir()
}
#[cfg(not(windows))]
{
unix_cache_dir()
}
}
#[cfg(windows)]
pub(crate) fn windows_cache_dir() -> PathBuf {
if let Some(dir) = dirs::data_local_dir() {
return dir.join("codebook").join("cache");
}
if let Some(dir) = dirs::data_dir() {
return dir.join("codebook").join("cache");
}
if let Some(home) = dirs::home_dir() {
return home
.join("AppData")
.join("Local")
.join("codebook")
.join("cache");
}
env::temp_dir().join("codebook").join("cache")
}
#[cfg(not(windows))]
pub(crate) fn unix_cache_dir() -> PathBuf {
if let Some(xdg_data_home) = env::var_os("XDG_DATA_HOME")
&& !xdg_data_home.is_empty()
{
return PathBuf::from(xdg_data_home).join("codebook").join("cache");
}
if let Some(home) = dirs::home_dir() {
return home
.join(".local")
.join("share")
.join("codebook")
.join("cache");
}
env::temp_dir().join("codebook").join("cache")
}
pub(crate) fn expand_tilde<P: AsRef<Path>>(path_user_input: P) -> Option<PathBuf> {
let p = path_user_input.as_ref();
let path = p.to_string_lossy();
if path == "~" {
return dirs::home_dir();
}
let rest = path.strip_prefix("~/");
#[cfg(windows)]
let rest = rest.or_else(|| path.strip_prefix("~\\"));
match rest {
Some(rest) => {
dirs::home_dir().map(|home| home.join(rest.trim_start_matches(std::path::is_separator)))
}
None => Some(p.to_path_buf()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(windows))]
use std::ffi::OsString;
#[cfg(not(windows))]
use std::sync::{Mutex, MutexGuard};
#[cfg(not(windows))]
static ENV_MUTEX: Mutex<()> = Mutex::new(());
#[cfg(not(windows))]
fn lock_env_and_set_xdg(value: Option<&str>) -> (MutexGuard<'static, ()>, Option<OsString>) {
let guard = ENV_MUTEX.lock().unwrap();
let previous = env::var_os("XDG_DATA_HOME");
unsafe {
match value {
Some(val) => env::set_var("XDG_DATA_HOME", val),
None => env::remove_var("XDG_DATA_HOME"),
}
}
(guard, previous)
}
#[cfg(not(windows))]
fn restore_xdg(previous: Option<OsString>) {
unsafe {
match previous {
Some(val) => env::set_var("XDG_DATA_HOME", val),
None => env::remove_var("XDG_DATA_HOME"),
}
}
}
#[cfg(not(windows))]
#[test]
fn unix_cache_dir_uses_xdg_data_home() {
let (guard, previous) = lock_env_and_set_xdg(Some("/tmp/codebook-xdg"));
let expected = PathBuf::from("/tmp/codebook-xdg")
.join("codebook")
.join("cache");
assert_eq!(unix_cache_dir(), expected);
restore_xdg(previous);
drop(guard);
}
#[cfg(not(windows))]
#[test]
fn unix_cache_dir_falls_back_to_home() {
let (guard, previous) = lock_env_and_set_xdg(Some(""));
let home = dirs::home_dir().expect("home directory must be available for the test");
let expected = home
.join(".local")
.join("share")
.join("codebook")
.join("cache");
assert_eq!(unix_cache_dir(), expected);
restore_xdg(previous);
drop(guard);
}
#[cfg(not(windows))]
#[test]
fn default_cache_dir_matches_unix_on_non_windows() {
let (guard, previous) = lock_env_and_set_xdg(Some("/tmp/codebook-xdg-default"));
assert_eq!(default_cache_dir(), unix_cache_dir());
restore_xdg(previous);
drop(guard);
}
#[test]
fn expand_tilde_resolves_home_directory() {
let home = dirs::home_dir().expect("home directory must be available for the test");
assert_eq!(expand_tilde("~"), Some(home));
}
#[test]
fn expand_tilde_resolves_unix_style_home_path() {
let home = dirs::home_dir().expect("home directory must be available for the test");
assert_eq!(
expand_tilde("~/dotfiles/codebook.toml"),
Some(home.join("dotfiles/codebook.toml"))
);
}
#[test]
fn expand_tilde_stays_within_home_on_extra_separators() {
let home = dirs::home_dir().expect("home directory must be available for the test");
assert_eq!(expand_tilde("~//etc/passwd"), Some(home.join("etc/passwd")));
}
#[cfg(windows)]
#[test]
fn expand_tilde_resolves_windows_style_home_path() {
let home = dirs::home_dir().expect("home directory must be available for the test");
assert_eq!(
expand_tilde(r"~\dotfiles\codebook.toml"),
Some(home.join(r"dotfiles\codebook.toml"))
);
}
#[cfg(not(windows))]
#[test]
fn expand_tilde_leaves_windows_style_path_unchanged_on_unix() {
let path = PathBuf::from(r"~\dotfiles\codebook.toml");
assert_eq!(expand_tilde(&path), Some(path));
}
#[test]
fn expand_tilde_leaves_other_paths_unchanged() {
let path = PathBuf::from("~user/codebook.toml");
assert_eq!(expand_tilde(&path), Some(path));
}
}