urs 0.5.1

Rust utility library
Documentation
//! # config
//!
//! `config` includes a simple configuration file parser

use std::error::Error;
use std::io::prelude::*;
use std::str::FromStr;
use std::{collections::HashMap, fmt::Debug, fs::File};

#[derive(Clone)]
pub struct Configuration(HashMap<String, String>);

impl Configuration {
    /// Get value in the configuration
    pub fn get(&self, key: &String) -> Option<&String> {
        self.0.get(key)
    }

    /// Get a value in the configuration and parse it to another type
    pub fn get_parse<T, E>(&self, key: &String) -> Option<Result<T, E>>
    where
        T: FromStr<Err = E>,
        E: Error,
    {
        match self.0.get(key)?.parse::<T>() {
            Ok(v) => Some(Ok(v)),
            Err(e) => Some(Err(e)),
        }
    }

    /// Set value in the configuration
    pub fn set(&mut self, key: String, value: String) -> Option<String> {
        self.0.insert(key, value)
    }
}

/// Parse a simple configuration file string
///
/// The syntax is of the following:
///
/// ```text
/// a=b
/// # this is a comment
/// value = spaces are allowed
/// ```
pub fn parse_config(config: &str) -> Configuration {
    Configuration(
        config
            .lines()
            .filter(|line| !line.starts_with('#'))
            .filter_map(|line| line.split_once('='))
            .map(|(key, value)| (String::from(key.trim()), String::from(value.trim())))
            .collect(),
    )
}

/// Like `parse_config` but reads a file
pub fn parse_config_file(f: &mut File) -> Result<Configuration, std::io::Error> {
    let mut data = "".to_owned();
    f.read_to_string(&mut data)?;
    Ok(parse_config(&data))
}

/// Returns application configuration
///
/// On GNU/Linux the configuration path may be: `/home/alice/.config/NAME/config`
pub fn open_app_config(name: &str) -> Option<Result<Configuration, std::io::Error>> {
    let cfg_file = dirs::config_dir()?.join(name).join("config");
    let mut file = match File::open(cfg_file) {
        Ok(f) => f,
        Err(e) => return Some(Err(e)),
    };
    Some(parse_config_file(&mut file))
}

impl From<Configuration> for HashMap<String, String> {
    fn from(value: Configuration) -> Self {
        value.0
    }
}

impl From<&str> for Configuration {
    fn from(value: &str) -> Self {
        parse_config(value)
    }
}

impl TryFrom<&mut File> for Configuration {
    type Error = std::io::Error;

    fn try_from(value: &mut File) -> Result<Self, Self::Error> {
        parse_config_file(value)
    }
}

impl Debug for Configuration {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_map().entries(self.0.iter()).finish()?;

        Ok(())
    }
}