Skip to main content

entrenar/train/tui/
config.rs

1//! YAML Configuration for Monitor (ENT-062)
2//!
3//! Declarative configuration for terminal monitoring.
4
5use super::callback::TerminalMonitorCallback;
6use super::capability::{DashboardLayout, TerminalCapabilities, TerminalMode};
7
8/// Monitor configuration for YAML.
9#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
10#[serde(default)]
11pub struct MonitorConfig {
12    /// Enable terminal monitoring
13    pub enabled: bool,
14    /// Dashboard layout
15    pub layout: String,
16    /// Terminal mode (ascii, unicode, ansi)
17    pub terminal_mode: String,
18    /// Refresh interval in milliseconds
19    pub refresh_ms: u64,
20    /// Sparkline width
21    pub sparkline_width: usize,
22    /// Show ETA
23    pub show_eta: bool,
24    /// Reference curve path (optional)
25    pub reference_curve: Option<String>,
26}
27
28impl Default for MonitorConfig {
29    fn default() -> Self {
30        Self {
31            enabled: true,
32            layout: "compact".to_string(),
33            terminal_mode: "auto".to_string(),
34            refresh_ms: 100,
35            sparkline_width: 20,
36            show_eta: true,
37            reference_curve: None,
38        }
39    }
40}
41
42impl MonitorConfig {
43    /// Create TerminalMonitorCallback from config.
44    pub fn to_callback(&self) -> TerminalMonitorCallback {
45        let layout = match self.layout.as_str() {
46            "minimal" => DashboardLayout::Minimal,
47            "full" => DashboardLayout::Full,
48            "compact" => DashboardLayout::Compact,
49            _ => {
50                eprintln!("Warning: unknown layout '{}', defaulting to Compact", self.layout);
51                DashboardLayout::Compact
52            }
53        };
54
55        let mode = match self.terminal_mode.as_str() {
56            "ascii" => TerminalMode::Ascii,
57            "ansi" => TerminalMode::Ansi,
58            "unicode" => TerminalMode::Unicode,
59            "auto" => TerminalCapabilities::detect().recommended_mode(),
60            _ => {
61                eprintln!(
62                    "Warning: unknown terminal_mode '{}', defaulting to auto-detect",
63                    self.terminal_mode
64                );
65                TerminalCapabilities::detect().recommended_mode()
66            }
67        };
68
69        TerminalMonitorCallback::new()
70            .layout(layout)
71            .mode(mode)
72            .sparkline_width(self.sparkline_width)
73            .refresh_interval_ms(self.refresh_ms)
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_monitor_config_default() {
83        let config = MonitorConfig::default();
84        assert!(config.enabled);
85        assert_eq!(config.layout, "compact");
86        assert_eq!(config.terminal_mode, "auto");
87        assert_eq!(config.refresh_ms, 100);
88        assert_eq!(config.sparkline_width, 20);
89        assert!(config.show_eta);
90        assert!(config.reference_curve.is_none());
91    }
92
93    #[test]
94    fn test_monitor_config_to_callback() {
95        let config = MonitorConfig::default();
96        let _callback = config.to_callback();
97        // Just verify it doesn't panic
98    }
99
100    #[test]
101    fn test_monitor_config_to_callback_minimal() {
102        let config = MonitorConfig { layout: "minimal".to_string(), ..Default::default() };
103        let _callback = config.to_callback();
104    }
105
106    #[test]
107    fn test_monitor_config_to_callback_full() {
108        let config = MonitorConfig { layout: "full".to_string(), ..Default::default() };
109        let _callback = config.to_callback();
110    }
111
112    #[test]
113    fn test_monitor_config_to_callback_ascii() {
114        let config = MonitorConfig { terminal_mode: "ascii".to_string(), ..Default::default() };
115        let _callback = config.to_callback();
116    }
117
118    #[test]
119    fn test_monitor_config_to_callback_ansi() {
120        let config = MonitorConfig { terminal_mode: "ansi".to_string(), ..Default::default() };
121        let _callback = config.to_callback();
122    }
123
124    #[test]
125    fn test_monitor_config_serde_roundtrip() {
126        let config = MonitorConfig {
127            enabled: true,
128            layout: "full".to_string(),
129            terminal_mode: "unicode".to_string(),
130            refresh_ms: 200,
131            sparkline_width: 30,
132            show_eta: false,
133            reference_curve: Some("golden.json".to_string()),
134        };
135
136        let yaml = serde_yaml::to_string(&config).expect("config should be valid");
137        let parsed: MonitorConfig = serde_yaml::from_str(&yaml).expect("parsing should succeed");
138
139        assert_eq!(parsed.enabled, config.enabled);
140        assert_eq!(parsed.layout, config.layout);
141        assert_eq!(parsed.terminal_mode, config.terminal_mode);
142        assert_eq!(parsed.refresh_ms, config.refresh_ms);
143        assert_eq!(parsed.sparkline_width, config.sparkline_width);
144        assert_eq!(parsed.show_eta, config.show_eta);
145        assert_eq!(parsed.reference_curve, config.reference_curve);
146    }
147
148    #[test]
149    fn test_monitor_config_to_callback_unicode() {
150        let config = MonitorConfig { terminal_mode: "unicode".to_string(), ..Default::default() };
151        let _callback = config.to_callback();
152    }
153
154    #[test]
155    fn test_monitor_config_from_yaml_defaults() {
156        // YAML with only enabled field - tests serde defaults
157        let yaml = "enabled: true\n";
158        let parsed: MonitorConfig = serde_yaml::from_str(yaml).expect("parsing should succeed");
159
160        // default_true is called for show_eta
161        assert!(parsed.show_eta);
162        // default_refresh is called for refresh_ms
163        assert_eq!(parsed.refresh_ms, 100);
164        // default_sparkline_width is called
165        assert_eq!(parsed.sparkline_width, 20);
166    }
167
168    #[test]
169    fn test_monitor_config_clone() {
170        let config = MonitorConfig::default();
171        let cloned = config.clone();
172        assert_eq!(config.enabled, cloned.enabled);
173        assert_eq!(config.layout, cloned.layout);
174    }
175
176    #[test]
177    fn test_monitor_config_debug() {
178        let config = MonitorConfig::default();
179        let debug_str = format!("{config:?}");
180        assert!(debug_str.contains("MonitorConfig"));
181    }
182}