aaai_core/profile/
prefs.rs1use serde::{Deserialize, Serialize};
8use std::path::PathBuf;
9
10#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "lowercase")]
13pub enum Theme {
14 #[default]
16 Light,
17 Dark,
19 System,
21}
22
23impl std::fmt::Display for Theme {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 match self {
26 Theme::Light => write!(f, "Light"),
27 Theme::Dark => write!(f, "Dark"),
28 Theme::System => write!(f, "System"),
29 }
30 }
31}
32
33impl Theme {
34 pub fn choices() -> &'static [Theme] {
40 &[Theme::Light, Theme::Dark]
41 }
42}
43
44#[derive(Debug, Clone, Default, Serialize, Deserialize)]
46pub struct UserPrefs {
47 #[serde(default)]
49 pub theme: Theme,
50
51 #[serde(default)]
54 pub language: String,
55
56 #[serde(default = "default_ignored_dirs")]
61 pub global_ignored_dirs: Vec<String>,
62}
63
64fn default_ignored_dirs() -> Vec<String> {
65 vec![
66 ".git".into(),
67 "target".into(),
68 "node_modules".into(),
69 ".DS_Store".into(),
70 ]
71}
72
73impl UserPrefs {
74 fn path() -> anyhow::Result<PathBuf> {
75 let base = dirs::home_dir()
76 .ok_or_else(|| anyhow::anyhow!("Cannot determine home directory"))?
77 .join(".aaai");
78 std::fs::create_dir_all(&base)?;
79 Ok(base.join("prefs.yaml"))
80 }
81
82 pub fn load() -> Self {
84 match Self::path().and_then(|p| {
85 if !p.exists() { return Ok(Self::default()); }
86 let text = std::fs::read_to_string(&p)?;
87 serde_yaml::from_str(&text).map_err(|e| anyhow::anyhow!(e))
88 }) {
89 Ok(prefs) => prefs,
90 Err(e) => {
91 log::warn!("Could not load prefs: {e}");
92 Self::default()
93 }
94 }
95 }
96
97 pub fn save(&self) {
99 if let Err(e) = Self::path().and_then(|p| {
100 let yaml = serde_yaml::to_string(self).map_err(|e| anyhow::anyhow!(e))?;
101 std::fs::write(&p, yaml)?;
102 Ok(())
103 }) {
104 log::warn!("Could not save prefs: {e}");
105 }
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn round_trip_yaml() {
115 let prefs = UserPrefs { theme: Theme::Dark, ..Default::default() };
116 let yaml = serde_yaml::to_string(&prefs).unwrap();
117 let restored: UserPrefs = serde_yaml::from_str(&yaml).unwrap();
118 assert_eq!(restored.theme, Theme::Dark);
119 }
120
121 #[test]
122 fn default_is_light() {
123 assert_eq!(UserPrefs::default().theme, Theme::Light);
124 }
125
126 #[test]
127 fn display_names() {
128 assert_eq!(Theme::Light.to_string(), "Light");
129 assert_eq!(Theme::Dark.to_string(), "Dark");
130 }
131
132 #[test]
135 fn new_fields_round_trip() {
136 let p = UserPrefs {
137 language: "ja".into(),
138 global_ignored_dirs: vec![".git".into(), "target".into()],
139 ..Default::default()
140 };
141 let yaml = serde_yaml::to_string(&p).unwrap();
142 let r: UserPrefs = serde_yaml::from_str(&yaml).unwrap();
143 assert_eq!(r.language, "ja");
144 assert_eq!(r.global_ignored_dirs, vec![".git", "target"]);
145 }
146
147 #[test]
148 fn missing_fields_get_defaults() {
149 let yaml = "theme: light\n";
151 let p: UserPrefs = serde_yaml::from_str(yaml).unwrap();
152 assert!(!p.global_ignored_dirs.is_empty(), "default dirs should be applied");
153 assert_eq!(p.language, "", "absent language should be empty string");
154 }
155}