use std::io::Read; use std::path::Path; use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; pub struct Conf { pub job_dir: PathBuf, pub output_dir: PathBuf, pub check_interval: Duration, pub driver_url: String, } impl Conf { pub fn get_default_conf() -> Conf { return Conf { job_dir: PathBuf::from_str("jobs.d").unwrap(), output_dir: PathBuf::from_str("results.d").unwrap(), check_interval: Duration::new(15*60, 0), driver_url: String::from_str("http://localhost:4444").unwrap(), }; } pub fn update_from_file(&mut self, path: &Path) { let mut file = match std::fs::File::open(path) { Err(why) => { println!("Could not open file '{}': {}", path.display(), why); return; }, Ok(file) => file, }; let mut content = String::new(); match file.read_to_string(&mut content) { Err(why) => println!("Could not read from file '{}': {}", path.display(), why), Ok(_) => (), } let lines = content.lines(); for line in lines { if line.starts_with('#') { continue; } let result = line.split_once('='); if result.is_none() { println!("Skipping configuration line '{}', no key-value delimiter (=) found", line); continue; } let key = result.unwrap().0.trim(); let value = result.unwrap().1.trim(); if key.eq("") || value.eq("") { println!("Skipping configuration line '{}', no key or value side is empty", line); continue; } match key { "job_dir" => { println!("{} changed from '{}' to '{}' by line in '{}'", key, self.job_dir.display(), value, path.display()); self.job_dir = PathBuf::from_str(value).unwrap(); }, "output_dir" => { println!("{} changed from '{}' to '{}' by line in '{}'", key, self.output_dir.display(), value, path.display()); self.output_dir = PathBuf::from_str(value).unwrap(); }, "check_interval" => { let converted_value = match value.parse::() { Err(why) => { println!("Failed to convert '{}' to u64: {}", value, why); continue;}, Ok(v) => v, }; println!("{} changed from '{}' to '{}' by line in '{}'", key, self.check_interval.as_secs(), value, path.display()); self.check_interval = Duration::new(converted_value, 0); }, "driver_url" => { println!("{} changed from '{}' to '{}' by line in '{}'", key, self.driver_url, value, path.display()); self.driver_url = String::from_str(value).unwrap(); } _ => { println!("Unknown key '{}' in file '{}'", key, path.display()); } } } } } #[cfg(test)] mod tests { use std::io::Write; use std::str::FromStr; use tempfile::NamedTempFile; use super::*; fn write_conf_to_temp_file(content: &str) -> NamedTempFile { let mut f = NamedTempFile::new().unwrap(); f.write_all(content.as_bytes()).expect("Failed to write configuration to file"); return f; } #[test] fn test_not_existing() { let mut conf = Conf::get_default_conf(); conf.update_from_file(Path::new("/not/a/real/file")); } #[test] fn test_set_all_values() { let mut conf = Conf::get_default_conf(); let tf = write_conf_to_temp_file(r#" job_dir = /val=ue # this line is ignored, and the white space on the next line is intentional output_dir=/var/lib/output check_interval=3600 driver_url = https://example.test:6666 "#); conf.update_from_file(tf.path()); assert!(Path::new("/val=ue").eq(&conf.job_dir)); assert!(Path::new("/var/lib/output").eq(&conf.output_dir)); assert!(String::from_str("https://example.test:6666").unwrap().eq(&conf.driver_url)); assert_eq!(3600, conf.check_interval.as_secs()); } #[test] fn test_non_numeric_check_interval_fails() { let mut conf = Conf::get_default_conf(); let tf = write_conf_to_temp_file(r#" check_interval=d3600 "#); conf.update_from_file(tf.path()); assert_eq!(15*60, conf.check_interval.as_secs()); } }