robin/config/
robin_config.rs

1use std::collections::HashMap;
2use std::fs;
3use std::path::Path;
4use serde::{Deserialize, Serialize};
5use anyhow::{Context, Result};
6use serde_json::Value;
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!("No .robin.json found. Run 'robin init' first"));
19        }
20
21        let content = fs::read_to_string(path)
22            .with_context(|| format!("Failed to read config file: {}", path.display()))?;
23        
24        let mut config: Self = serde_json::from_str(&content)
25            .with_context(|| "The .robin.json file exists but contains malformed JSON. Please check the file format.")?;
26
27        // Load and merge included configs
28        if !config.include.is_empty() {
29            let base_dir = path.parent().unwrap_or_else(|| Path::new("."));
30            config = config.merge_includes(base_dir)?;
31        }
32
33        Ok(config)
34    }
35
36    fn merge_includes(&self, base_dir: &Path) -> Result<Self> {
37        let mut merged_scripts = self.scripts.clone();
38
39        for include_path in &self.include {
40            let full_path = base_dir.join(include_path);
41            let included_config = Self::load(&full_path)
42                .with_context(|| format!("Failed to load included config: {}", include_path))?;
43            
44            // Merge scripts from included config
45            for (key, value) in included_config.scripts {
46                if !merged_scripts.contains_key(&key) {
47                    merged_scripts.insert(key, value);
48                }
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 = serde_json::to_string_pretty(self)
60            .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("deploy staging".to_string(), Value::String("echo 'ruby deploy tool --staging'".to_string()));
72        scripts.insert("deploy production".to_string(), Value::String("...".to_string()));
73        scripts.insert("release beta".to_string(), Value::String("...".to_string()));
74        scripts.insert("release alpha".to_string(), Value::String("...".to_string()));
75        scripts.insert("release dev".to_string(), Value::String("...".to_string()));
76
77        Self { 
78            include: Vec::new(),
79            scripts 
80        }
81    }
82}