dodot_lib/handlers/
mod.rs1pub mod homebrew;
11pub mod install;
12pub mod path;
13pub mod shell;
14pub mod symlink;
15
16use std::collections::HashMap;
17use std::path::Path;
18
19use serde::Serialize;
20
21use crate::datastore::DataStore;
22use crate::fs::Fs;
23use crate::operations::HandlerIntent;
24use crate::paths::Pather;
25use crate::rules::RuleMatch;
26use crate::Result;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
34pub enum HandlerCategory {
35 Configuration,
36 CodeExecution,
37}
38
39#[derive(Debug, Clone, Serialize)]
41pub struct HandlerStatus {
42 pub file: String,
44 pub handler: String,
46 pub deployed: bool,
48 pub message: String,
50}
51
52pub trait Handler: Send + Sync {
63 fn name(&self) -> &str;
65
66 fn category(&self) -> HandlerCategory;
68
69 fn to_intents(
74 &self,
75 matches: &[RuleMatch],
76 config: &HandlerConfig,
77 paths: &dyn Pather,
78 ) -> Result<Vec<HandlerIntent>>;
79
80 fn check_status(
82 &self,
83 file: &Path,
84 pack: &str,
85 datastore: &dyn DataStore,
86 ) -> Result<HandlerStatus>;
87}
88
89#[derive(Debug, Clone, Default, Serialize)]
94pub struct HandlerConfig {
95 pub force_home: Vec<String>,
97 pub protected_paths: Vec<String>,
99 #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
102 pub targets: std::collections::HashMap<String, String>,
103}
104
105pub const HANDLER_SYMLINK: &str = "symlink";
107pub const HANDLER_SHELL: &str = "shell";
108pub const HANDLER_PATH: &str = "path";
109pub const HANDLER_INSTALL: &str = "install";
110pub const HANDLER_HOMEBREW: &str = "homebrew";
111
112pub fn create_registry(fs: &dyn Fs) -> HashMap<String, Box<dyn Handler + '_>> {
118 let mut registry: HashMap<String, Box<dyn Handler>> = HashMap::new();
119 registry.insert(HANDLER_SYMLINK.into(), Box::new(symlink::SymlinkHandler));
120 registry.insert(HANDLER_SHELL.into(), Box::new(shell::ShellHandler));
121 registry.insert(HANDLER_PATH.into(), Box::new(path::PathHandler));
122 registry.insert(
123 HANDLER_INSTALL.into(),
124 Box::new(install::InstallHandler::new(fs)),
125 );
126 registry.insert(
127 HANDLER_HOMEBREW.into(),
128 Box::new(homebrew::HomebrewHandler::new(fs)),
129 );
130 registry
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[allow(dead_code)]
139 fn assert_object_safe(_: &dyn Handler) {}
140
141 #[allow(dead_code)]
142 fn assert_boxable(_: Box<dyn Handler>) {}
143
144 #[test]
145 fn handler_category_eq() {
146 assert_eq!(
147 HandlerCategory::Configuration,
148 HandlerCategory::Configuration
149 );
150 assert_ne!(
151 HandlerCategory::Configuration,
152 HandlerCategory::CodeExecution
153 );
154 }
155
156 #[test]
157 fn handler_status_serializes() {
158 let status = HandlerStatus {
159 file: "vimrc".into(),
160 handler: "symlink".into(),
161 deployed: true,
162 message: "linked to ~/.vimrc".into(),
163 };
164 let json = serde_json::to_string(&status).unwrap();
165 assert!(json.contains("deployed"));
166 assert!(json.contains("linked to ~/.vimrc"));
167 }
168
169 #[test]
170 fn handler_config_default() {
171 let config = HandlerConfig::default();
172 assert!(config.force_home.is_empty());
173 assert!(config.protected_paths.is_empty());
174 }
175}