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