nabla_cli/cli/
config.rs

1use anyhow::{Result, anyhow};
2use clap::Subcommand;
3use home;
4use serde::{Deserialize, Serialize};
5use std::fs;
6
7#[derive(Serialize, Deserialize, Default)]
8pub struct ConfigStore {
9    #[serde(default)]
10    settings: std::collections::HashMap<String, String>,
11}
12
13impl ConfigStore {
14    pub fn new() -> Result<Self> {
15        let config_path = Self::get_config_path()?;
16        if config_path.exists() {
17            let content = fs::read_to_string(&config_path)?;
18            let config: ConfigStore = serde_json::from_str(&content)?;
19            Ok(config)
20        } else {
21            Ok(ConfigStore::default())
22        }
23    }
24
25    pub fn get_base_url(&self) -> Result<String> {
26        // Try config setting first, then environment variable, then error
27        if let Some(url) = self.get_setting("base_url")? {
28            return Ok(url);
29        }
30
31        if let Ok(url) = std::env::var("NABLA_BASE_URL") {
32            return Ok(url);
33        }
34
35        Err(anyhow!(
36            "No base URL configured. Set with 'nabla config set-base-url <url>' or NABLA_BASE_URL env var"
37        ))
38    }
39
40    pub fn get_setting(&self, key: &str) -> Result<Option<String>> {
41        Ok(self.settings.get(key).cloned())
42    }
43
44    pub fn set_setting(&mut self, key: &str, value: &str) -> Result<()> {
45        self.settings.insert(key.to_string(), value.to_string());
46        self.save()
47    }
48
49    pub fn set_base_url(&mut self, url: &str) -> Result<()> {
50        // Validate URL format
51        if !url.starts_with("http://") && !url.starts_with("https://") {
52            return Err(anyhow!("Base URL must start with http:// or https://"));
53        }
54        self.set_setting("base_url", url)?;
55        println!("✅ Base URL set to: {}", url);
56        Ok(())
57    }
58
59    pub fn list_settings(&self) -> Result<Vec<(String, String)>> {
60        let mut settings: Vec<(String, String)> = self
61            .settings
62            .iter()
63            .map(|(k, v)| (k.clone(), v.clone()))
64            .collect();
65        settings.sort_by(|a, b| a.0.cmp(&b.0));
66        Ok(settings)
67    }
68
69    fn get_config_path() -> Result<std::path::PathBuf> {
70        let home = home::home_dir().ok_or_else(|| anyhow!("Could not determine home directory"))?;
71        let config_dir = home.join(".nabla");
72        fs::create_dir_all(&config_dir)?;
73        Ok(config_dir.join("config.json"))
74    }
75
76    fn save(&self) -> Result<()> {
77        let config_path = Self::get_config_path()?;
78        let content = serde_json::to_string_pretty(&self)?;
79        fs::write(&config_path, content)?;
80        Ok(())
81    }
82}
83
84#[derive(Subcommand)]
85pub enum ConfigCommands {
86    Get { key: String },
87    Set { key: String, value: String },
88    SetBaseUrl { url: String },
89    List,
90}