Skip to main content

robin/config/
robin_config.rs

1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::collections::HashMap;
5use std::fs;
6use std::path::Path;
7
8#[derive(Debug, Serialize, Deserialize)]
9pub struct RobinConfig {
10    #[serde(default)]
11    pub include: Vec<String>,
12    pub scripts: HashMap<String, Value>,
13}
14
15impl RobinConfig {
16    pub fn load(path: &Path) -> Result<Self> {
17        if !path.exists() {
18            return Err(anyhow::anyhow!(
19                "No .robin.json found. Run 'robin init' first"
20            ));
21        }
22
23        let content = fs::read_to_string(path)
24            .with_context(|| format!("Failed to read config file: {}", path.display()))?;
25
26        let mut config: Self = serde_json::from_str(&content)
27            .with_context(|| "The .robin.json file exists but contains malformed JSON. Please check the file format.")?;
28
29        // Load and merge included configs
30        if !config.include.is_empty() {
31            let base_dir = path.parent().unwrap_or_else(|| Path::new("."));
32            config = config.merge_includes(base_dir)?;
33        }
34
35        Ok(config)
36    }
37
38    fn merge_includes(&self, base_dir: &Path) -> Result<Self> {
39        let mut merged_scripts = self.scripts.clone();
40
41        for include_path in &self.include {
42            let full_path = base_dir.join(include_path);
43            let included_config = Self::load(&full_path)
44                .with_context(|| format!("Failed to load included config: {}", include_path))?;
45
46            // Merge scripts from included config; existing keys take precedence.
47            for (key, value) in included_config.scripts {
48                merged_scripts.entry(key).or_insert(value);
49            }
50        }
51
52        Ok(Self {
53            include: self.include.clone(),
54            scripts: merged_scripts,
55        })
56    }
57
58    pub fn save(&self, path: &Path) -> Result<()> {
59        let content =
60            serde_json::to_string_pretty(self).with_context(|| "Failed to serialize config")?;
61
62        fs::write(path, content)
63            .with_context(|| format!("Failed to write config to: {}", path.display()))?;
64
65        Ok(())
66    }
67
68    pub fn create_template() -> Self {
69        let mut scripts = HashMap::new();
70        scripts.insert("clean".to_string(), Value::String("...".to_string()));
71        scripts.insert(
72            "deploy staging".to_string(),
73            Value::String("echo 'ruby deploy tool --staging'".to_string()),
74        );
75        scripts.insert(
76            "deploy production".to_string(),
77            Value::String("...".to_string()),
78        );
79        scripts.insert("release beta".to_string(), Value::String("...".to_string()));
80        scripts.insert(
81            "release alpha".to_string(),
82            Value::String("...".to_string()),
83        );
84        scripts.insert("release dev".to_string(), Value::String("...".to_string()));
85
86        Self {
87            include: Vec::new(),
88            scripts,
89        }
90    }
91}