diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..8455033 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,181 @@ +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; +use crate::cli::SyncBackend; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Dotfile { + pub path: PathBuf, + pub content_hash: Option, + pub permissions: Option, + pub platform: Option, + pub template: Option, + #[serde(default)] + pub excludes: Vec, +} + +impl Default for Dotfile { + fn default() -> Self { + Self { + path: PathBuf::new(), + content_hash: None, + permissions: None, + platform: None, + template: Some(false), + excludes: Vec::new(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SyncConfig { + pub backend: Option, + pub remote: Option, + pub branch: String, + pub path: PathBuf, + #[serde(default)] + pub excludes: Vec, +} + +impl Default for SyncConfig { + fn default() -> Self { + Self { + backend: Some(SyncBackend::Git), + remote: None, + branch: "main".to_string(), + path: PathBuf::from("dotfiles"), + excludes: Vec::new(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MergeConfig { + pub strategy: Option, + pub keep_backup: bool, + pub auto_resolve: bool, +} + +impl Default for MergeConfig { + fn default() -> Self { + Self { + strategy: Some("ask".to_string()), + keep_backup: true, + auto_resolve: false, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DetectConfig { + pub patterns: Vec, + pub exclude_patterns: Vec, + pub include_hidden: bool, + pub scan_depth: usize, +} + +impl Default for DetectConfig { + fn default() -> Self { + Self { + patterns: vec![ + ".*".to_string(), + ".config/*".to_string(), + ".local/*".to_string(), + ], + exclude_patterns: vec![ + ".git".to_string(), + ".gitignore".to_string(), + "node_modules".to_string(), + ".cache".to_string(), + ], + include_hidden: true, + scan_depth: 5, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BackupConfig { + pub directory: PathBuf, + pub max_backups: usize, + pub timestamp_format: String, +} + +impl Default for BackupConfig { + fn default() -> Self { + Self { + directory: dirs::home_dir() + .map(|p| p.join(".dotmigrate/backups")) + .unwrap_or_else(|| PathBuf::from(".dotmigrate/backups")), + max_backups: 10, + timestamp_format: "%Y-%m-%d_%H-%M-%S".to_string(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + pub version: String, + pub sync: SyncConfig, + pub merge: MergeConfig, + pub detect: DetectConfig, + pub backup: BackupConfig, + #[serde(default)] + pub dotfiles: Vec, +} + +impl Default for Config { + fn default() -> Self { + Self { + version: "1.0".to_string(), + sync: SyncConfig::default(), + merge: MergeConfig::default(), + detect: DetectConfig::default(), + backup: BackupConfig::default(), + dotfiles: Vec::new(), + } + } +} + +impl Config { + pub fn default_for_backend(backend: SyncBackend) -> Self { + let mut config = Self::default(); + config.sync.backend = Some(backend); + config + } + + pub fn default_path() -> Result { + dirs::home_dir() + .map(|p| p.join(".dotmigrate/config.yml")) + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "Home directory not found")) + } + + pub fn load(path: &Path) -> Result { + let content = std::fs::read_to_string(path)?; + let config: Config = serde_yaml::from_str(&content)?; + Ok(config) + } + + pub fn save(&self, path: &Path) -> Result<(), anyhow::Error> { + let content = serde_yaml::to_string(self)?; + std::fs::write(path, content)?; + Ok(()) + } + + pub fn find_config() -> Option { + let current_dir = std::env::current_dir().ok()?; + let home_dir = dirs::home_dir()?; + + for dir in [current_dir, home_dir] { + let config_path = dir.join(".dotmigrate/config.yml"); + if config_path.exists() { + return Some(config_path); + } + let alt_path = dir.join("dotmigrate.yml"); + if alt_path.exists() { + return Some(alt_path); + } + } + + None + } +}