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}