oxur_cli/config/
loader.rs1use super::{paths, ReplConfig};
10use anyhow::{Context, Result};
11
12pub struct ConfigLoader {
14 config: ReplConfig,
15}
16
17impl ConfigLoader {
18 pub fn new() -> Self {
20 Self { config: ReplConfig::default() }
21 }
22
23 pub fn with_file(mut self) -> Result<Self> {
27 if let Some(path) = paths::find_config_file() {
28 let content = std::fs::read_to_string(&path)
29 .with_context(|| format!("Failed to read config file: {}", path.display()))?;
30
31 let file_config: ReplConfig = toml::from_str(&content)
32 .with_context(|| format!("Failed to parse config file: {}", path.display()))?;
33
34 self.config.merge(file_config);
35 }
36 Ok(self)
37 }
38
39 pub fn with_env(mut self) -> Self {
50 if let Ok(prompt) = std::env::var("OXUR_REPL_PROMPT") {
52 self.config.terminal.prompt = prompt;
53 }
54
55 if let Ok(cont_prompt) = std::env::var("OXUR_REPL_CONTINUATION_PROMPT") {
56 self.config.terminal.continuation_prompt = cont_prompt;
57 }
58
59 if let Ok(banner) = std::env::var("OXUR_REPL_BANNER") {
60 self.config.terminal.banner = Some(banner);
61 }
62
63 if let Ok(val) = std::env::var("OXUR_REPL_COLOR") {
64 self.config.terminal.color_enabled = parse_bool(&val);
65 }
66
67 if let Ok(mode) = std::env::var("OXUR_REPL_EDIT_MODE") {
68 if let Some(edit_mode) = parse_edit_mode(&mode) {
69 self.config.terminal.edit_mode = edit_mode;
70 }
71 }
72
73 if let Ok(val) = std::env::var("OXUR_HISTORY_ENABLED") {
75 self.config.history.enabled = parse_bool(&val);
76 }
77
78 if let Ok(val) = std::env::var("OXUR_HISTORY_MAX_SIZE") {
79 if let Ok(size) = val.parse() {
80 self.config.history.max_size = Some(size);
81 }
82 }
83
84 self
85 }
86
87 pub fn with_cli_overrides(mut self, no_color: bool) -> Self {
91 if no_color {
92 self.config.terminal.color_enabled = false;
93 }
94 self
95 }
96
97 pub fn build(self) -> ReplConfig {
99 self.config
100 }
101}
102
103impl Default for ConfigLoader {
104 fn default() -> Self {
105 Self::new()
106 }
107}
108
109fn parse_bool(s: &str) -> bool {
111 matches!(s.to_lowercase().as_str(), "true" | "1" | "yes" | "on")
112}
113
114fn parse_edit_mode(s: &str) -> Option<super::EditMode> {
116 match s.to_lowercase().as_str() {
117 "emacs" => Some(super::EditMode::Emacs),
118 "vi" | "vim" => Some(super::EditMode::Vi),
119 _ => None,
120 }
121}
122
123pub fn load_config(no_color: bool) -> Result<ReplConfig> {
131 ConfigLoader::new().with_file()?.with_env().with_cli_overrides(no_color).build().pipe(Ok)
132}
133
134trait Pipe: Sized {
136 fn pipe<F, R>(self, f: F) -> R
137 where
138 F: FnOnce(Self) -> R,
139 {
140 f(self)
141 }
142}
143
144impl<T> Pipe for T {}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_parse_bool_true() {
152 assert!(parse_bool("true"));
153 assert!(parse_bool("TRUE"));
154 assert!(parse_bool("1"));
155 assert!(parse_bool("yes"));
156 assert!(parse_bool("on"));
157 }
158
159 #[test]
160 fn test_parse_bool_false() {
161 assert!(!parse_bool("false"));
162 assert!(!parse_bool("FALSE"));
163 assert!(!parse_bool("0"));
164 assert!(!parse_bool("no"));
165 assert!(!parse_bool("off"));
166 assert!(!parse_bool(""));
167 assert!(!parse_bool("invalid"));
168 }
169
170 #[test]
171 fn test_parse_edit_mode() {
172 assert_eq!(parse_edit_mode("emacs"), Some(super::super::EditMode::Emacs));
173 assert_eq!(parse_edit_mode("EMACS"), Some(super::super::EditMode::Emacs));
174 assert_eq!(parse_edit_mode("vi"), Some(super::super::EditMode::Vi));
175 assert_eq!(parse_edit_mode("vim"), Some(super::super::EditMode::Vi));
176 assert_eq!(parse_edit_mode("invalid"), None);
177 }
178
179 #[test]
180 fn test_loader_defaults() {
181 let config = ConfigLoader::new().build();
182 assert_eq!(config.terminal.prompt, "oxur> ");
183 assert!(config.terminal.color_enabled);
184 assert!(config.history.enabled);
185 }
186
187 #[test]
188 fn test_loader_with_cli_overrides() {
189 let config = ConfigLoader::new().with_cli_overrides(true).build();
190 assert!(!config.terminal.color_enabled);
191 }
192
193 #[test]
194 fn test_loader_chain() {
195 let result = ConfigLoader::new()
197 .with_file() .map(|l| l.with_env())
199 .map(|l| l.with_cli_overrides(false))
200 .map(|l| l.build());
201
202 assert!(result.is_ok());
203 }
204}