INI File Parser

Parse INI sections and key-value pairs into a sorted map, trimming quotes and rejecting malformed headers or empty keys.

RustConfig Management

Code

snippet
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());
}