// This file is licensed under the terms of 0BSD: // // Permission to use, copy, modify, and/or distribute this software for any purpose with or without // fee is hereby granted. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS // SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE // OF THIS SOFTWARE. use std::sync::OnceLock; use std::fs::File; use std::io::{self, BufRead}; use std::path::{Path, PathBuf}; // Ini-like configuration, with sections. // Global config first, then config for each subsection // key=value // // [section] // key2=value2 #[derive(Default, Debug)] pub struct Config { pub maildir: PathBuf, pub email_fmt: String, pub title_fmt: String, pub base_url: String, pub description: String, pub include_html: bool, pub out_dir: PathBuf, pub subsections: Vec, pub imap_domain: String, pub imap_port: u32, pub username: String, pub password: String, pub api_addr: String, pub api_port: u32, pub smtp_domain: String, pub smtp_port: u32, pub sonic_search_addr: String, pub sonic_search_port: u32, pub sonic_search_password: String, } impl Config { // TODO defaults here pub fn match_kv(&mut self, key: &str, value: &str) { match key { "email_fmt" => self.email_fmt = value.to_string(), "title_fmt" => self.title_fmt = value.to_string(), "base_url" => self.base_url = value.to_string(), "description" => self.description = value.to_string(), "imap_domain" => self.imap_domain = value.to_string(), "imap_port" => self.imap_port = value.parse().unwrap(), "username" => self.username = value.to_string(), "password" => self.password = value.to_string(), "maildir" => self.maildir = PathBuf::from(value), "api_addr" => self.api_addr = value.to_string(), "api_port" => self.api_port = value.parse().unwrap(), "smtp_domain" => self.smtp_domain = value.to_string(), "smtp_port" => self.smtp_port = value.parse().unwrap(), "sonic_search_addr" => self.sonic_search_addr = value.to_string(), "sonic_search_port" => self.sonic_search_port = value.parse().unwrap(), "sonic_search_password" => self.sonic_search_password = value.to_string(), _ => {} } } } #[derive(Default, Clone, Debug)] pub struct Subsection { pub name: String, // something pub title: String, // something mail archive pub email: String, pub description: String, } impl Subsection { // TODO defaults here fn match_kv(&mut self, key: &str, value: &str) { match key { "title" => self.title = value.to_string(), "email" => self.email = value.to_string(), "description" => self.description = value.to_string(), _ => {} } } } pub static INSTANCE: OnceLock = OnceLock::new(); impl Config { pub fn global() -> &'static Config { INSTANCE.get().expect("Config is not initialized") } pub fn default_subsection(&self, name: &str) -> Subsection { Subsection { name: name.to_owned(), title: self.title_fmt.replace("%s", name), email: self.email_fmt.replace("%s", name), description: String::new(), } } pub fn get_subsection(&self, name: &str) -> Option { self.subsections .iter() .find(|sub| sub.name == name) .cloned() } pub fn from_file>(path: P) -> Result { let file = File::open(path)?; // let sub_sections = vec![]; let mut conf = Config::default(); let mut current_section = None; for l in io::BufReader::new(file).lines() { let line = l?; if line.starts_with('[') && line.ends_with(']') { let name = &line[1..line.len() - 1]; // Defaults from global config if let Some(section) = current_section { conf.subsections.push(section); } current_section = Some(conf.default_subsection(name)) } if line.is_empty() { continue; } if let Some(i) = line.find('=') { let key = &line[..i]; let value = &line[i + 1..]; if let Some(ref mut s) = current_section { s.match_kv(key, value); } else { conf.match_kv(key, value); } } else { // panic!("Invalid config") } } if let Some(section) = current_section { conf.subsections.push(section); } Ok(conf) } }