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
// Import necessary libraries and modules
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Error, ErrorKind};
use std::str::FromStr;
// Define a public struct `Config` with fields for configuration options
#[derive(Debug, Default)]
pub struct Config<T> {
fields: HashMap<String, T>,
}
// Implement methods for the `Config` struct
impl<T> Config<T>
where
T: std::fmt::Debug + Default + FromStr,
<T as FromStr>::Err: std::fmt::Debug,
{
// Define a method to create a `Config` instance from a file
pub fn from_file(file_path: &str) -> Result<Config<T>, Error> {
// Attempt to open the specified file
let file = File::open(file_path)?;
// Create a default `Config` instance
let mut config = Config::default();
// Define characters that indicate comments in the configuration file
let mut comment_chars = vec!['#', ';'];
// Iterate over each line in the file
for line in BufReader::new(file).lines() {
// Unwrap the line, handling any potential errors
let line = line?;
// Trim leading and trailing whitespaces from the line
let trimmed_line = line.trim();
// Skip empty lines and lines starting with comment characters
if !trimmed_line.is_empty()
&& !comment_chars.contains(&trimmed_line.chars().next().unwrap())
{
// Split the line into key-value parts based on the '=' character
let parts: Vec<&str> = trimmed_line.split('=').map(|s| s.trim()).collect();
// Ensure that there are exactly two parts (key and value)
if parts.len() != 2 {
return Err(Error::new(
ErrorKind::InvalidData,
"Invalid configuration format",
));
}
// Extract key and value
let key = parts[0].to_string();
let value_str = parts[1].trim_matches('"'); // Remove surrounding double quotes
// Use Default::default() for the default value
let value = if value_str.is_empty() {
Default::default()
} else {
value_str
.parse()
.map_err(|_| Error::new(ErrorKind::InvalidData, "Failed to parse value"))?
};
config.fields.insert(key, value);
}
}
// Return the populated `Config` instance
Ok(config)
}
// Define a method to get the value of using the `key`
pub fn get_value(&self, key: &str) -> Option<&T> {
self.fields.get(key)
}
}
// Define tests for the `Config` struct and its methods
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_from_file() {
let config = Config::from_file("example.cfg");
assert!(config.is_ok(), "Failed to read configuration file: {:?}", config.err());
let config = config.unwrap();
assert_eq!(config.get_value("key1"), Some(&"value1".to_string()));
assert_eq!(config.get_value("key2"), Some(&42.to_string()));
}
}