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 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 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}