//! XDG Base Directory support for Tools ecosystem //! //! Provides standard directories for tool-specific configuration and data storage. //! Follows XDG Base Directory Specification on Linux and macOS. //! //! # Examples //! ```no_run //! use tools_shared::tool_data_dir; //! //! let data_dir = tool_data_dir("my-tool"); //! println!("Data directory: {}", data_dir.display()); //! // Outputs: ~/.local/share/tools/my-tool or $XDG_DATA_HOME/tools/my-tool //! ``` use std::path::PathBuf; /// Get XDG data directory for a tool /// /// Returns: `$XDG_DATA_HOME/tools/{tool_name}/` or `~/.local/share/tools/{tool_name}/` /// /// # Arguments /// * `tool_name` - Name of the tool (e.g., "project-lifecycle") /// /// # Panics /// Panics if home directory cannot be determined pub fn tool_data_dir(tool_name: &str) -> PathBuf { dirs::data_dir() .map(|d| d.join("tools").join(tool_name)) .unwrap_or_else(|| { let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string()); PathBuf::from(home) .join(".local/share/tools") .join(tool_name) }) } /// Get XDG config directory for a tool /// /// Returns: `$XDG_CONFIG_HOME/tools/{tool_name}/` or `~/.config/tools/{tool_name}/` /// /// # Arguments /// * `tool_name` - Name of the tool (e.g., "project-lifecycle") /// /// # Panics /// Panics if home directory cannot be determined pub fn tool_config_dir(tool_name: &str) -> PathBuf { dirs::config_dir() .map(|d| d.join("tools").join(tool_name)) .unwrap_or_else(|| { let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string()); PathBuf::from(home).join(".config/tools").join(tool_name) }) } /// Get XDG cache directory for a tool /// /// Returns: `$XDG_CACHE_HOME/tools/{tool_name}/` or `~/.cache/tools/{tool_name}/` /// /// # Arguments /// * `tool_name` - Name of the tool (e.g., "project-lifecycle") /// /// # Panics /// Panics if home directory cannot be determined pub fn tool_cache_dir(tool_name: &str) -> PathBuf { dirs::cache_dir() .map(|d| d.join("tools").join(tool_name)) .unwrap_or_else(|| { let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string()); PathBuf::from(home).join(".cache/tools").join(tool_name) }) } /// Ensure directory exists, creating it if necessary /// /// # Arguments /// * `path` - Path to directory /// /// # Errors /// Returns error if directory cannot be created /// /// # Examples /// ```no_run /// use tools_shared::{tool_data_dir, ensure_dir}; /// /// let dir = tool_data_dir("my-tool"); /// ensure_dir(&dir).expect("Failed to create directory"); /// ``` pub fn ensure_dir(path: &PathBuf) -> std::io::Result { if !path.exists() { std::fs::create_dir_all(path)?; } Ok(path.clone()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_tool_data_dir() { let data_dir = tool_data_dir("test-tool"); assert!(data_dir.to_string_lossy().contains("test-tool")); assert!( data_dir.to_string_lossy().contains("tools") || data_dir.to_string_lossy().contains(".local") ); } #[test] fn test_tool_config_dir() { let config_dir = tool_config_dir("test-tool"); assert!(config_dir.to_string_lossy().contains("test-tool")); assert!( config_dir.to_string_lossy().contains("tools") || config_dir.to_string_lossy().contains(".config") ); } #[test] fn test_tool_cache_dir() { let cache_dir = tool_cache_dir("test-tool"); assert!(cache_dir.to_string_lossy().contains("test-tool")); assert!( cache_dir.to_string_lossy().contains("tools") || cache_dir.to_string_lossy().contains(".cache") ); } #[test] fn test_ensure_dir_creates_directory() { use tempfile::TempDir; let temp = TempDir::new().expect("Failed to create temp dir"); let test_dir = temp.path().join("test").join("nested").join("dir"); ensure_dir(&test_dir).expect("Failed to ensure dir"); assert!(test_dir.exists()); } #[test] fn test_ensure_dir_existing() { use tempfile::TempDir; let temp = TempDir::new().expect("Failed to create temp dir"); let test_dir = temp.path().join("test"); std::fs::create_dir(&test_dir).expect("Failed to create dir"); ensure_dir(&test_dir).expect("Failed to ensure dir"); assert!(test_dir.exists()); } }