INI File Parser
Parse INI sections and key-value pairs into a sorted map, trimming quotes and rejecting malformed headers or empty keys.
Code
use std::collections::BTreeMap;
#[derive(Debug, Default)]
struct IniFile {
sections: BTreeMap<String, BTreeMap<String, String>>,
}
impl IniFile {
fn get(&self, section: &str, key: &str) -> Option<&str> {
self.sections.get(section)?.get(key).map(String::as_str)
}
}
fn parse_ini(input: &str) -> Result<IniFile, String> {
let mut ini = IniFile::default();
let mut section = String::from("default");
ini.sections.insert(section.clone(), BTreeMap::new());
for (index, raw_line) in input.lines().enumerate() {
let line = raw_line.trim();
if line.is_empty() || line.starts_with(';') || line.starts_with('#') {
continue;
}
if line.starts_with('[') {
if !line.ends_with(']') {
return Err(format!("line {}: unclosed section header", index + 1));
}
let name = line[1..line.len() - 1].trim();
if name.is_empty() {
return Err(format!("line {}: empty section name", index + 1));
}
section = name.to_string();
ini.sections.entry(section.clone()).or_default();
continue;
}
let (key, value) = match line.split_once('=') {
Some(pair) => pair,
None => return Err(format!("line {}: expected key=value", index + 1)),
};
let key = key.trim();
if key.is_empty() {
return Err(format!("line {}: empty key", index + 1));
}
ini.sections
.entry(section.clone())
.or_default()
.insert(key.to_string(), value.trim().trim_matches('"').to_string());
}
Ok(ini)
}
fn main() {
let document = r#"
; application settings
[server]
host = "0.0.0.0"
port = 8080
[database]
url = postgres://localhost/app
pool_size = 10
"#;
let ini = parse_ini(document).expect("valid ini");
println!("host: {:?}", ini.get("server", "host"));
println!("pool_size: {:?}", ini.get("database", "pool_size"));
println!("missing: {:?}", ini.get("server", "missing"));
println!("error: {:?}", parse_ini("[broken\nkey=1").is_err());
}Report a problem with this exampleOpens a pre-filled issue on GitHub.