clock_tui/
config.rs

1use chrono_tz::Tz;
2use serde::{Deserialize, Deserializer};
3
4fn deserialize_timezone<'de, D>(deserializer: D) -> Result<Option<Tz>, D::Error>
5where
6    D: Deserializer<'de>,
7{
8    let s: Option<String> = Option::deserialize(deserializer)?;
9    match s {
10        Some(s) => s.parse().map(Some).map_err(serde::de::Error::custom),
11        None => Ok(None),
12    }
13}
14
15#[derive(Debug, Deserialize)]
16pub struct Config {
17    #[serde(default)]
18    pub default: DefaultConfig,
19    #[serde(default)]
20    pub clock: ClockConfig,
21    #[serde(default)]
22    pub timer: TimerConfig,
23    #[serde(default)]
24    pub stopwatch: StopwatchConfig,
25    #[serde(default)]
26    pub countdown: CountdownConfig,
27}
28
29#[derive(Debug, Deserialize)]
30pub struct DefaultConfig {
31    #[serde(default = "default_mode")]
32    pub mode: String,
33    #[serde(default = "default_color")]
34    pub color: String,
35    #[serde(default = "default_size")]
36    pub size: u16,
37}
38
39#[derive(Debug, Deserialize)]
40pub struct ClockConfig {
41    #[serde(default = "default_true")]
42    pub show_date: bool,
43    #[serde(default = "default_true")]
44    pub show_seconds: bool,
45    #[serde(default = "default_false")]
46    pub show_millis: bool,
47    #[serde(default, deserialize_with = "deserialize_timezone")]
48    pub timezone: Option<Tz>,
49}
50
51#[derive(Debug, Deserialize)]
52pub struct TimerConfig {
53    #[serde(default = "default_timer_durations")]
54    pub durations: Vec<String>,
55    #[serde(default)]
56    pub titles: Vec<String>,
57    #[serde(default)]
58    pub repeat: bool,
59    #[serde(default = "default_true")]
60    pub show_millis: bool,
61    #[serde(default)]
62    pub start_paused: bool,
63    #[serde(default)]
64    pub auto_quit: bool,
65    #[serde(default)]
66    pub execute: Vec<String>,
67}
68
69#[derive(Debug, Deserialize)]
70pub struct StopwatchConfig {}
71
72#[derive(Debug, Deserialize)]
73pub struct CountdownConfig {
74    #[serde(default)]
75    pub time: Option<String>,
76    #[serde(default)]
77    pub title: Option<String>,
78    #[serde(default)]
79    pub show_millis: bool,
80    #[serde(default)]
81    pub continue_on_zero: bool,
82    #[serde(default)]
83    pub reverse: bool,
84}
85
86impl Default for DefaultConfig {
87    fn default() -> Self {
88        Self {
89            mode: default_mode(),
90            color: default_color(),
91            size: default_size(),
92        }
93    }
94}
95
96impl Default for ClockConfig {
97    fn default() -> Self {
98        Self {
99            show_date: default_true(),
100            show_seconds: default_true(),
101            show_millis: default_false(),
102            timezone: None,
103        }
104    }
105}
106
107impl Default for TimerConfig {
108    fn default() -> Self {
109        Self {
110            durations: default_timer_durations(),
111            titles: Vec::new(),
112            repeat: false,
113            show_millis: default_true(),
114            start_paused: false,
115            auto_quit: false,
116            execute: Vec::new(),
117        }
118    }
119}
120
121impl Default for StopwatchConfig {
122    fn default() -> Self {
123        Self {}
124    }
125}
126
127impl Default for CountdownConfig {
128    fn default() -> Self {
129        Self {
130            time: None,
131            title: None,
132            show_millis: false,
133            continue_on_zero: false,
134            reverse: false,
135        }
136    }
137}
138
139fn default_mode() -> String {
140    "clock".to_string()
141}
142
143fn default_color() -> String {
144    "green".to_string()
145}
146
147fn default_size() -> u16 {
148    1
149}
150
151fn default_true() -> bool {
152    true
153}
154
155fn default_false() -> bool {
156    false
157}
158
159fn default_timer_durations() -> Vec<String> {
160    vec!["25m".to_string(), "5m".to_string()]
161}
162
163impl Config {
164    pub fn load() -> Option<Self> {
165        // ~/.config/tclock/config.toml
166        let config_path = dirs::home_dir()?
167            .join(".config")
168            .join("tclock")
169            .join("config.toml");
170
171        if !config_path.exists() {
172            return None;
173        };
174
175        let content = std::fs::read_to_string(config_path).ok()?;
176        match toml::from_str(&content) {
177            Ok(config) => Some(config),
178            Err(e) => {
179                eprintln!("解析配置文件失败: {}", e);
180                None
181            }
182        }
183    }
184}