|
2 | 2 | // Licensed under the Apache License, Version 2.0 (see LICENSE).
|
3 | 3 |
|
4 | 4 | use std::collections::{HashMap, HashSet};
|
5 |
| -use std::ffi::OsString; |
6 | 5 | use std::fs;
|
7 | 6 | use std::path::Path;
|
8 | 7 |
|
@@ -216,133 +215,16 @@ fn toml_table_to_dict(table: &Value) -> HashMap<String, Val> {
|
216 | 215 | }
|
217 | 216 | }
|
218 | 217 |
|
219 |
| -#[derive(Clone)] |
220 |
| -struct ConfigSource { |
221 |
| - #[allow(dead_code)] |
222 |
| - path: Option<OsString>, |
223 |
| - config: Value, |
224 |
| -} |
225 |
| - |
226 |
| -impl ConfigSource { |
227 |
| - fn option_name(id: &OptionId) -> String { |
228 |
| - id.name("_", NameTransform::None) |
229 |
| - } |
230 |
| - |
231 |
| - fn get_value(&self, id: &OptionId) -> Option<&Value> { |
232 |
| - self.config |
233 |
| - .get(id.scope()) |
234 |
| - .and_then(|table| table.get(Self::option_name(id))) |
235 |
| - } |
236 |
| - |
237 |
| - fn get_list<T: FromValue>( |
238 |
| - &self, |
239 |
| - id: &OptionId, |
240 |
| - parse_list: fn(&str) -> Result<Vec<ListEdit<T>>, ParseError>, |
241 |
| - ) -> Result<Vec<ListEdit<T>>, String> { |
242 |
| - let mut list_edits = vec![]; |
243 |
| - if let Some(table) = self.config.get(id.scope()) { |
244 |
| - let option_name = Self::option_name(id); |
245 |
| - if let Some(value) = table.get(&option_name) { |
246 |
| - match value { |
247 |
| - Value::Table(sub_table) => { |
248 |
| - if sub_table.is_empty() |
249 |
| - || !sub_table.keys().collect::<HashSet<_>>().is_subset( |
250 |
| - &["add".to_owned(), "remove".to_owned()] |
251 |
| - .iter() |
252 |
| - .collect::<HashSet<_>>(), |
253 |
| - ) |
254 |
| - { |
255 |
| - return Err(format!( |
256 |
| - "Expected {option_name} to contain an 'add' element, a 'remove' element or both but found: {sub_table:?}" |
257 |
| - )); |
258 |
| - } |
259 |
| - if let Some(add) = sub_table.get("add") { |
260 |
| - list_edits.push(ListEdit { |
261 |
| - action: ListEditAction::Add, |
262 |
| - items: T::extract_list(&format!("{option_name}.add"), add)?, |
263 |
| - }) |
264 |
| - } |
265 |
| - if let Some(remove) = sub_table.get("remove") { |
266 |
| - list_edits.push(ListEdit { |
267 |
| - action: ListEditAction::Remove, |
268 |
| - items: T::extract_list(&format!("{option_name}.remove"), remove)?, |
269 |
| - }) |
270 |
| - } |
271 |
| - } |
272 |
| - Value::String(v) => { |
273 |
| - list_edits.extend(parse_list(v).map_err(|e| e.render(option_name))?); |
274 |
| - } |
275 |
| - value => list_edits.push(ListEdit { |
276 |
| - action: ListEditAction::Replace, |
277 |
| - items: T::extract_list(&option_name, value)?, |
278 |
| - }), |
279 |
| - } |
280 |
| - } |
281 |
| - } |
282 |
| - Ok(list_edits) |
283 |
| - } |
284 |
| - |
285 |
| - fn get_dict(&self, id: &OptionId) -> Result<Option<DictEdit>, String> { |
286 |
| - if let Some(table) = self.config.get(id.scope()) { |
287 |
| - let option_name = Self::option_name(id); |
288 |
| - if let Some(value) = table.get(&option_name) { |
289 |
| - match value { |
290 |
| - Value::Table(sub_table) => { |
291 |
| - if let Some(add) = sub_table.get("add") { |
292 |
| - if sub_table.len() == 1 && add.is_table() { |
293 |
| - return Ok(Some(DictEdit { |
294 |
| - action: DictEditAction::Add, |
295 |
| - items: toml_table_to_dict(add), |
296 |
| - })); |
297 |
| - } |
298 |
| - } |
299 |
| - return Ok(Some(DictEdit { |
300 |
| - action: DictEditAction::Replace, |
301 |
| - items: toml_table_to_dict(value), |
302 |
| - })); |
303 |
| - } |
304 |
| - Value::String(v) => { |
305 |
| - return Ok(Some(parse_dict(v).map_err(|e| e.render(option_name))?)); |
306 |
| - } |
307 |
| - _ => { |
308 |
| - return Err(format!( |
309 |
| - "Expected {option_name} to be a toml table or Python dict, but given {value}." |
310 |
| - )); |
311 |
| - } |
312 |
| - } |
313 |
| - } |
314 |
| - } |
315 |
| - Ok(None) |
316 |
| - } |
317 |
| -} |
318 |
| - |
319 | 218 | #[derive(Clone)]
|
320 | 219 | pub(crate) struct Config {
|
321 |
| - sources: Vec<ConfigSource>, |
| 220 | + value: Value, |
322 | 221 | }
|
323 | 222 |
|
324 | 223 | impl Config {
|
325 | 224 | pub(crate) fn parse<P: AsRef<Path>>(
|
326 |
| - files: &[P], |
327 |
| - seed_values: &InterpolationMap, |
328 |
| - ) -> Result<Config, String> { |
329 |
| - let mut sources = vec![]; |
330 |
| - for file in files { |
331 |
| - sources.push(Self::parse_source(file, seed_values)?); |
332 |
| - } |
333 |
| - Ok(Config { sources }) |
334 |
| - } |
335 |
| - |
336 |
| - pub(crate) fn merge(self, other: Config) -> Config { |
337 |
| - Config { |
338 |
| - sources: self.sources.into_iter().chain(other.sources).collect(), |
339 |
| - } |
340 |
| - } |
341 |
| - |
342 |
| - fn parse_source<P: AsRef<Path>>( |
343 | 225 | file: P,
|
344 | 226 | seed_values: &InterpolationMap,
|
345 |
| - ) -> Result<ConfigSource, String> { |
| 227 | + ) -> Result<Config, String> { |
346 | 228 | let config_contents = fs::read_to_string(&file).map_err(|e| {
|
347 | 229 | format!(
|
348 | 230 | "Failed to read config file {}: {}",
|
@@ -419,29 +301,67 @@ impl Config {
|
419 | 301 | };
|
420 | 302 |
|
421 | 303 | let new_table = Table::from_iter(new_sections?);
|
422 |
| - Ok(ConfigSource { |
423 |
| - path: Some(file.as_ref().as_os_str().into()), |
424 |
| - config: Value::Table(new_table), |
| 304 | + Ok(Self { |
| 305 | + value: Value::Table(new_table), |
425 | 306 | })
|
426 | 307 | }
|
427 | 308 |
|
| 309 | + fn option_name(id: &OptionId) -> String { |
| 310 | + id.name("_", NameTransform::None) |
| 311 | + } |
| 312 | + |
428 | 313 | fn get_value(&self, id: &OptionId) -> Option<&Value> {
|
429 |
| - self.sources |
430 |
| - .iter() |
431 |
| - .rev() |
432 |
| - .find_map(|source| source.get_value(id)) |
| 314 | + self.value |
| 315 | + .get(id.scope()) |
| 316 | + .and_then(|table| table.get(Self::option_name(id))) |
433 | 317 | }
|
434 | 318 |
|
435 | 319 | fn get_list<T: FromValue>(
|
436 | 320 | &self,
|
437 | 321 | id: &OptionId,
|
438 | 322 | parse_list: fn(&str) -> Result<Vec<ListEdit<T>>, ParseError>,
|
439 | 323 | ) -> Result<Option<Vec<ListEdit<T>>>, String> {
|
440 |
| - let mut edits: Vec<ListEdit<T>> = vec![]; |
441 |
| - for source in self.sources.iter() { |
442 |
| - edits.append(&mut source.get_list(id, parse_list)?); |
| 324 | + let mut list_edits = vec![]; |
| 325 | + if let Some(table) = self.value.get(id.scope()) { |
| 326 | + let option_name = Self::option_name(id); |
| 327 | + if let Some(value) = table.get(&option_name) { |
| 328 | + match value { |
| 329 | + Value::Table(sub_table) => { |
| 330 | + if sub_table.is_empty() |
| 331 | + || !sub_table.keys().collect::<HashSet<_>>().is_subset( |
| 332 | + &["add".to_owned(), "remove".to_owned()] |
| 333 | + .iter() |
| 334 | + .collect::<HashSet<_>>(), |
| 335 | + ) |
| 336 | + { |
| 337 | + return Err(format!( |
| 338 | + "Expected {option_name} to contain an 'add' element, a 'remove' element or both but found: {sub_table:?}" |
| 339 | + )); |
| 340 | + } |
| 341 | + if let Some(add) = sub_table.get("add") { |
| 342 | + list_edits.push(ListEdit { |
| 343 | + action: ListEditAction::Add, |
| 344 | + items: T::extract_list(&format!("{option_name}.add"), add)?, |
| 345 | + }) |
| 346 | + } |
| 347 | + if let Some(remove) = sub_table.get("remove") { |
| 348 | + list_edits.push(ListEdit { |
| 349 | + action: ListEditAction::Remove, |
| 350 | + items: T::extract_list(&format!("{option_name}.remove"), remove)?, |
| 351 | + }) |
| 352 | + } |
| 353 | + } |
| 354 | + Value::String(v) => { |
| 355 | + list_edits.extend(parse_list(v).map_err(|e| e.render(option_name))?); |
| 356 | + } |
| 357 | + value => list_edits.push(ListEdit { |
| 358 | + action: ListEditAction::Replace, |
| 359 | + items: T::extract_list(&option_name, value)?, |
| 360 | + }), |
| 361 | + } |
| 362 | + } |
443 | 363 | }
|
444 |
| - Ok(Some(edits)) |
| 364 | + Ok(Some(list_edits)) |
445 | 365 | }
|
446 | 366 | }
|
447 | 367 |
|
@@ -482,13 +402,36 @@ impl OptionsSource for Config {
|
482 | 402 | self.get_list(id, parse_string_list)
|
483 | 403 | }
|
484 | 404 |
|
485 |
| - fn get_dict(&self, id: &OptionId) -> Result<Option<Vec<DictEdit>>, String> { |
486 |
| - let mut edits = vec![]; |
487 |
| - for source in self.sources.iter() { |
488 |
| - if let Some(edit) = source.get_dict(id)? { |
489 |
| - edits.push(edit); |
| 405 | + fn get_dict(&self, id: &OptionId) -> Result<Option<DictEdit>, String> { |
| 406 | + if let Some(table) = self.value.get(id.scope()) { |
| 407 | + let option_name = Self::option_name(id); |
| 408 | + if let Some(value) = table.get(&option_name) { |
| 409 | + match value { |
| 410 | + Value::Table(sub_table) => { |
| 411 | + if let Some(add) = sub_table.get("add") { |
| 412 | + if sub_table.len() == 1 && add.is_table() { |
| 413 | + return Ok(Some(DictEdit { |
| 414 | + action: DictEditAction::Add, |
| 415 | + items: toml_table_to_dict(add), |
| 416 | + })); |
| 417 | + } |
| 418 | + } |
| 419 | + return Ok(Some(DictEdit { |
| 420 | + action: DictEditAction::Replace, |
| 421 | + items: toml_table_to_dict(value), |
| 422 | + })); |
| 423 | + } |
| 424 | + Value::String(v) => { |
| 425 | + return Ok(Some(parse_dict(v).map_err(|e| e.render(option_name))?)); |
| 426 | + } |
| 427 | + _ => { |
| 428 | + return Err(format!( |
| 429 | + "Expected {option_name} to be a toml table or Python dict, but given {value}." |
| 430 | + )); |
| 431 | + } |
| 432 | + } |
490 | 433 | }
|
491 | 434 | }
|
492 |
| - Ok(if edits.is_empty() { None } else { Some(edits) }) |
| 435 | + Ok(None) |
493 | 436 | }
|
494 | 437 | }
|
0 commit comments