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}