1use std::collections::HashMap;
4
5#[allow(dead_code)]
11#[derive(Clone, Debug, PartialEq)]
12pub enum ConfigValue {
13 Bool(bool),
14 Int(i64),
15 Float(f64),
16 Str(String),
17}
18
19#[allow(dead_code)]
21#[derive(Clone, Debug)]
22pub struct ConfigProfile {
23 pub name: String,
25 pub values: HashMap<String, ConfigValue>,
27 pub dirty: bool,
29}
30
31#[allow(dead_code)]
33pub type ConfigLookup<'a> = Option<&'a ConfigValue>;
34
35#[allow(dead_code)]
37pub struct ConfigManager {
38 pub profiles: Vec<ConfigProfile>,
40 pub active: String,
42}
43
44#[allow(dead_code)]
50pub fn new_config_manager() -> ConfigManager {
51 let default_profile = ConfigProfile {
52 name: "default".to_string(),
53 values: HashMap::new(),
54 dirty: false,
55 };
56 ConfigManager {
57 profiles: vec![default_profile],
58 active: "default".to_string(),
59 }
60}
61
62#[allow(dead_code)]
69pub fn create_profile(mgr: &mut ConfigManager, name: &str) -> bool {
70 if mgr.profiles.iter().any(|p| p.name == name) {
71 return false;
72 }
73 mgr.profiles.push(ConfigProfile {
74 name: name.to_string(),
75 values: HashMap::new(),
76 dirty: false,
77 });
78 true
79}
80
81#[allow(dead_code)]
84pub fn delete_profile(mgr: &mut ConfigManager, name: &str) -> bool {
85 if mgr.active == name {
86 return false;
87 }
88 if let Some(pos) = mgr.profiles.iter().position(|p| p.name == name) {
89 mgr.profiles.remove(pos);
90 return true;
91 }
92 false
93}
94
95#[allow(dead_code)]
97pub fn active_profile(mgr: &ConfigManager) -> &str {
98 &mgr.active
99}
100
101#[allow(dead_code)]
104pub fn switch_profile(mgr: &mut ConfigManager, name: &str) -> bool {
105 if mgr.profiles.iter().any(|p| p.name == name) {
106 mgr.active = name.to_string();
107 return true;
108 }
109 false
110}
111
112#[allow(dead_code)]
114pub fn profile_count(mgr: &ConfigManager) -> usize {
115 mgr.profiles.len()
116}
117
118#[allow(dead_code)]
120pub fn list_profiles(mgr: &ConfigManager) -> Vec<&str> {
121 mgr.profiles.iter().map(|p| p.name.as_str()).collect()
122}
123
124#[allow(dead_code)]
131pub fn set_profile_value(
132 mgr: &mut ConfigManager,
133 profile: &str,
134 key: &str,
135 val: ConfigValue,
136) -> bool {
137 if let Some(p) = mgr.profiles.iter_mut().find(|p| p.name == profile) {
138 p.values.insert(key.to_string(), val);
139 p.dirty = true;
140 return true;
141 }
142 false
143}
144
145#[allow(dead_code)]
147pub fn get_profile_value<'a>(mgr: &'a ConfigManager, profile: &str, key: &str) -> ConfigLookup<'a> {
148 mgr.profiles
149 .iter()
150 .find(|p| p.name == profile)
151 .and_then(|p| p.values.get(key))
152}
153
154#[allow(dead_code)]
157pub fn get_value_with_fallback<'a>(mgr: &'a ConfigManager, key: &str) -> ConfigLookup<'a> {
158 let active = mgr.active.clone();
159 if let Some(v) = get_profile_value(mgr, &active, key) {
160 return Some(v);
161 }
162 get_profile_value(mgr, "default", key)
163}
164
165#[allow(dead_code)]
173pub fn merge_profiles(mgr: &mut ConfigManager, src: &str, dst: &str) -> bool {
174 let src_values: Option<Vec<(String, ConfigValue)>> =
176 mgr.profiles.iter().find(|p| p.name == src).map(|p| {
177 p.values
178 .iter()
179 .map(|(k, v)| (k.clone(), v.clone()))
180 .collect()
181 });
182
183 if let Some(pairs) = src_values {
184 if let Some(dst_profile) = mgr.profiles.iter_mut().find(|p| p.name == dst) {
185 for (k, v) in pairs {
186 dst_profile.values.insert(k, v);
187 }
188 dst_profile.dirty = true;
189 return true;
190 }
191 }
192 false
193}
194
195#[allow(dead_code)]
198pub fn reset_profile_to_defaults(mgr: &mut ConfigManager, profile: &str) -> bool {
199 if let Some(p) = mgr.profiles.iter_mut().find(|p| p.name == profile) {
200 p.values.clear();
201 p.dirty = true;
202 return true;
203 }
204 false
205}
206
207#[allow(dead_code)]
216pub fn config_from_pairs(
217 mgr: &mut ConfigManager,
218 profile: &str,
219 pairs: &[(&str, &str)],
220) -> Option<usize> {
221 let profile_exists = mgr.profiles.iter().any(|p| p.name == profile);
222 if !profile_exists {
223 return None;
224 }
225 let mut count = 0;
226 for (k, v) in pairs {
227 let val = parse_config_value(v);
228 set_profile_value(mgr, profile, k, val);
229 count += 1;
230 }
231 Some(count)
232}
233
234fn parse_config_value(s: &str) -> ConfigValue {
235 if s == "true" {
236 return ConfigValue::Bool(true);
237 }
238 if s == "false" {
239 return ConfigValue::Bool(false);
240 }
241 if s.contains('.') {
242 if let Ok(f) = s.parse::<f64>() {
243 return ConfigValue::Float(f);
244 }
245 }
246 if let Ok(i) = s.parse::<i64>() {
247 return ConfigValue::Int(i);
248 }
249 ConfigValue::Str(s.to_string())
250}
251
252fn config_value_to_json(v: &ConfigValue) -> String {
257 match v {
258 ConfigValue::Bool(b) => b.to_string(),
259 ConfigValue::Int(i) => i.to_string(),
260 ConfigValue::Float(f) => format!("{f}"),
261 ConfigValue::Str(s) => format!(r#""{s}""#),
262 }
263}
264
265#[allow(dead_code)]
267pub fn config_to_json(mgr: &ConfigManager) -> String {
268 let profiles_json: Vec<String> = mgr
269 .profiles
270 .iter()
271 .map(|p| {
272 let entries: Vec<String> = p
273 .values
274 .iter()
275 .map(|(k, v)| format!(r#""{k}":{}"#, config_value_to_json(v)))
276 .collect();
277 format!(
278 r#"{{"name":"{}","dirty":{},"values":{{{}}}}}"#,
279 p.name,
280 p.dirty,
281 entries.join(",")
282 )
283 })
284 .collect();
285
286 format!(
287 r#"{{"active":"{}","profiles":[{}]}}"#,
288 mgr.active,
289 profiles_json.join(",")
290 )
291}
292
293#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_new_config_manager_has_default_profile() {
303 let mgr = new_config_manager();
304 assert_eq!(profile_count(&mgr), 1);
305 assert_eq!(active_profile(&mgr), "default");
306 }
307
308 #[test]
309 fn test_create_profile_succeeds() {
310 let mut mgr = new_config_manager();
311 assert!(create_profile(&mut mgr, "production"));
312 assert_eq!(profile_count(&mgr), 2);
313 }
314
315 #[test]
316 fn test_create_profile_duplicate_fails() {
317 let mut mgr = new_config_manager();
318 create_profile(&mut mgr, "dev");
319 assert!(!create_profile(&mut mgr, "dev"));
320 assert_eq!(profile_count(&mgr), 2);
321 }
322
323 #[test]
324 fn test_delete_profile_removes_it() {
325 let mut mgr = new_config_manager();
326 create_profile(&mut mgr, "temp");
327 assert!(delete_profile(&mut mgr, "temp"));
328 assert_eq!(profile_count(&mgr), 1);
329 }
330
331 #[test]
332 fn test_delete_active_profile_fails() {
333 let mut mgr = new_config_manager();
334 assert!(!delete_profile(&mut mgr, "default"));
335 }
336
337 #[test]
338 fn test_switch_profile() {
339 let mut mgr = new_config_manager();
340 create_profile(&mut mgr, "alt");
341 assert!(switch_profile(&mut mgr, "alt"));
342 assert_eq!(active_profile(&mgr), "alt");
343 }
344
345 #[test]
346 fn test_switch_profile_unknown_fails() {
347 let mut mgr = new_config_manager();
348 assert!(!switch_profile(&mut mgr, "ghost"));
349 }
350
351 #[test]
352 fn test_set_and_get_profile_value_bool() {
353 let mut mgr = new_config_manager();
354 set_profile_value(&mut mgr, "default", "show_grid", ConfigValue::Bool(true));
355 let v = get_profile_value(&mgr, "default", "show_grid");
356 assert_eq!(v, Some(&ConfigValue::Bool(true)));
357 }
358
359 #[test]
360 fn test_set_and_get_profile_value_int() {
361 let mut mgr = new_config_manager();
362 set_profile_value(&mut mgr, "default", "max_fps", ConfigValue::Int(60));
363 let v = get_profile_value(&mgr, "default", "max_fps");
364 assert_eq!(v, Some(&ConfigValue::Int(60)));
365 }
366
367 #[test]
368 fn test_set_and_get_profile_value_float() {
369 let mut mgr = new_config_manager();
370 set_profile_value(&mut mgr, "default", "gamma", ConfigValue::Float(2.2));
371 let v = get_profile_value(&mgr, "default", "gamma");
372 assert_eq!(v, Some(&ConfigValue::Float(2.2)));
373 }
374
375 #[test]
376 fn test_set_and_get_profile_value_str() {
377 let mut mgr = new_config_manager();
378 set_profile_value(
379 &mut mgr,
380 "default",
381 "lang",
382 ConfigValue::Str("en".to_string()),
383 );
384 let v = get_profile_value(&mgr, "default", "lang");
385 assert_eq!(v, Some(&ConfigValue::Str("en".to_string())));
386 }
387
388 #[test]
389 fn test_get_value_with_fallback_uses_active() {
390 let mut mgr = new_config_manager();
391 create_profile(&mut mgr, "custom");
392 switch_profile(&mut mgr, "custom");
393 set_profile_value(&mut mgr, "custom", "key", ConfigValue::Int(99));
394 let v = get_value_with_fallback(&mgr, "key");
395 assert_eq!(v, Some(&ConfigValue::Int(99)));
396 }
397
398 #[test]
399 fn test_get_value_with_fallback_falls_to_default() {
400 let mut mgr = new_config_manager();
401 set_profile_value(
402 &mut mgr,
403 "default",
404 "fallback_key",
405 ConfigValue::Bool(false),
406 );
407 create_profile(&mut mgr, "p2");
408 switch_profile(&mut mgr, "p2");
409 let v = get_value_with_fallback(&mgr, "fallback_key");
410 assert_eq!(v, Some(&ConfigValue::Bool(false)));
411 }
412
413 #[test]
414 fn test_merge_profiles() {
415 let mut mgr = new_config_manager();
416 create_profile(&mut mgr, "src");
417 create_profile(&mut mgr, "dst");
418 set_profile_value(&mut mgr, "src", "x", ConfigValue::Int(1));
419 assert!(merge_profiles(&mut mgr, "src", "dst"));
420 assert_eq!(
421 get_profile_value(&mgr, "dst", "x"),
422 Some(&ConfigValue::Int(1))
423 );
424 }
425
426 #[test]
427 fn test_reset_profile_to_defaults() {
428 let mut mgr = new_config_manager();
429 set_profile_value(&mut mgr, "default", "k", ConfigValue::Int(5));
430 assert!(reset_profile_to_defaults(&mut mgr, "default"));
431 assert!(get_profile_value(&mgr, "default", "k").is_none());
432 }
433
434 #[test]
435 fn test_config_from_pairs_parses_types() {
436 let mut mgr = new_config_manager();
437 let pairs = vec![
438 ("flag", "true"),
439 ("count", "10"),
440 ("ratio", "0.5"),
441 ("name", "hello"),
442 ];
443 let n = config_from_pairs(&mut mgr, "default", &pairs);
444 assert_eq!(n, Some(4));
445 assert_eq!(
446 get_profile_value(&mgr, "default", "flag"),
447 Some(&ConfigValue::Bool(true))
448 );
449 assert_eq!(
450 get_profile_value(&mgr, "default", "count"),
451 Some(&ConfigValue::Int(10))
452 );
453 }
454
455 #[test]
456 fn test_list_profiles() {
457 let mut mgr = new_config_manager();
458 create_profile(&mut mgr, "p1");
459 create_profile(&mut mgr, "p2");
460 let names = list_profiles(&mgr);
461 assert_eq!(names.len(), 3);
462 assert!(names.contains(&"default"));
463 assert!(names.contains(&"p1"));
464 }
465
466 #[test]
467 fn test_config_to_json_contains_active() {
468 let mgr = new_config_manager();
469 let json = config_to_json(&mgr);
470 assert!(json.contains("\"active\":\"default\""));
471 }
472
473 #[test]
474 fn test_config_from_pairs_unknown_profile_returns_none() {
475 let mut mgr = new_config_manager();
476 let result = config_from_pairs(&mut mgr, "nonexistent", &[("k", "v")]);
477 assert!(result.is_none());
478 }
479}