kv-parser 0.2.0

A simple parser of key-value-files as hash maps
Documentation
#![deny(missing_docs)]

//! A simple parser for key-value files.
//!
//! This crate provides a function to parse simple text files containing key-value pairs
//! into a hash map. Each line in the file should contain a key
//! and value separated by whitespace.
//!
//! # Examples
//!
//! ```
//! use kv_parser::file_to_key_value_map;
//! use std::path::Path;
//!
//! # fn main() -> Result<(), kv_parser::Error> {
//! let path = Path::new("config.txt");
//! let config = file_to_key_value_map(path)?;
//! # Ok(())
//! # }
//! ```

use std::{
    collections::HashMap,
    fmt::Display,
    fs::File,
    io::{BufRead, BufReader},
    path::Path,
};

/// Errors that can occur during key-value file parsing.
#[derive(Debug)]
pub enum Error {
    /// Failed to open the specified file.
    OpeningFile,
    /// I/O error occurred while reading the file.
    ReadingFile,
    /// Duplicate key found in the file.
    MultipleKeys(String),
}

impl Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::OpeningFile => write!(f, "Failed to open file"),
            Self::ReadingFile => write!(f, "Error reading file"),
            Self::MultipleKeys(key) => write!(f, "Duplicate key found: {key}"),
        }
    }
}

impl std::error::Error for Error {}

/// Parses a key-value file into a hash map.
///
/// The function reads a text file where each non-empty line contains a key-value pair
/// separated by whitespace. The first whitespace character on each line separates the
/// key from the value.
///
/// # Format Rules
/// - Keys cannot contain whitespace
/// - Values can contain any characters (leading/trailing whitespace is trimmed)
/// - Empty lines and lines without whitespace are ignored
/// - Keys must be unique (duplicate keys result in an error)
///
/// # Arguments
///
/// * `path` - Path to the key-value file to parse
///
/// # Returns
///
/// Returns a hash map on success, or an error on failure.
///
/// # Errors
///
/// This function will return an error if:
/// - The file cannot be opened (`Error::OpeningFile`)
/// - An I/O error occurs while reading (`Error::ReadingFile`)
/// - A duplicate key is found in the file (`Error::MultipleKeys`)
pub fn file_to_key_value_map(path: &Path) -> Result<HashMap<Box<str>, Box<str>>, Error> {
    let Ok(file) = File::open(path) else {
        return Err(Error::OpeningFile);
    };

    let reader = BufReader::new(file);
    let mut map = HashMap::new();

    for line in reader.lines() {
        let Ok(line) = line else {
            return Err(Error::ReadingFile);
        };

        if line.trim().is_empty() {
            continue;
        }

        let Some(index) = line.find(|c: char| c.is_whitespace()) else {
            continue;
        };

        let key = line[0..index].trim();
        if map.contains_key(key) {
            return Err(Error::MultipleKeys(key.to_string()));
        }
        let value = line[(index + 1)..].trim();
        map.insert(key.into(), value.into());
    }

    Ok(map)
}