1use std::fs;
9use std::path::{Path, PathBuf};
10
11use serde::{Deserialize, Serialize};
12
13use crate::exit::{CtlError, CtlResult};
14
15const DEFAULT_SERVER: &str = "http://127.0.0.1:8080";
16
17#[derive(Debug, Clone, Serialize, Deserialize, Default)]
18pub struct Config {
19 pub server_url: Option<String>,
21 pub api_token: Option<String>,
23}
24
25impl Config {
26 pub fn effective_server(&self) -> String {
28 self.server_url
29 .clone()
30 .or_else(|| std::env::var("CELLCTL_SERVER").ok())
31 .unwrap_or_else(|| DEFAULT_SERVER.to_string())
32 }
33
34 pub fn effective_token(&self) -> Option<String> {
36 self.api_token
37 .clone()
38 .or_else(|| std::env::var("CELLCTL_TOKEN").ok())
39 }
40}
41
42pub fn config_path() -> CtlResult<PathBuf> {
44 if let Ok(explicit) = std::env::var("CELLCTL_CONFIG") {
45 return Ok(PathBuf::from(explicit));
46 }
47 let home =
48 home::home_dir().ok_or_else(|| CtlError::usage("cannot determine home directory"))?;
49 Ok(home.join(".cellctl").join("config"))
50}
51
52pub fn load() -> CtlResult<Config> {
53 let path = config_path()?;
54 if !path.exists() {
55 return Ok(Config::default());
56 }
57 let raw = fs::read_to_string(&path)
58 .map_err(|e| CtlError::usage(format!("read {}: {e}", path.display())))?;
59 let cfg: Config = toml::from_str(&raw)
60 .map_err(|e| CtlError::usage(format!("parse {}: {e}", path.display())))?;
61 Ok(cfg)
62}
63
64pub fn save(cfg: &Config) -> CtlResult<()> {
65 let path = config_path()?;
66 if let Some(parent) = path.parent() {
67 ensure_dir(parent)?;
68 }
69 let raw = toml::to_string_pretty(cfg)
70 .map_err(|e| CtlError::usage(format!("serialize config: {e}")))?;
71 fs::write(&path, raw).map_err(|e| CtlError::usage(format!("write {}: {e}", path.display())))?;
72 Ok(())
73}
74
75fn ensure_dir(p: &Path) -> CtlResult<()> {
76 fs::create_dir_all(p).map_err(|e| CtlError::usage(format!("mkdir {}: {e}", p.display())))
77}