config_tools/
config.rs

1use crate::{builder::ConfigBuilder, error::Error};
2use ini::Ini;
3use std::collections::BTreeMap;
4
5pub trait Section: Sized {
6    fn from_section(map: &BTreeMap<String, String>) -> Result<Self, Error>;
7}
8
9#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
10pub struct Config {
11    pub sections: BTreeMap<String, BTreeMap<String, String>>,
12    pub general_values: BTreeMap<String, String>,
13}
14
15impl Config {
16    pub fn general(&self) -> &BTreeMap<String, String> {
17        &self.general_values
18    }
19
20    pub fn get(&self, section: Option<&str>, key: &str) -> Option<&String> {
21        if let Some(section) = section {
22            return self.sections.get(section).and_then(|s| s.get(key));
23        } else {
24            return self.general_values.get(key);
25        }
26    }
27
28    pub fn get_as<T>(&self, section: Option<&str>, key: &str) -> Option<T>
29    where
30        T: std::str::FromStr + std::fmt::Debug,
31    {
32        self.get(section, key).and_then(|v| v.parse().ok())
33    }
34
35    pub fn load(filename: &str) -> Result<Self, Error> {
36        let ini = Ini::load_from_file(filename).map_err(Error::ConfigLoad)?;
37        let mut sections = BTreeMap::new();
38        let mut general_values = BTreeMap::new();
39
40        for (section, prop) in ini.iter() {
41            if let Some(section) = section {
42                let mut section_map = BTreeMap::new();
43                prop.iter().for_each(|(key, value)| {
44                    section_map.insert(key.to_string(), value.to_string());
45                });
46
47                sections.insert(section.to_string(), section_map);
48            } else {
49                prop.iter().for_each(|(key, value)| {
50                    general_values.insert(key.to_string(), value.to_string());
51                })
52            }
53        }
54
55        Ok(Config {
56            sections,
57            general_values,
58        })
59    }
60
61    pub fn load_or_default(filename: &str, default: Config) -> Self {
62        match Self::load(filename) {
63            Ok(config) => config,
64            Err(_) => default,
65        }
66    }
67
68    pub fn builder() -> ConfigBuilder {
69        ConfigBuilder {
70            config: Config::default(),
71            section: None,
72        }
73    }
74
75    pub fn save(&self, filename: &str) -> Result<&Self, Error> {
76        let mut ini = Ini::new();
77
78        let mut section = ini.with_general_section();
79        for (key, value) in &self.general_values {
80            section.set(key, value);
81        }
82
83        for (title, prop) in &self.sections {
84            let mut section = ini.with_section(Some(title));
85            for (key, value) in prop {
86                section.set(key, value);
87            }
88        }
89
90        ini.write_to_file(filename).map_err(Error::ConfigCreation)?;
91
92        Ok(self)
93    }
94
95    pub fn section(&self, title: &str) -> Option<&BTreeMap<String, String>> {
96        self.sections.get(title)
97    }
98
99    pub fn sections(&self) -> &BTreeMap<String, BTreeMap<String, String>> {
100        &self.sections
101    }
102
103    pub fn update(&mut self, section: Option<&str>, key: &str, value: &str) -> &mut Self {
104        if let Some(section) = section {
105            self.sections
106                .entry(section.to_string())
107                .or_default()
108                .insert(key.to_string(), value.to_string());
109        } else {
110            self.general_values
111                .insert(key.to_string(), value.to_string());
112        }
113
114        self
115    }
116}