kv_parser/
lib.rs

1#![deny(missing_docs)]
2
3//! A simple parser for key-value files.
4//!
5//! This crate provides a function to parse simple text files containing key-value pairs
6//! into a hash map. Each line in the file should contain a key
7//! and value separated by whitespace.
8//!
9//! # Examples
10//!
11//! ```
12//! use kv_parser::file_to_key_value_map;
13//! use std::path::Path;
14//!
15//! # fn main() -> Result<(), kv_parser::Error> {
16//! let path = Path::new("config.txt");
17//! let config = file_to_key_value_map(path)?;
18//! # Ok(())
19//! # }
20//! ```
21
22use std::{
23    collections::HashMap,
24    fmt::Display,
25    fs::File,
26    io::{BufRead, BufReader},
27    path::Path,
28};
29
30/// Errors that can occur during key-value file parsing.
31#[derive(Debug)]
32pub enum Error {
33    /// Failed to open the specified file.
34    OpeningFile,
35    /// I/O error occurred while reading the file.
36    ReadingFile,
37    /// Duplicate key found in the file.
38    MultipleKeys(String),
39}
40
41impl Display for Error {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        match self {
44            Self::OpeningFile => write!(f, "Failed to open file"),
45            Self::ReadingFile => write!(f, "Error reading file"),
46            Self::MultipleKeys(key) => write!(f, "Duplicate key found: {key}"),
47        }
48    }
49}
50
51impl std::error::Error for Error {}
52
53/// Parses a key-value file into a hash map.
54///
55/// The function reads a text file where each non-empty line contains a key-value pair
56/// separated by whitespace. The first whitespace character on each line separates the
57/// key from the value.
58///
59/// # Format Rules
60/// - Keys cannot contain whitespace
61/// - Values can contain any characters (leading/trailing whitespace is trimmed)
62/// - Empty lines and lines without whitespace are ignored
63/// - Keys must be unique (duplicate keys result in an error)
64///
65/// # Arguments
66///
67/// * `path` - Path to the key-value file to parse
68///
69/// # Returns
70///
71/// Returns a hash map on success, or an error on failure.
72///
73/// # Errors
74///
75/// This function will return an error if:
76/// - The file cannot be opened (`Error::OpeningFile`)
77/// - An I/O error occurs while reading (`Error::ReadingFile`)
78/// - A duplicate key is found in the file (`Error::MultipleKeys`)
79pub fn file_to_key_value_map(path: &Path) -> Result<HashMap<Box<str>, Box<str>>, Error> {
80    let Ok(file) = File::open(path) else {
81        return Err(Error::OpeningFile);
82    };
83
84    let reader = BufReader::new(file);
85    let mut map = HashMap::new();
86
87    for line in reader.lines() {
88        let Ok(line) = line else {
89            return Err(Error::ReadingFile);
90        };
91
92        if line.trim().is_empty() {
93            continue;
94        }
95
96        let Some(index) = line.find(|c: char| c.is_whitespace()) else {
97            continue;
98        };
99
100        let key = line[0..index].trim();
101        if map.contains_key(key) {
102            return Err(Error::MultipleKeys(key.to_string()));
103        }
104        let value = line[(index + 1)..].trim();
105        map.insert(key.into(), value.into());
106    }
107
108    Ok(map)
109}