1use std::fs; 2use std::path::{Path, PathBuf}; 3 4use anyhow::{Context, Result}; 5use serde::{Deserialize, Serialize}; 6 7#[derive(Debug, Clone, Serialize, Deserialize, Default)] 8pub struct RootConfig { 9 #[serde(default)] 10 pub default: DefaultSection, 11 #[serde(default)] 12 pub auth: AuthSection, 13 #[serde(default)] 14 pub knots: KnotsSection, 15 #[serde(default)] 16 pub ui: UiSection, 17} 18 19#[derive(Debug, Clone, Serialize, Deserialize, Default)] 20pub struct DefaultSection { 21 pub knot: Option<String>, 22 pub editor: Option<String>, 23 pub pager: Option<String>, 24 #[serde(default = "default_format")] 25 pub format: String, 26} 27 28fn default_format() -> String { 29 "table".to_string() 30} 31 32#[derive(Debug, Clone, Serialize, Deserialize, Default)] 33pub struct AuthSection { 34 pub handle: Option<String>, 35 pub did: Option<String>, 36 pub pds_url: Option<String>, 37} 38 39#[derive(Debug, Clone, Serialize, Deserialize, Default)] 40pub struct KnotsSection { 41 pub default: Option<String>, 42 #[serde(default)] 43 pub custom: serde_json::Value, 44} 45 46#[derive(Debug, Clone, Serialize, Deserialize, Default)] 47pub struct UiSection { 48 #[serde(default)] 49 pub color: bool, 50 #[serde(default)] 51 pub progress_bar: bool, 52 #[serde(default)] 53 pub confirm_destructive: bool, 54} 55 56pub fn default_config_path() -> Result<PathBuf> { 57 // Use ~/.config/tangled on all platforms for consistency 58 let home = std::env::var("HOME") 59 .or_else(|_| std::env::var("USERPROFILE")) 60 .context("Could not determine home directory")?; 61 Ok(PathBuf::from(home) 62 .join(".config") 63 .join("tangled") 64 .join("config.toml")) 65} 66 67pub fn load_config(path: Option<&Path>) -> Result<Option<RootConfig>> { 68 let path = path 69 .map(|p| p.to_path_buf()) 70 .unwrap_or(default_config_path()?); 71 if !path.exists() { 72 return Ok(None); 73 } 74 let content = fs::read_to_string(&path) 75 .with_context(|| format!("Failed reading config file: {}", path.display()))?; 76 let cfg: RootConfig = toml::from_str(&content).context("Invalid TOML in config")?; 77 Ok(Some(cfg)) 78} 79 80pub fn save_config(cfg: &RootConfig, path: Option<&Path>) -> Result<()> { 81 let path = path 82 .map(|p| p.to_path_buf()) 83 .unwrap_or(default_config_path()?); 84 if let Some(parent) = path.parent() { 85 std::fs::create_dir_all(parent)?; 86 } 87 let toml = toml::to_string_pretty(cfg)?; 88 fs::write(&path, toml) 89 .with_context(|| format!("Failed writing config file: {}", path.display()))?; 90 Ok(()) 91}