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