1use anyhow::{Context, Result, anyhow};
2
3use serde::Deserialize;
4
5use std::path::PathBuf;
6
7#[derive(Debug, Default, Deserialize)]
31#[serde(default)]
32pub struct Config {
33 pub format: Option<crate::ByteFormat>,
35
36 pub keys: KeysConfig,
38
39 pub gitignore: Option<bool>,
44
45 pub cleanup_heuristics: Option<bool>,
50}
51
52#[derive(Debug, Deserialize)]
54#[serde(default)]
55pub struct KeysConfig {
56 #[serde(default = "default_esc_navigates_back")]
63 pub esc_navigates_back: bool,
64}
65
66fn default_esc_navigates_back() -> bool {
67 true
68}
69
70impl Default for KeysConfig {
71 fn default() -> Self {
72 Self {
73 esc_navigates_back: default_esc_navigates_back(),
74 }
75 }
76}
77
78impl Config {
79 pub fn load() -> Result<Self> {
89 let Ok(path) = Self::path() else {
90 log::info!("Configuration path couldn't be determined. Using defaults.");
91 return Ok(Config::default());
92 };
93
94 let contents = match std::fs::read_to_string(&path) {
95 Ok(c) => c,
96 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
97 log::info!(
98 "Configuration not loaded from {}: file not found. Using defaults.",
99 path.display()
100 );
101 return Ok(Config::default());
102 }
103 Err(e) => {
104 return Err(e)
105 .with_context(|| format!("Failed to read config at {}", path.display()));
106 }
107 };
108
109 toml::from_str(&contents)
110 .with_context(|| format!("Failed to parse config at {}", path.display()))
111 }
112
113 pub fn default_file_content() -> &'static str {
115 concat!(
116 "# dua-cli configuration\n",
117 "#\n",
118 "# Byte count format to use when --format and DUA_FORMAT are not set.\n",
119 "# Supported values: metric, binary, bytes, gb, gib, mb, mib.\n",
120 "# format = \"binary\"\n",
121 "#\n",
122 "# Controls whether Git-ignored entry detection is enabled in interactive mode.\n",
123 "# Supported values: true, false.\n",
124 "# If unset, behavior defaults to true.\n",
125 "# gitignore = true\n",
126 "#\n",
127 "# Controls whether cleanup heuristics are enabled in interactive mode.\n",
128 "# Supported values: true, false.\n",
129 "# If unset, behavior defaults to true.\n",
130 "# cleanup_heuristics = true\n",
131 "#\n",
132 "[keys]\n",
133 "# If true, pressing <Esc> in the main pane ascends to the parent directory.\n",
134 "# If false, <Esc> follows the default quit behavior.\n",
135 "esc_navigates_back = true\n",
136 )
137 }
138
139 pub fn path() -> Result<PathBuf> {
148 let config_dir = dirs::config_dir()
151 .ok_or_else(|| anyhow!("platform config directory is unavailable"))?;
152 Ok(config_dir.join("dua-cli").join("config.toml"))
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::Config;
159
160 #[test]
161 fn parses_configured_byte_format() {
162 let config: Config = toml::from_str(
163 r#"
164 format = "mb"
165
166 [keys]
167 esc_navigates_back = false
168 "#,
169 )
170 .expect("valid config");
171
172 assert_eq!(config.format, Some(crate::ByteFormat::MB));
173 assert!(!config.keys.esc_navigates_back);
174 }
175
176 #[test]
177 fn parses_configured_gitignore() {
178 let config: Config = toml::from_str(
179 r#"
180 format = "mb"
181 gitignore = false
182
183 [keys]
184 esc_navigates_back = false
185 "#,
186 )
187 .expect("valid config");
188
189 assert_eq!(config.gitignore, Some(false));
190 }
191
192 #[test]
193 fn gitignore_defaults_to_enabled() {
194 let config: Config = toml::from_str(
195 r#"
196 format = "mb"
197
198 [keys]
199 esc_navigates_back = false
200 "#,
201 )
202 .expect("valid config");
203
204 assert_eq!(config.gitignore, None);
205 }
206
207 #[test]
208 fn parses_configured_cleanup_heuristics() {
209 let config: Config = toml::from_str(
210 r#"
211 format = "mb"
212 cleanup_heuristics = false
213
214 [keys]
215 esc_navigates_back = false
216 "#,
217 )
218 .expect("valid config");
219
220 assert_eq!(config.cleanup_heuristics, Some(false));
221 }
222
223 #[test]
224 fn cleanup_heuristics_defaults_to_enabled() {
225 let config: Config = toml::from_str(
226 r#"
227 format = "mb"
228
229 [keys]
230 esc_navigates_back = false
231 "#,
232 )
233 .expect("valid config");
234
235 assert_eq!(config.cleanup_heuristics, None);
236 }
237}