1#![deny(missing_docs)]
2
3use header_parsing::parse_header;
9use indexmap::IndexMap;
10use thiserror::Error;
11
12use std::{
13 fs::File,
14 io::{BufRead, BufReader},
15 path::Path,
16};
17
18#[derive(Debug, Error)]
20pub enum Error {
21 #[error("Failed to open the configuration file")]
23 OpeningFile,
24 #[error("Subheader found without a corresponding header")]
26 SubheaderWithoutHeader,
27 #[error("Multiple values found for key: {0}")]
29 MultipleKeys(Box<str>),
30 #[error("Input issues")]
32 Input,
33}
34
35impl Error {
36 pub fn print_message(&self, path: &Path) {
38 use Error::*;
39 match self {
40 OpeningFile => eprintln!("Opening file {path:?} failed"),
41 SubheaderWithoutHeader => {
42 eprintln!("File {path:?} has a subheader without a header")
43 }
44 MultipleKeys(key) => eprintln!("Path {path:?} has a duplicate of key {key}"),
45 Input => eprintln!("Input error while reading {path:?}"),
46 }
47 }
48}
49
50pub fn parse_config(path: &Path) -> Result<IndexMap<Box<str>, Box<str>>, Error> {
52 let Ok(file) = File::open(path) else {
53 return Err(Error::OpeningFile);
54 };
55 let reader = BufReader::new(file);
56
57 let mut map = IndexMap::new();
58
59 let mut key_path = Vec::new();
60
61 for line in reader.lines() {
62 let Ok(line) = line else {
63 return Err(Error::Input);
64 };
65 if let Some(success) = parse_header(&mut key_path, &line) {
66 let Ok(changes) = success else {
67 return Err(Error::SubheaderWithoutHeader);
68 };
69
70 changes.apply();
71 continue;
72 }
73
74 let line = line.trim();
75 if line.is_empty() {
76 continue;
77 }
78
79 let (key, value) = line
80 .split_once(char::is_whitespace)
81 .map_or_else(|| (line, ""), |(key, value)| (key, value.trim_start()));
82
83 let key_path = key_path
84 .iter()
85 .rev()
86 .fold(String::new(), |result, new| format!("{new}:{result}"));
87 let full_key = format!("{key_path}{key}").into();
88 if map.contains_key(&full_key) {
89 return Err(Error::MultipleKeys(full_key));
90 }
91 map.insert(full_key, value.into());
92 }
93
94 Ok(map)
95}