1use std::path::PathBuf;
8
9use serde::{Deserialize, Serialize};
10
11use crate::error::{Error, Result};
12
13#[derive(Debug, Default, Clone, Serialize, Deserialize)]
15#[serde(default)]
16pub struct Config {
17 pub style: Option<crate::style::Style>,
19 pub volume: Option<f32>,
21 pub times: Option<u32>,
23}
24
25impl Config {
26 pub fn config_path() -> Result<PathBuf> {
29 if let Some(dir) = std::env::var_os("HONK_CONFIG_DIR") {
30 return Ok(PathBuf::from(dir).join("config.yaml"));
31 }
32 let dir = dirs::config_dir().ok_or(Error::NoConfigDir)?;
33 Ok(dir.join("honk").join("config.yaml"))
34 }
35
36 pub fn load() -> Result<Self> {
38 let path = Self::config_path()?;
39 if !path.exists() {
40 return Ok(Self::default());
41 }
42 let yaml = std::fs::read_to_string(&path)?;
43 Ok(serde_norway::from_str(&yaml)?)
44 }
45
46 pub fn save(&self) -> Result<()> {
48 let path = Self::config_path()?;
49 if let Some(parent) = path.parent() {
50 std::fs::create_dir_all(parent)?;
51 }
52 let yaml = serde_norway::to_string(self)?;
53 std::fs::write(&path, yaml)?;
54 Ok(())
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use crate::style::Style;
62
63 #[test]
64 fn round_trips_through_yaml() {
65 let cfg = Config {
66 style: Some(Style::Awooga),
67 volume: Some(0.5),
68 times: Some(3),
69 };
70 let yaml = serde_norway::to_string(&cfg).unwrap();
71 let parsed: Config = serde_norway::from_str(&yaml).unwrap();
72 assert_eq!(parsed.style, Some(Style::Awooga));
73 assert_eq!(parsed.volume, Some(0.5));
74 assert_eq!(parsed.times, Some(3));
75 }
76
77 #[test]
78 fn serialized_output_is_stable() {
79 let cfg = Config {
82 style: Some(Style::Car),
83 volume: Some(0.5),
84 times: Some(2),
85 };
86 assert_eq!(
87 serde_norway::to_string(&cfg).unwrap(),
88 "style: car\nvolume: 0.5\ntimes: 2\n"
89 );
90 assert_eq!(
91 serde_norway::to_string(&Config::default()).unwrap(),
92 "style: null\nvolume: null\ntimes: null\n"
93 );
94 }
95
96 #[test]
97 fn empty_yaml_uses_defaults() {
98 let parsed: Config = serde_norway::from_str("{}").unwrap();
99 assert!(parsed.style.is_none() && parsed.volume.is_none() && parsed.times.is_none());
100 }
101}