difflore_core/infra/
config.rs1use std::path::Path;
2
3use crate::infra::paths;
4
5#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
6pub enum ThemeMode {
7 #[default]
8 Dark,
9 Light,
10}
11
12#[derive(Debug, Clone, Default, PartialEq, Eq)]
13pub struct DiffloreConfig {
14 pub theme: ThemeMode,
15}
16
17pub fn load() -> DiffloreConfig {
22 let Ok(path) = paths::config_file() else {
23 return DiffloreConfig::default();
24 };
25 load_from_path(&path)
26}
27
28pub fn load_from_path(path: &Path) -> DiffloreConfig {
33 let Ok(raw) = std::fs::read_to_string(path) else {
34 return DiffloreConfig::default();
35 };
36 let mut cfg = DiffloreConfig::default();
37 for (key, value) in parse_kv_pairs(&raw) {
38 if key == "theme" {
39 cfg.theme = match value.as_str() {
40 "light" => ThemeMode::Light,
41 "dark" => ThemeMode::Dark,
42 _ => ThemeMode::default(),
43 };
44 }
45 }
46 cfg
47}
48
49fn parse_kv_pairs(src: &str) -> Vec<(String, String)> {
50 let mut out = Vec::new();
51 for line in src.lines() {
52 let line = line.trim_start();
53 if line.is_empty() || line.starts_with('#') {
54 continue;
55 }
56 let Some(eq) = line.find('=') else {
57 continue;
58 };
59 let key = line[..eq].trim().to_owned();
60 let rest = line[eq + 1..].trim_start();
61 let Some(rest) = rest.strip_prefix('"') else {
62 continue;
63 };
64 let Some(end) = rest.find('"') else {
65 continue;
66 };
67 out.push((key, rest[..end].to_owned()));
68 }
69 out
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use std::fs;
76
77 fn write_cfg(contents: &str) -> tempfile::TempDir {
78 let tmp = tempfile::tempdir().unwrap();
79 fs::write(tmp.path().join("config.toml"), contents).unwrap();
80 tmp
81 }
82
83 #[test]
84 fn load_from_path_returns_default_when_file_missing() {
85 let tmp = tempfile::tempdir().unwrap();
86 let cfg = load_from_path(&tmp.path().join("does-not-exist.toml"));
87 assert_eq!(cfg, DiffloreConfig::default());
88 assert_eq!(cfg.theme, ThemeMode::Dark);
89 }
90
91 #[test]
92 fn load_from_path_parses_theme_light() {
93 let tmp = write_cfg(r#"theme = "light""#);
94 assert_eq!(
95 load_from_path(&tmp.path().join("config.toml")).theme,
96 ThemeMode::Light
97 );
98 }
99
100 #[test]
101 fn load_from_path_parses_theme_dark() {
102 let tmp = write_cfg(r#"theme = "dark""#);
103 assert_eq!(
104 load_from_path(&tmp.path().join("config.toml")).theme,
105 ThemeMode::Dark
106 );
107 }
108
109 #[test]
110 fn load_from_path_malformed_theme_falls_back_to_default() {
111 let tmp = write_cfg("theme = bogus\n");
113 assert_eq!(
114 load_from_path(&tmp.path().join("config.toml")).theme,
115 ThemeMode::Dark
116 );
117 }
118
119 #[test]
120 fn load_from_path_tolerates_comments_and_extra_keys() {
121 let tmp = write_cfg("# leading comment\ntheme = \"light\"\nfoo = \"bar\"\n");
122 assert_eq!(
123 load_from_path(&tmp.path().join("config.toml")).theme,
124 ThemeMode::Light
125 );
126 }
127
128 #[test]
129 fn load_returns_default_when_file_missing_in_data_home() {
130 let _ = load(); }
137
138 #[test]
139 fn unrecognised_theme_value_falls_back_to_default() {
140 let tmp = write_cfg(r#"theme = "neon""#);
141 assert_eq!(
142 load_from_path(&tmp.path().join("config.toml")).theme,
143 ThemeMode::Dark
144 );
145 }
146}