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}