dotini/
lib.rs

1///! An INI parser struct.
2/// This struct can be used to parse an INI-formatted string or file and convert it into a
3/// HashMap of HashMaps where each inner HashMap contains key-value pairs of properties in a section.
4/// Examples
5///
6/// ```rust
7/// use std::collections::HashMap;
8/// use dotini::INIParser;
9/// use ini_parser::INIParser;
10/// let content = r#"
11/// [user]
12/// name = John Doe
13/// email = johndoe@example.com
14/// "#;
15///
16/// let parser = INIParser::from_string(content).unwrap();
17/// let output: HashMap<String, HashMap<String, String>> = parser.into_inner();
18/// assert_eq!(output["user"]["name"], "John Doe");
19/// assert_eq!(output["user"]["email"], "johndoe@example.com");
20///
21extern crate pest;
22#[macro_use]
23extern crate pest_derive;
24
25use pest::Parser;
26use std::collections::HashMap;
27use std::fs;
28
29/// Generic Result type for dotini.
30pub type INIParserResult<T> = Result<T, InIParseError>;
31
32/// Possible error enum for dotini.
33#[derive(Debug)]
34pub enum InIParseError {
35    FileReadError(String),
36    UnsuccessfulParse(String),
37    Finished,
38    Unreachable,
39}
40
41/// Ini is the main parser that does the job for us.
42/// takes some set of rules from ini.pest file.
43#[derive(Parser)]
44#[grammar = "ini.pest"]
45pub struct Ini;
46
47/// The INIParser struct is used to parse INI configuration files into a HashMap data structure for easy access to configuration values.
48/// To use the INIParser, we only need to create a new instance of the struct using either: `INIParser::from_string` or `INIParser::from_file`. the configuration values are stored in the output field of the struct
49#[derive(Debug)]
50pub struct INIParser {
51    pub output: HashMap<String, HashMap<String, String>>,
52}
53
54impl INIParser {
55    pub fn from_string(content: &str) -> INIParserResult<Self> {
56        Self::parse(content)
57    }
58
59    /**
60     * Creates a new INIParser struct from an INI file.
61     *
62     * # Arguments
63     * * `path` - A string containing the path to the INI file to parse.
64     *
65     * # Returns
66     * Returns an `INIParserResult` containing the parsed `INIParser` struct, or an `INIParseError`
67     * if there is an issue reading or parsing the file.
68     */
69    pub fn from_file(path: &str) -> INIParserResult<Self> {
70        let content = fs::read_to_string(path)
71            .map_err(|err| InIParseError::FileReadError(err.to_string()))?;
72
73        Self::parse(&content)
74    }
75
76    /**
77     * Returns the inner HashMap of the `INIParser` struct.
78     *
79     * # Returns
80     * Returns a `HashMap` where each key is a section in the INI file and the corresponding value
81     * is another `HashMap` containing key-value pairs of properties in that section.
82     */
83    pub fn into_inner(self) -> HashMap<String, HashMap<String, String>> {
84        self.output
85    }
86
87    /**
88     * Parses an INI-formatted string and returns an `INIParser` struct containing the parsed content.
89     *
90     * # Arguments
91     * * `content` - An INI-formatted string to parse.
92     *
93     * # Returns
94     * Returns an `INIParserResult` containing the parsed `INIParser` struct, or an `INIParseError`
95     * if there is an issue parsing the content.
96     */
97    fn parse(content: &str) -> INIParserResult<Self> {
98        let ini = Ini::parse(Rule::file, content)
99            .map_err(|err| InIParseError::UnsuccessfulParse(err.to_string()))?
100            .next()
101            .ok_or(InIParseError::UnsuccessfulParse(
102                "Unsuccessful parse".to_string(),
103            ))?;
104        let mut output: HashMap<String, HashMap<String, String>> = HashMap::new();
105        let mut current_section = "untagged".to_string();
106
107        for line in ini.into_inner() {
108            match line.as_rule() {
109                Rule::section => {
110                    current_section = line.into_inner()
111                        .next()
112                        .ok_or(InIParseError::Finished)?
113                        .as_str()
114                        .to_string();
115                }
116                Rule::property => {
117                    let mut prop = line.into_inner();
118                    let name = prop
119                        .next()
120                        .ok_or(InIParseError::Finished)?
121                        .as_str()
122                        .to_string();
123                    let val = prop
124                        .next()
125                        .ok_or(InIParseError::Finished)?
126                        .as_str()
127                        .to_string();
128
129                    output.entry(current_section.to_string())
130                          .or_default()
131                          .insert(name, val);
132                }
133                Rule::EOI => (),
134                _ => unreachable!(),
135            };
136        }
137        Ok(Self { output })
138    }
139}