use std::path::PathBuf;
#[cfg(not(feature = "alt-folder-name"))]
const PROJECT_FOLDER_NAME: &str = "brainwires-rag";
#[cfg(feature = "alt-folder-name")]
const PROJECT_FOLDER_NAME: &str = "brainwires";
pub struct PlatformPaths;
impl PlatformPaths {
pub fn data_dir() -> PathBuf {
if cfg!(target_os = "windows") {
std::env::var("LOCALAPPDATA")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("."))
} else if cfg!(target_os = "macos") {
std::env::var("HOME")
.map(|home| PathBuf::from(home).join("Library/Application Support"))
.unwrap_or_else(|_| PathBuf::from("."))
} else {
std::env::var("XDG_DATA_HOME")
.map(PathBuf::from)
.or_else(|_| {
std::env::var("HOME").map(|home| PathBuf::from(home).join(".local/share"))
})
.unwrap_or_else(|_| PathBuf::from("."))
}
}
pub fn cache_dir() -> PathBuf {
if cfg!(target_os = "windows") {
std::env::var("LOCALAPPDATA")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("."))
} else if cfg!(target_os = "macos") {
std::env::var("HOME")
.map(|home| PathBuf::from(home).join("Library/Caches"))
.unwrap_or_else(|_| PathBuf::from("."))
} else {
std::env::var("XDG_CACHE_HOME")
.map(PathBuf::from)
.or_else(|_| std::env::var("HOME").map(|home| PathBuf::from(home).join(".cache")))
.unwrap_or_else(|_| PathBuf::from("."))
}
}
pub fn config_dir() -> PathBuf {
if cfg!(target_os = "windows") {
std::env::var("APPDATA")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("."))
} else if cfg!(target_os = "macos") {
std::env::var("HOME")
.map(|home| PathBuf::from(home).join("Library/Application Support"))
.unwrap_or_else(|_| PathBuf::from("."))
} else {
std::env::var("XDG_CONFIG_HOME")
.map(PathBuf::from)
.or_else(|_| std::env::var("HOME").map(|home| PathBuf::from(home).join(".config")))
.unwrap_or_else(|_| PathBuf::from("."))
}
}
pub fn project_folder_name() -> &'static str {
PROJECT_FOLDER_NAME
}
pub fn project_data_dir() -> PathBuf {
Self::data_dir().join(PROJECT_FOLDER_NAME)
}
pub fn project_cache_dir() -> PathBuf {
Self::cache_dir().join(PROJECT_FOLDER_NAME)
}
pub fn project_config_dir() -> PathBuf {
Self::config_dir().join(PROJECT_FOLDER_NAME)
}
pub fn default_lancedb_path() -> PathBuf {
Self::project_data_dir().join("lancedb")
}
pub fn default_hash_cache_path() -> PathBuf {
Self::project_cache_dir().join("hash_cache.json")
}
pub fn default_git_cache_path() -> PathBuf {
Self::project_cache_dir().join("git_cache.json")
}
#[cfg(feature = "native")]
pub fn default_fastembed_cache_path() -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".brainwires")
.join("cache")
.join("fastembed")
}
#[cfg(not(feature = "native"))]
pub fn default_fastembed_cache_path() -> PathBuf {
PathBuf::from(".brainwires").join("cache").join("fastembed")
}
pub fn default_config_path() -> PathBuf {
Self::project_config_dir().join("config.toml")
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
#[test]
fn test_data_dir_not_empty() {
let dir = PlatformPaths::data_dir();
assert!(!dir.as_os_str().is_empty());
}
#[test]
fn test_cache_dir_not_empty() {
let dir = PlatformPaths::cache_dir();
assert!(!dir.as_os_str().is_empty());
}
#[test]
fn test_config_dir_not_empty() {
let dir = PlatformPaths::config_dir();
assert!(!dir.as_os_str().is_empty());
}
#[test]
fn test_project_paths_contain_project_name() {
let data_dir = PlatformPaths::project_data_dir();
let cache_dir = PlatformPaths::project_cache_dir();
let config_dir = PlatformPaths::project_config_dir();
assert!(data_dir.to_string_lossy().contains("brainwires-rag"));
assert!(cache_dir.to_string_lossy().contains("brainwires-rag"));
assert!(config_dir.to_string_lossy().contains("brainwires-rag"));
}
#[test]
fn test_default_lancedb_path() {
let path = PlatformPaths::default_lancedb_path();
assert!(path.to_string_lossy().contains("brainwires-rag"));
assert!(path.to_string_lossy().contains("lancedb"));
}
#[test]
fn test_default_hash_cache_path() {
let path = PlatformPaths::default_hash_cache_path();
assert!(path.to_string_lossy().contains("brainwires-rag"));
assert!(path.to_string_lossy().contains("hash_cache.json"));
}
#[test]
fn test_default_git_cache_path() {
let path = PlatformPaths::default_git_cache_path();
assert!(path.to_string_lossy().contains("brainwires-rag"));
assert!(path.to_string_lossy().contains("git_cache.json"));
}
#[test]
fn test_default_config_path() {
let path = PlatformPaths::default_config_path();
assert!(path.to_string_lossy().contains("brainwires-rag"));
assert!(path.to_string_lossy().contains("config.toml"));
}
#[test]
fn test_paths_are_absolute_or_relative() {
let data_dir = PlatformPaths::data_dir();
assert!(data_dir.is_absolute() || data_dir == PathBuf::from("."));
}
#[test]
#[cfg(target_os = "linux")]
fn test_data_dir_with_xdg_data_home() {
let original = env::var("XDG_DATA_HOME").ok();
unsafe {
env::set_var("XDG_DATA_HOME", "/custom/data");
}
let dir = PlatformPaths::data_dir();
assert_eq!(dir, PathBuf::from("/custom/data"));
unsafe {
match original {
Some(val) => env::set_var("XDG_DATA_HOME", val),
None => env::remove_var("XDG_DATA_HOME"),
}
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_data_dir_fallback_to_home() {
let xdg_original = env::var("XDG_DATA_HOME").ok();
let home_original = env::var("HOME").ok();
unsafe {
env::remove_var("XDG_DATA_HOME");
env::set_var("HOME", "/home/testuser");
}
let dir = PlatformPaths::data_dir();
assert_eq!(dir, PathBuf::from("/home/testuser/.local/share"));
unsafe {
match xdg_original {
Some(val) => env::set_var("XDG_DATA_HOME", val),
None => env::remove_var("XDG_DATA_HOME"),
}
match home_original {
Some(val) => env::set_var("HOME", val),
None => env::remove_var("HOME"),
}
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_cache_dir_with_xdg_cache_home() {
let original = env::var("XDG_CACHE_HOME").ok();
unsafe {
env::set_var("XDG_CACHE_HOME", "/custom/cache");
}
let dir = PlatformPaths::cache_dir();
assert_eq!(dir, PathBuf::from("/custom/cache"));
unsafe {
match original {
Some(val) => env::set_var("XDG_CACHE_HOME", val),
None => env::remove_var("XDG_CACHE_HOME"),
}
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_cache_dir_fallback_to_home() {
let xdg_original = env::var("XDG_CACHE_HOME").ok();
let home_original = env::var("HOME").ok();
unsafe {
env::remove_var("XDG_CACHE_HOME");
env::set_var("HOME", "/home/testuser");
}
let dir = PlatformPaths::cache_dir();
assert_eq!(dir, PathBuf::from("/home/testuser/.cache"));
unsafe {
match xdg_original {
Some(val) => env::set_var("XDG_CACHE_HOME", val),
None => env::remove_var("XDG_CACHE_HOME"),
}
match home_original {
Some(val) => env::set_var("HOME", val),
None => env::remove_var("HOME"),
}
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_config_dir_with_xdg_config_home() {
let original = env::var("XDG_CONFIG_HOME").ok();
unsafe {
env::set_var("XDG_CONFIG_HOME", "/custom/config");
}
let dir = PlatformPaths::config_dir();
assert_eq!(dir, PathBuf::from("/custom/config"));
unsafe {
match original {
Some(val) => env::set_var("XDG_CONFIG_HOME", val),
None => env::remove_var("XDG_CONFIG_HOME"),
}
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_config_dir_fallback_to_home() {
let xdg_original = env::var("XDG_CONFIG_HOME").ok();
let home_original = env::var("HOME").ok();
unsafe {
env::remove_var("XDG_CONFIG_HOME");
env::set_var("HOME", "/home/testuser");
}
let dir = PlatformPaths::config_dir();
assert_eq!(dir, PathBuf::from("/home/testuser/.config"));
unsafe {
match xdg_original {
Some(val) => env::set_var("XDG_CONFIG_HOME", val),
None => env::remove_var("XDG_CONFIG_HOME"),
}
match home_original {
Some(val) => env::set_var("HOME", val),
None => env::remove_var("HOME"),
}
}
}
#[test]
fn test_all_project_dirs_are_subdirectories() {
let data_dir = PlatformPaths::data_dir();
let project_data = PlatformPaths::project_data_dir();
assert!(
project_data.starts_with(&data_dir) || data_dir == PathBuf::from("."),
"project_data_dir should be subdirectory of data_dir"
);
let cache_dir = PlatformPaths::cache_dir();
let project_cache = PlatformPaths::project_cache_dir();
assert!(
project_cache.starts_with(&cache_dir) || cache_dir == PathBuf::from("."),
"project_cache_dir should be subdirectory of cache_dir"
);
}
#[test]
fn test_specific_file_paths() {
let lancedb_path = PlatformPaths::default_lancedb_path();
let hash_cache_path = PlatformPaths::default_hash_cache_path();
let git_cache_path = PlatformPaths::default_git_cache_path();
let config_path = PlatformPaths::default_config_path();
for path in [
&lancedb_path,
&hash_cache_path,
&git_cache_path,
&config_path,
] {
assert!(
path.to_string_lossy().contains("brainwires-rag"),
"Path {:?} should contain 'brainwires-rag'",
path
);
}
assert!(lancedb_path.ends_with("lancedb"));
assert!(hash_cache_path.ends_with("hash_cache.json"));
assert!(git_cache_path.ends_with("git_cache.json"));
assert!(config_path.ends_with("config.toml"));
}
}