Skip to main content

retch_cli/
config.rs

1use serde::{Deserialize, Serialize};
2use std::fs;
3use std::path::PathBuf;
4
5/// Configuration for the retch CLI.
6///
7/// This struct represents the options that can be set in the `config.toml` file.
8#[derive(Debug, Serialize, Deserialize, Default, Clone)]
9pub struct Config {
10    /// The name of the theme to use (e.g., "dark", "catppuccin-mocha").
11    pub theme: Option<String>,
12    /// Whether to display the distro logo.
13    pub show_logo: Option<bool>,
14    /// Whether to force ASCII-only logo output.
15    pub ascii_only: Option<bool>,
16    /// List of fields to display, in order.
17    pub fields: Option<Vec<String>>,
18    /// Custom theme color overrides.
19    pub custom_theme: Option<CustomTheme>,
20}
21
22/// Custom color overrides for themes.
23///
24/// Allows users to specify hex codes or color names for specific UI elements.
25#[derive(Debug, Serialize, Deserialize, Default, Clone)]
26pub struct CustomTheme {
27    /// Color for field labels.
28    pub label_color: Option<String>,
29    /// Color for field values.
30    pub value_color: Option<String>,
31    /// Color for accent elements.
32    pub accent_color: Option<String>,
33    /// Color for the title/username line.
34    pub title_color: Option<String>,
35    /// Color for separators.
36    pub separator_color: Option<String>,
37}
38
39impl Config {
40    /// Loads the configuration from the default system path.
41    ///
42    /// Typically looks in `~/.config/retch/config.toml`.
43    pub fn load() -> anyhow::Result<Self> {
44        if let Some(path) = Self::config_path() {
45            if path.exists() {
46                let contents = fs::read_to_string(&path)?;
47                let config: Config = toml::from_str(&contents)?;
48                return Ok(config);
49            }
50        }
51        Ok(Self::default())
52    }
53
54    /// Returns the expected path to the configuration file.
55    pub fn config_path() -> Option<PathBuf> {
56        dirs::config_dir().map(|mut p| {
57            p.push("retch");
58            p.push("config.toml");
59            p
60        })
61    }
62
63    /// Merges CLI options into the configuration.
64    ///
65    /// CLI arguments take precedence over values defined in the config file.
66    pub fn merge_with_cli(&self, cli: &crate::cli::Cli) -> Self {
67        let mut merged = self.clone();
68
69        if let Some(theme) = &cli.theme {
70            merged.theme = Some(theme.clone());
71        }
72        if cli.no_logo {
73            merged.show_logo = Some(false);
74        }
75        if cli.ascii_only {
76            merged.ascii_only = Some(true);
77        }
78        if let Some(fields_str) = &cli.fields {
79            // Split comma-separated string into Vec<String>
80            let fields = fields_str
81                .split(',')
82                .map(|s| s.trim().to_string())
83                .filter(|s| !s.is_empty())
84                .collect::<Vec<String>>();
85            merged.fields = Some(fields);
86        }
87
88        merged
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use crate::cli::Cli;
96    use clap::Parser;
97
98    #[test]
99    fn test_config_merge_with_cli() {
100        let config = Config {
101            theme: Some("dark".to_string()),
102            show_logo: Some(true),
103            ascii_only: Some(false),
104            fields: Some(vec!["os".to_string(), "kernel".to_string()]),
105            ..Default::default()
106        };
107
108        // Test theme override
109        let cli = Cli::try_parse_from(&["retch", "--theme", "light"]).unwrap();
110        let merged = config.merge_with_cli(&cli);
111        assert_eq!(merged.theme, Some("light".to_string()));
112
113        // Test no-logo override
114        let cli = Cli::try_parse_from(&["retch", "--no-logo"]).unwrap();
115        let merged = config.merge_with_cli(&cli);
116        assert_eq!(merged.show_logo, Some(false));
117
118        // Test ascii-only override
119        let cli = Cli::try_parse_from(&["retch", "--ascii-only"]).unwrap();
120        let merged = config.merge_with_cli(&cli);
121        assert_eq!(merged.ascii_only, Some(true));
122
123        // Test fields override
124        let cli = Cli::try_parse_from(&["retch", "--fields", "cpu,gpu,memory"]).unwrap();
125        let merged = config.merge_with_cli(&cli);
126        assert_eq!(
127            merged.fields,
128            Some(vec![
129                "cpu".to_string(),
130                "gpu".to_string(),
131                "memory".to_string()
132            ])
133        );
134    }
135}