1mod history;
25mod loader;
26mod metrics;
27pub mod paths;
28mod terminal;
29
30pub use history::{HistoryConfig, HistoryConfigBuilder};
31pub use loader::{load_config, ConfigLoader};
32pub use metrics::{MetricsConfig, MetricsConfigBuilder};
33pub use terminal::{EditMode, TerminalConfig, TerminalConfigBuilder};
34
35use serde::{Deserialize, Serialize};
36
37#[derive(Debug, Clone, Serialize, Deserialize, Default)]
41#[serde(default)]
42pub struct ReplConfig {
43 pub terminal: TerminalConfig,
45 pub history: HistoryConfig,
47 pub metrics: MetricsConfig,
49}
50
51impl ReplConfig {
52 pub fn load(no_color: bool) -> anyhow::Result<Self> {
68 load_config(no_color)
69 }
70
71 pub fn builder() -> ReplConfigBuilder {
73 ReplConfigBuilder::new()
74 }
75
76 pub fn merge(&mut self, other: ReplConfig) {
80 self.terminal.merge(other.terminal);
81 self.history.merge(other.history);
82 self.metrics.merge(other.metrics);
83 }
84}
85
86#[derive(Debug, Clone, Default)]
88pub struct ReplConfigBuilder {
89 config: ReplConfig,
90}
91
92impl ReplConfigBuilder {
93 pub fn new() -> Self {
95 Self { config: ReplConfig::default() }
96 }
97
98 pub fn terminal(mut self, config: TerminalConfig) -> Self {
100 self.config.terminal = config;
101 self
102 }
103
104 pub fn history(mut self, config: HistoryConfig) -> Self {
106 self.config.history = config;
107 self
108 }
109
110 pub fn metrics(mut self, config: MetricsConfig) -> Self {
112 self.config.metrics = config;
113 self
114 }
115
116 pub fn build(self) -> ReplConfig {
118 self.config
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn test_repl_config_default() {
128 let config = ReplConfig::default();
129 assert_eq!(config.terminal.prompt, "oxur> ");
130 assert!(config.history.enabled);
131 }
132
133 #[test]
134 fn test_repl_config_builder() {
135 let terminal = TerminalConfig::builder().prompt(">>> ").build();
136
137 let history = HistoryConfig::builder().max_size(500).build();
138
139 let config = ReplConfig::builder().terminal(terminal).history(history).build();
140
141 assert_eq!(config.terminal.prompt, ">>> ");
142 assert_eq!(config.history.max_size, Some(500));
143 }
144
145 #[test]
146 fn test_serde_roundtrip() {
147 let config = ReplConfig::builder()
148 .terminal(TerminalConfig::builder().prompt("test> ").build())
149 .build();
150
151 let toml = toml::to_string(&config).unwrap();
152 let parsed: ReplConfig = toml::from_str(&toml).unwrap();
153
154 assert_eq!(config.terminal.prompt, parsed.terminal.prompt);
155 }
156
157 #[test]
158 fn test_merge() {
159 let mut base = ReplConfig::default();
160 let other = ReplConfig::builder()
161 .terminal(TerminalConfig::builder().prompt("merged> ").build())
162 .build();
163
164 base.merge(other);
165 assert_eq!(base.terminal.prompt, "merged> ");
166 }
167
168 #[test]
169 fn test_toml_parsing() {
170 let toml_str = r#"
171[terminal]
172prompt = "λ> "
173continuation_prompt = " | "
174color_enabled = true
175edit_mode = "vi"
176
177[history]
178enabled = true
179max_size = 5000
180"#;
181
182 let config: ReplConfig = toml::from_str(toml_str).unwrap();
183 assert_eq!(config.terminal.prompt, "λ> ");
184 assert_eq!(config.terminal.continuation_prompt, " | ");
185 assert_eq!(config.terminal.edit_mode, EditMode::Vi);
186 assert_eq!(config.history.max_size, Some(5000));
187 }
188}