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