1use std::collections::{BTreeMap, BTreeSet};
2
3use toml_edit::{Decor, DocumentMut, Item, Table, Value};
4
5use crate::{
6 ConfigErr, ConfigResult, ConfigType, ConfigValue,
7 output::{Output, OutputFormat},
8};
9
10type ConfigTable = BTreeMap<String, ConfigItem>;
11
12#[derive(Debug, Clone)]
16pub struct ConfigItem {
17 table_name: String,
18 key: String,
19 value: ConfigValue,
20 comments: String,
21}
22
23impl ConfigItem {
24 fn new(table_name: &str, table: &Table, key: &str, value: &Value) -> ConfigResult<Self> {
25 let inner = || {
26 let item = table.key(key).unwrap();
27 let comments = prefix_comments(item.leaf_decor())
28 .unwrap_or_default()
29 .to_string();
30 let suffix = suffix_comments(value.decor()).unwrap_or_default().trim();
31 let value = if !suffix.is_empty() {
32 let ty_str = suffix.trim_start_matches('#');
33 let ty = ConfigType::new(ty_str)?;
34 ConfigValue::from_raw_value_type(value, ty)?
35 } else {
36 ConfigValue::from_raw_value(value)?
37 };
38 Ok(Self {
39 table_name: table_name.into(),
40 key: key.into(),
41 value,
42 comments,
43 })
44 };
45 let res = inner();
46 if let Err(e) = &res {
47 eprintln!("Parsing error at key `{}`: {:?}", key, e);
48 }
49 res
50 }
51
52 fn new_global(table: &Table, key: &str, value: &Value) -> ConfigResult<Self> {
53 Self::new(Config::GLOBAL_TABLE_NAME, table, key, value)
54 }
55
56 pub fn item_name(&self) -> String {
61 if self.table_name == Config::GLOBAL_TABLE_NAME {
62 self.key.clone()
63 } else {
64 format!("{}.{}", self.table_name, self.key)
65 }
66 }
67
68 pub fn table_name(&self) -> &str {
70 &self.table_name
71 }
72
73 pub fn key(&self) -> &str {
75 &self.key
76 }
77
78 pub fn value(&self) -> &ConfigValue {
80 &self.value
81 }
82
83 pub fn comments(&self) -> &str {
85 &self.comments
86 }
87
88 pub fn value_mut(&mut self) -> &mut ConfigValue {
90 &mut self.value
91 }
92}
93
94#[derive(Default, Debug)]
99pub struct Config {
100 global: ConfigTable,
101 tables: BTreeMap<String, ConfigTable>,
102 table_comments: BTreeMap<String, String>,
103}
104
105impl Config {
106 pub const GLOBAL_TABLE_NAME: &'static str = "$GLOBAL";
108
109 pub fn new() -> Self {
111 Self {
112 global: ConfigTable::new(),
113 tables: BTreeMap::new(),
114 table_comments: BTreeMap::new(),
115 }
116 }
117
118 pub fn is_empty(&self) -> bool {
120 self.global.is_empty() && self.tables.is_empty()
121 }
122
123 fn new_table(&mut self, name: &str, comments: &str) -> ConfigResult<&mut ConfigTable> {
124 if name == Self::GLOBAL_TABLE_NAME {
125 return Err(ConfigErr::Other(format!(
126 "Table name `{}` is reserved",
127 Self::GLOBAL_TABLE_NAME
128 )));
129 }
130 if self.tables.contains_key(name) {
131 return Err(ConfigErr::Other(format!("Duplicate table name `{}`", name)));
132 }
133 self.tables.insert(name.into(), ConfigTable::new());
134 self.table_comments.insert(name.into(), comments.into());
135 Ok(self.tables.get_mut(name).unwrap())
136 }
137
138 pub fn global_table(&self) -> &BTreeMap<String, ConfigItem> {
140 &self.global
141 }
142
143 pub fn table_at(&self, name: &str) -> Option<&BTreeMap<String, ConfigItem>> {
145 if name == Self::GLOBAL_TABLE_NAME {
146 Some(&self.global)
147 } else {
148 self.tables.get(name)
149 }
150 }
151
152 pub fn table_at_mut(&mut self, name: &str) -> Option<&mut BTreeMap<String, ConfigItem>> {
154 if name == Self::GLOBAL_TABLE_NAME {
155 Some(&mut self.global)
156 } else {
157 self.tables.get_mut(name)
158 }
159 }
160
161 pub fn config_at(&self, table: &str, key: &str) -> Option<&ConfigItem> {
163 self.table_at(table).and_then(|t| t.get(key))
164 }
165
166 pub fn config_at_mut(&mut self, table: &str, key: &str) -> Option<&mut ConfigItem> {
169 self.table_at_mut(table).and_then(|t| t.get_mut(key))
170 }
171
172 pub fn table_comments_at(&self, name: &str) -> Option<&str> {
174 self.table_comments.get(name).map(|s| s.as_str())
175 }
176
177 pub fn table_iter(&self) -> impl Iterator<Item = (&str, &ConfigTable, &str)> {
182 let global_iter = [(Self::GLOBAL_TABLE_NAME, &self.global, "")].into_iter();
183 let other_iter = self.tables.iter().map(|(name, configs)| {
184 (
185 name.as_str(),
186 configs,
187 self.table_comments.get(name).unwrap().as_str(),
188 )
189 });
190 global_iter.chain(other_iter)
191 }
192
193 pub fn iter(&self) -> impl Iterator<Item = &ConfigItem> {
198 self.table_iter().flat_map(|(_, c, _)| c.values())
199 }
200}
201
202impl Config {
203 pub fn from_toml(toml: &str) -> ConfigResult<Self> {
205 let doc = toml.parse::<DocumentMut>()?;
206 let table = doc.as_table();
207
208 let mut result = Self::new();
209 for (key, item) in table.iter() {
210 match item {
211 Item::Value(val) => {
212 result
213 .global
214 .insert(key.into(), ConfigItem::new_global(table, key, val)?);
215 }
216 Item::Table(table) => {
217 let table_name = key;
218 let comments = prefix_comments(table.decor());
219 let configs = result.new_table(key, comments.unwrap_or_default())?;
220 for (key, item) in table.iter() {
221 if let Item::Value(val) = item {
222 configs
223 .insert(key.into(), ConfigItem::new(table_name, table, key, val)?);
224 } else {
225 return Err(ConfigErr::InvalidValue);
226 }
227 }
228 }
229 Item::None => {}
230 _ => {
231 return Err(ConfigErr::Other(format!(
232 "Object array `[[{}]]` is not supported",
233 key
234 )));
235 }
236 }
237 }
238 Ok(result)
239 }
240
241 pub fn dump(&self, fmt: OutputFormat) -> ConfigResult<String> {
243 let mut output = Output::new(fmt);
244 for (name, table, comments) in self.table_iter() {
245 if name != Self::GLOBAL_TABLE_NAME {
246 output.table_begin(name, comments);
247 }
248 for (key, item) in table.iter() {
249 if let Err(e) = output.write_item(item) {
250 eprintln!("Dump config `{}` failed: {:?}", key, e);
251 }
252 }
253 if name != Self::GLOBAL_TABLE_NAME {
254 output.table_end();
255 }
256 }
257 Ok(output.result().into())
258 }
259
260 pub fn dump_toml(&self) -> ConfigResult<String> {
262 self.dump(OutputFormat::Toml)
263 }
264
265 pub fn dump_rs(&self) -> ConfigResult<String> {
267 self.dump(OutputFormat::Rust)
268 }
269
270 pub fn merge(&mut self, other: &Self) -> ConfigResult<()> {
272 for (name, other_table, table_comments) in other.table_iter() {
273 let self_table = if let Some(table) = self.table_at_mut(name) {
274 table
275 } else {
276 self.new_table(name, table_comments)?
277 };
278 for (key, item) in other_table.iter() {
279 if self_table.contains_key(key) {
280 return Err(ConfigErr::Other(format!("Duplicate key `{}`", key)));
281 } else {
282 self_table.insert(key.into(), item.clone());
283 }
284 }
285 }
286 Ok(())
287 }
288
289 pub fn update(&mut self, other: &Self) -> ConfigResult<(Vec<ConfigItem>, Vec<ConfigItem>)> {
296 let mut touched = BTreeSet::new(); let mut extra = Vec::new(); for other_item in other.iter() {
300 let table_name = other_item.table_name.clone();
301 let key = other_item.key.clone();
302 let self_table = if let Some(table) = self.table_at_mut(&table_name) {
303 table
304 } else {
305 extra.push(other_item.clone());
306 continue;
307 };
308
309 if let Some(self_item) = self_table.get_mut(&key) {
310 self_item.value.update(other_item.value.clone())?;
311 touched.insert(self_item.item_name());
312 } else {
313 extra.push(other_item.clone());
314 }
315 }
316
317 let untouched = self
319 .iter()
320 .filter(|item| !touched.contains(&item.item_name()))
321 .cloned()
322 .collect::<Vec<_>>();
323 Ok((untouched, extra))
324 }
325}
326
327fn prefix_comments(decor: &Decor) -> Option<&str> {
328 decor.prefix().and_then(|s| s.as_str())
329}
330
331fn suffix_comments(decor: &Decor) -> Option<&str> {
332 decor.suffix().and_then(|s| s.as_str())
333}