1use std::{fs, io};
2
3use serde::{Deserialize, Serialize};
4use toml_edit::{DocumentMut, Item, Table};
5
6use crate::config_dir;
7
8pub mod common;
9
10#[derive(Debug, thiserror::Error)]
11pub enum ConfigError {
12 #[error("IoError: {0}")]
13 Io(#[from] std::io::Error),
14
15 #[error("No home directory was found for the current user")]
16 NoHomeDir,
17
18 #[error("TomlError: {0}")]
19 TomlEdit(#[from] toml_edit::TomlError),
20
21 #[error("TomlError: {0}")]
22 Toml(#[from] toml::de::Error),
23}
24
25pub trait Config: Sized + Serialize + for<'de> Deserialize<'de> + Default {
26 const CONFIG_FILE_NAME: &'static str;
28
29 fn load() -> Result<Self, ConfigError> {
31 let mut file = config_dir().join(Self::CONFIG_FILE_NAME);
32 file.add_extension("toml");
33
34 let config = match std::fs::read_to_string(&file) {
35 Ok(toml) => toml::from_str(&toml)?,
36 Err(err) if matches![err.kind(), io::ErrorKind::NotFound] => {
37 let config = Self::default();
38 config.save()?;
39 config
40 }
41 Err(err) => return Err(err.into()),
42 };
43
44 Ok(config)
45 }
46
47 fn save(&self) -> Result<(), ConfigError> {
54 let config_dir = config_dir();
55 let mut file = config_dir.join(Self::CONFIG_FILE_NAME);
56 file.add_extension("toml");
57
58 let config_doc = toml_edit::ser::to_document(self).unwrap();
59 let mut disk_doc: DocumentMut = if file.exists() {
60 fs::read_to_string(&file)?.parse()?
61 } else {
62 DocumentMut::new()
63 };
64
65 write_needed_config_values(disk_doc.as_table_mut(), config_doc.as_table());
66
67 fs::create_dir_all(config_dir)?;
68 fs::write(file, disk_doc.to_string())?;
69
70 Ok(())
71 }
72}
73
74fn write_needed_config_values(disk: &mut Table, current_table: &Table) {
75 for (key, value) in current_table {
76 match value {
77 Item::Table(table) => {
78 let disk_table = disk
79 .entry(key)
80 .or_insert(Item::Table(Table::new()))
81 .as_table_mut()
82 .unwrap();
83 write_needed_config_values(disk_table, table);
84 }
85 _ => {
86 disk.insert(key, value.clone());
87 }
88 }
89 }
90}