1use crate::hooks::HookEvent;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::PathBuf;
7
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
10#[serde(default)]
11pub struct PluginConfig {
12 pub enabled: bool,
14
15 pub hook_timeout_ms: u64,
17
18 pub paths: Vec<String>,
20
21 pub continue_on_error: bool,
23
24 #[serde(default)]
26 pub hooks: HookConfig,
27}
28
29impl PluginConfig {
30 pub fn new() -> Self {
32 PluginConfig {
33 enabled: true,
34 hook_timeout_ms: 5000,
35 paths: vec![],
36 continue_on_error: false,
37 hooks: HookConfig::default(),
38 }
39 }
40
41 pub fn hook_timeout(&self) -> std::time::Duration {
43 std::time::Duration::from_millis(self.hook_timeout_ms)
44 }
45
46 pub fn all_paths(&self, project_root: &std::path::Path) -> Vec<PathBuf> {
48 let mut paths = Vec::new();
49
50 paths.push(project_root.join(".hx").join("plugins"));
52
53 for path in &self.paths {
55 let expanded = shellexpand::tilde(path);
56 paths.push(PathBuf::from(expanded.as_ref()));
57 }
58
59 if let Some(config_dir) = dirs::config_dir() {
61 paths.push(config_dir.join("hx").join("plugins"));
62 }
63
64 paths
65 }
66
67 pub fn scripts_for_hook(&self, event: HookEvent) -> &[String] {
69 match event {
70 HookEvent::PreBuild => &self.hooks.pre_build,
71 HookEvent::PostBuild => &self.hooks.post_build,
72 HookEvent::PreTest => &self.hooks.pre_test,
73 HookEvent::PostTest => &self.hooks.post_test,
74 HookEvent::PreRun => &self.hooks.pre_run,
75 HookEvent::PostRun => &self.hooks.post_run,
76 HookEvent::PreClean => &self.hooks.pre_clean,
77 HookEvent::PostClean => &self.hooks.post_clean,
78 HookEvent::PreLock => &self.hooks.pre_lock,
79 HookEvent::PostLock => &self.hooks.post_lock,
80 HookEvent::Init => &self.hooks.init,
81 }
82 }
83}
84
85#[derive(Debug, Clone, Default, Serialize, Deserialize)]
87#[serde(default)]
88pub struct HookConfig {
89 pub pre_build: Vec<String>,
91
92 pub post_build: Vec<String>,
94
95 pub pre_test: Vec<String>,
97
98 pub post_test: Vec<String>,
100
101 pub pre_run: Vec<String>,
103
104 pub post_run: Vec<String>,
106
107 pub pre_clean: Vec<String>,
109
110 pub post_clean: Vec<String>,
112
113 pub pre_lock: Vec<String>,
115
116 pub post_lock: Vec<String>,
118
119 pub init: Vec<String>,
121}
122
123impl HookConfig {
124 pub fn has_any_hooks(&self) -> bool {
126 !self.pre_build.is_empty()
127 || !self.post_build.is_empty()
128 || !self.pre_test.is_empty()
129 || !self.post_test.is_empty()
130 || !self.pre_run.is_empty()
131 || !self.post_run.is_empty()
132 || !self.pre_clean.is_empty()
133 || !self.post_clean.is_empty()
134 || !self.pre_lock.is_empty()
135 || !self.post_lock.is_empty()
136 || !self.init.is_empty()
137 }
138
139 pub fn as_map(&self) -> HashMap<HookEvent, &[String]> {
141 let mut map = HashMap::new();
142 map.insert(HookEvent::PreBuild, self.pre_build.as_slice());
143 map.insert(HookEvent::PostBuild, self.post_build.as_slice());
144 map.insert(HookEvent::PreTest, self.pre_test.as_slice());
145 map.insert(HookEvent::PostTest, self.post_test.as_slice());
146 map.insert(HookEvent::PreRun, self.pre_run.as_slice());
147 map.insert(HookEvent::PostRun, self.post_run.as_slice());
148 map.insert(HookEvent::PreClean, self.pre_clean.as_slice());
149 map.insert(HookEvent::PostClean, self.post_clean.as_slice());
150 map.insert(HookEvent::PreLock, self.pre_lock.as_slice());
151 map.insert(HookEvent::PostLock, self.post_lock.as_slice());
152 map.insert(HookEvent::Init, self.init.as_slice());
153 map
154 }
155}
156
157mod dirs {
159 use std::path::PathBuf;
160
161 pub fn config_dir() -> Option<PathBuf> {
162 directories::BaseDirs::new().map(|dirs| dirs.config_dir().to_path_buf())
163 }
164}
165
166mod shellexpand {
168 use std::borrow::Cow;
169
170 pub fn tilde(path: &str) -> Cow<'_, str> {
171 if path.starts_with("~/")
172 && let Some(home) = directories::BaseDirs::new()
173 {
174 let home_str = home.home_dir().to_string_lossy();
175 return Cow::Owned(format!("{}{}", home_str, &path[1..]));
176 }
177 Cow::Borrowed(path)
178 }
179}
180
181impl From<hx_config::PluginConfig> for PluginConfig {
183 fn from(config: hx_config::PluginConfig) -> Self {
184 PluginConfig {
185 enabled: config.enabled,
186 hook_timeout_ms: config.hook_timeout_ms,
187 paths: config.paths,
188 continue_on_error: config.continue_on_error,
189 hooks: HookConfig::from(config.hooks),
190 }
191 }
192}
193
194impl From<hx_config::PluginHookConfig> for HookConfig {
195 fn from(hooks: hx_config::PluginHookConfig) -> Self {
196 HookConfig {
197 pre_build: hooks.pre_build,
198 post_build: hooks.post_build,
199 pre_test: hooks.pre_test,
200 post_test: hooks.post_test,
201 pre_run: hooks.pre_run,
202 post_run: hooks.post_run,
203 pre_clean: hooks.pre_clean,
204 post_clean: hooks.post_clean,
205 pre_lock: hooks.pre_lock,
206 post_lock: hooks.post_lock,
207 init: hooks.init,
208 }
209 }
210}