file_to_map/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//! # File To Map
//! A crate that parses your file to a hashmap-base struct.
//! Can be used to parse configuration file easily.

use std::collections::HashMap;
use std::fs;
use std::io::{Error, ErrorKind};
use std::ops::Index;

const DEFAULT_KEY_VALUE_SEP: &str = "=";
const DEFAULT_PAIR_SEP: &str = "\n";

/// File To Map struct
pub struct FileToMap<'a> {
    key_value_sep: &'a str,
    pair_sep: &'a str,
    map: HashMap<String, String>,
    file_name: &'a str,
}

impl<'a> FileToMap<'a> {
    /// Creates a new struct. The file won't be parsed until calling the build function.
    pub fn new(file_name: &'a str) -> Self {
        FileToMap {
            key_value_sep: DEFAULT_KEY_VALUE_SEP,
            pair_sep: DEFAULT_PAIR_SEP,
            map: HashMap::new(),
            file_name: file_name,
        }
    }

    /// Changes the key-value pair separator. The default is `=`.
    pub fn set_key_value_separator(mut self, sep: &'a str) -> Self {
        self.key_value_sep = sep;
        return self;
    }

    /// Changes the pairs separator. The default is just a new line, but can be changed to any
    /// string.
    pub fn set_pair_separator(mut self, sep: &'a str) -> Self {
        self.pair_sep = sep;
        return self;
    }

    /// This function parses the file and creates the map. Call this function after changing the
    /// separators (if necessary). Be aware - this function returns a result struct. it will return an error due to an IO file error or missing key-value separator.
    pub fn build(mut self) -> Result<Self, Error> {
        let data = fs::read_to_string(self.file_name)?; // reading the file
        let data = self.strip_trailing_newline(&data); // remove the new-line in the end of the file

        let splitted_data: Vec<String> =
            data.split(self.pair_sep).map(|s| String::from(s)).collect(); // split by pairs separator

        let data_parse_result = splitted_data
            .iter()
            .try_for_each(|s| -> std::io::Result<()> {
                let key_val: Vec<&str> = s.splitn(2, self.key_value_sep).collect(); // split to key and value
                if key_val.len() != 2 {
                    // will happen when cannot find key-value separator
                    return Err(Error::new(
                        ErrorKind::Other,
                        format!("Cannot find {} in {}", self.key_value_sep, s),
                    ));
                }
                self.map
                    .insert(String::from(key_val[0]), String::from(key_val[1])); // add the pair to map
                Ok(())
            });

        let ret: Result<Self, Error> = match data_parse_result {
            Ok(()) => Ok(self),
            Err(e) => Err(e),
        };

        ret
    }

    /// Get value by key, same as get() of HashMap struct.
    pub fn get(&self, key: &str) -> Option<&String> {
        self.map.get(key)
    }
    /// This function removes the trailing newline. Some of the text editors in unix-based systems adding this.
    fn strip_trailing_newline(&self, input: &'a str) -> &'a str {
        input
            .strip_suffix("\r\n")
            .or(input.strip_suffix("\n"))
            .unwrap_or(input)
    }
}

impl<'a> Index<&str> for FileToMap<'a> {
    type Output = String;
    /// Get value by key, using ["key"] (index operator).
    ///
    /// # Panic
    /// The function will panic when the key isn't in the map.
    fn index(&self, key: &str) -> &String {
        self.map
            .get(key)
            .expect(format!("Cannot find {} in FileToMap", key).as_str())
    }
}
#[cfg(test)]
mod test;