Skip to main content

codei_config/
plugins.rs

1use std::fs;
2use std::path::Path;
3
4use serde::{Deserialize, Serialize};
5
6use crate::error::ConfigError;
7
8#[derive(Debug, Clone, Serialize, Deserialize, Default)]
9pub struct PluginsConfig {
10    #[serde(default)]
11    pub hooks: Vec<HookConfig>,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct HookConfig {
16    pub event: HookEvent,
17    pub command: String,
18}
19
20#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
21#[serde(rename_all = "snake_case")]
22pub enum HookEvent {
23    BeforeTurn,
24    AfterTurn,
25}
26
27pub fn load_plugins(project_root: &Path) -> PluginsConfig {
28    let path = project_root.join(".codei").join("hooks.toml");
29    if !path.is_file() {
30        return PluginsConfig::default();
31    }
32    match fs::read_to_string(&path) {
33        Ok(text) => toml::from_str(&text).unwrap_or_default(),
34        Err(_) => PluginsConfig::default(),
35    }
36}
37
38pub fn run_hook(hook: &HookConfig, cwd: &Path, env: &[(&str, String)]) -> Result<(), ConfigError> {
39    let mut cmd = std::process::Command::new("sh");
40    cmd.arg("-lc").arg(&hook.command).current_dir(cwd);
41    for (key, value) in env {
42        cmd.env(key, value);
43    }
44    let status = cmd.status().map_err(|source| ConfigError::Read {
45        path: cwd.to_path_buf(),
46        source,
47    })?;
48    if !status.success() {
49        return Err(ConfigError::HookFailed {
50            command: hook.command.clone(),
51            code: status.code(),
52        });
53    }
54    Ok(())
55}
56
57pub fn run_hooks(
58    config: &PluginsConfig,
59    event: HookEvent,
60    cwd: &Path,
61    env: &[(&str, String)],
62) -> Result<(), ConfigError> {
63    for hook in config.hooks.iter().filter(|hook| hook.event == event) {
64        run_hook(hook, cwd, env)?;
65    }
66    Ok(())
67}