1use std::path::Path;
13
14use figment::{
15 providers::{Env, Format, Serialized, Toml},
16 Figment,
17};
18use serde::{Deserialize, Serialize};
19
20use crate::error::{CoreError, Result};
21use crate::paths::Paths;
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct Settings {
25 #[serde(default)]
28 pub default_issuer: Option<String>,
29
30 #[serde(default = "default_template")]
33 pub default_template: String,
34
35 #[serde(default = "default_true")]
37 pub open_pdf: bool,
38
39 #[serde(default = "default_true")]
42 pub self_update: bool,
43}
44
45fn default_template() -> String {
46 "vienna".to_string()
47}
48
49fn default_true() -> bool {
50 true
51}
52
53impl Default for Settings {
54 fn default() -> Self {
55 Self {
56 default_issuer: None,
57 default_template: default_template(),
58 open_pdf: true,
59 self_update: true,
60 }
61 }
62}
63
64impl Settings {
65 pub fn load(paths: &Paths) -> Result<Self> {
69 Self::load_from(&paths.config_file())
70 }
71
72 pub fn load_from(path: &Path) -> Result<Self> {
73 let settings: Settings = Figment::from(Serialized::defaults(Settings::default()))
74 .merge(Toml::file(path))
75 .merge(Env::prefixed("PAPERFOOT_"))
76 .extract()
77 .map_err(|e| CoreError::Config(format!("{e}")))?;
78 Ok(settings)
79 }
80
81 pub fn save(&self, paths: &Paths) -> Result<()> {
83 self.save_to(&paths.config_file())
84 }
85
86 pub fn save_to(&self, path: &Path) -> Result<()> {
87 if let Some(parent) = path.parent() {
88 std::fs::create_dir_all(parent)?;
89 }
90 let serialized = toml::to_string_pretty(self)?;
91 std::fs::write(path, serialized)?;
92 Ok(())
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use tempfile::TempDir;
100
101 #[test]
102 fn defaults_when_missing() {
103 let tmp = TempDir::new().unwrap();
104 let path = tmp.path().join("config.toml");
105 let s = Settings::load_from(&path).unwrap();
106 assert_eq!(s.default_template, "vienna");
107 assert!(s.open_pdf);
108 assert!(s.self_update);
109 assert!(s.default_issuer.is_none());
110 }
111
112 #[test]
113 fn roundtrip_through_toml() {
114 let tmp = TempDir::new().unwrap();
115 let path = tmp.path().join("config.toml");
116 let written = Settings {
117 default_issuer: Some("acme".into()),
118 default_template: "boutique".into(),
119 open_pdf: false,
120 self_update: true,
121 };
122 written.save_to(&path).unwrap();
123 let read = Settings::load_from(&path).unwrap();
124 assert_eq!(read.default_issuer.as_deref(), Some("acme"));
125 assert_eq!(read.default_template, "boutique");
126 assert!(!read.open_pdf);
127 assert!(read.self_update);
128 }
129}