1use std::path::{Path, PathBuf};
2
3use serde::Deserialize;
4
5#[derive(Debug, Deserialize)]
11#[serde(deny_unknown_fields)]
12struct ConfigFile {
13 dir: Option<String>,
14 format: Option<String>,
15 hints: Option<bool>,
16 site_prefix: Option<String>,
20}
21
22#[derive(Debug, PartialEq)]
24pub struct ResolvedDefaults {
25 pub dir: PathBuf,
26 pub format: String,
27 pub hints: bool,
28 pub site_prefix: Option<String>,
30}
31
32impl ResolvedDefaults {
33 fn hardcoded() -> Self {
34 Self {
35 dir: PathBuf::from("."),
36 format: "json".to_owned(),
37 hints: true,
38 site_prefix: None,
39 }
40 }
41}
42
43pub fn load_config() -> ResolvedDefaults {
50 match std::env::current_dir() {
51 Ok(cwd) => load_config_from(&cwd),
52 Err(e) => {
53 crate::warn::warn(format!(
54 "could not determine current directory to locate .hyalo.toml: {e}"
55 ));
56 ResolvedDefaults::hardcoded()
57 }
58 }
59}
60
61pub fn load_config_from(dir: &Path) -> ResolvedDefaults {
66 let path = dir.join(".hyalo.toml");
67
68 let contents = match std::fs::read_to_string(&path) {
69 Ok(s) => s,
70 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
71 return ResolvedDefaults::hardcoded();
72 }
73 Err(e) => {
74 crate::warn::warn(format!("could not read .hyalo.toml: {e}"));
75 return ResolvedDefaults::hardcoded();
76 }
77 };
78
79 let cfg: ConfigFile = match toml::from_str(&contents) {
80 Ok(c) => c,
81 Err(e) => {
82 crate::warn::warn(format!("malformed .hyalo.toml: {e}"));
83 return ResolvedDefaults::hardcoded();
84 }
85 };
86
87 let defaults = ResolvedDefaults::hardcoded();
88 ResolvedDefaults {
89 dir: cfg.dir.map(PathBuf::from).unwrap_or(defaults.dir),
90 format: cfg.format.unwrap_or(defaults.format),
91 hints: cfg.hints.unwrap_or(defaults.hints),
92 site_prefix: cfg.site_prefix,
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use std::fs;
99
100 use tempfile::TempDir;
101
102 use super::*;
103
104 fn make_temp() -> TempDir {
105 tempfile::tempdir().expect("failed to create temp dir")
106 }
107
108 #[test]
109 fn missing_config_returns_defaults() {
110 let dir = make_temp();
111 let resolved = load_config_from(dir.path());
112 assert_eq!(resolved, ResolvedDefaults::hardcoded());
113 }
114
115 #[test]
116 fn valid_full_config() {
117 let dir = make_temp();
118 fs::write(
119 dir.path().join(".hyalo.toml"),
120 r#"
121dir = "notes"
122format = "text"
123hints = true
124"#,
125 )
126 .unwrap();
127
128 let resolved = load_config_from(dir.path());
129 assert_eq!(resolved.dir, PathBuf::from("notes"));
130 assert_eq!(resolved.format, "text");
131 assert!(resolved.hints);
132 assert_eq!(resolved.site_prefix, None);
133 }
134
135 #[test]
136 fn site_prefix_config() {
137 let dir = make_temp();
138 fs::write(
139 dir.path().join(".hyalo.toml"),
140 r#"dir = "docs"
141site_prefix = "docs"
142"#,
143 )
144 .unwrap();
145
146 let resolved = load_config_from(dir.path());
147 assert_eq!(resolved.dir, PathBuf::from("docs"));
148 assert_eq!(resolved.site_prefix, Some("docs".to_owned()));
149 }
150
151 #[test]
152 fn partial_config_merges_with_defaults() {
153 let dir = make_temp();
154 fs::write(dir.path().join(".hyalo.toml"), "hints = false\n").unwrap();
155
156 let resolved = load_config_from(dir.path());
157 assert_eq!(resolved.dir, PathBuf::from("."));
159 assert_eq!(resolved.format, "json");
160 assert!(
161 !resolved.hints,
162 "config should override the default (true) to false"
163 );
164 }
165
166 #[test]
167 fn malformed_toml_returns_defaults() {
168 let dir = make_temp();
169 fs::write(dir.path().join(".hyalo.toml"), "this is not { valid toml").unwrap();
170
171 let resolved = load_config_from(dir.path());
172 assert_eq!(resolved, ResolvedDefaults::hardcoded());
173 }
174
175 #[test]
176 fn unknown_fields_returns_defaults() {
177 let dir = make_temp();
178 fs::write(dir.path().join(".hyalo.toml"), "unknown_key = \"value\"\n").unwrap();
179
180 let resolved = load_config_from(dir.path());
181 assert_eq!(resolved, ResolvedDefaults::hardcoded());
182 }
183
184 #[test]
185 fn invalid_format_value_passed_through() {
186 let dir = make_temp();
187 fs::write(dir.path().join(".hyalo.toml"), "format = \"xml\"\n").unwrap();
188
189 let resolved = load_config_from(dir.path());
191 assert_eq!(resolved.format, "xml");
192 assert_eq!(resolved.dir, PathBuf::from("."));
193 assert!(resolved.hints);
194 }
195}