config.rs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. // This file is licensed under the terms of 0BSD:
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any purpose with or without
  4. // fee is hereby granted.
  5. //
  6. // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
  7. // SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
  8. // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  9. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  10. // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
  11. // OF THIS SOFTWARE.
  12. use std::sync::OnceLock;
  13. use std::fs::File;
  14. use std::io::{self, BufRead};
  15. use std::path::{Path, PathBuf};
  16. // Ini-like configuration, with sections.
  17. // Global config first, then config for each subsection
  18. // key=value
  19. //
  20. // [section]
  21. // key2=value2
  22. #[derive(Default, Debug)]
  23. pub struct Config {
  24. pub maildir: PathBuf,
  25. pub email_fmt: String,
  26. pub title_fmt: String,
  27. pub base_url: String,
  28. pub description: String,
  29. pub include_html: bool,
  30. pub out_dir: PathBuf,
  31. pub subsections: Vec<Subsection>,
  32. pub imap_domain: String,
  33. pub imap_port: u32,
  34. pub username: String,
  35. pub password: String,
  36. pub api_addr: String,
  37. pub api_port: u32,
  38. pub smtp_domain: String,
  39. pub smtp_port: u32,
  40. pub sonic_search_addr: String,
  41. pub sonic_search_port: u32,
  42. pub sonic_search_password: String,
  43. }
  44. impl Config {
  45. // TODO defaults here
  46. pub fn match_kv(&mut self, key: &str, value: &str) {
  47. match key {
  48. "email_fmt" => self.email_fmt = value.to_string(),
  49. "title_fmt" => self.title_fmt = value.to_string(),
  50. "base_url" => self.base_url = value.to_string(),
  51. "description" => self.description = value.to_string(),
  52. "imap_domain" => self.imap_domain = value.to_string(),
  53. "imap_port" => self.imap_port = value.parse().unwrap(),
  54. "username" => self.username = value.to_string(),
  55. "password" => self.password = value.to_string(),
  56. "maildir" => self.maildir = PathBuf::from(value),
  57. "api_addr" => self.api_addr = value.to_string(),
  58. "api_port" => self.api_port = value.parse().unwrap(),
  59. "smtp_domain" => self.smtp_domain = value.to_string(),
  60. "smtp_port" => self.smtp_port = value.parse().unwrap(),
  61. "sonic_search_addr" => self.sonic_search_addr = value.to_string(),
  62. "sonic_search_port" => self.sonic_search_port = value.parse().unwrap(),
  63. "sonic_search_password" => self.sonic_search_password = value.to_string(),
  64. _ => {}
  65. }
  66. }
  67. }
  68. #[derive(Default, Clone, Debug)]
  69. pub struct Subsection {
  70. pub name: String, // something
  71. pub title: String, // something mail archive
  72. pub email: String,
  73. pub description: String,
  74. }
  75. impl Subsection {
  76. // TODO defaults here
  77. fn match_kv(&mut self, key: &str, value: &str) {
  78. match key {
  79. "title" => self.title = value.to_string(),
  80. "email" => self.email = value.to_string(),
  81. "description" => self.description = value.to_string(),
  82. _ => {}
  83. }
  84. }
  85. }
  86. pub static INSTANCE: OnceLock<Config> = OnceLock::new();
  87. impl Config {
  88. pub fn global() -> &'static Config {
  89. INSTANCE.get().expect("Config is not initialized")
  90. }
  91. pub fn default_subsection(&self, name: &str) -> Subsection {
  92. Subsection {
  93. name: name.to_owned(),
  94. title: self.title_fmt.replace("%s", name),
  95. email: self.email_fmt.replace("%s", name),
  96. description: String::new(),
  97. }
  98. }
  99. pub fn get_subsection(&self, name: &str) -> Option<Subsection> {
  100. self.subsections
  101. .iter()
  102. .find(|sub| sub.name == name)
  103. .cloned()
  104. }
  105. pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Config, std::io::Error> {
  106. let file = File::open(path)?;
  107. // let sub_sections = vec![];
  108. let mut conf = Config::default();
  109. let mut current_section = None;
  110. for l in io::BufReader::new(file).lines() {
  111. let line = l?;
  112. if line.starts_with('[') && line.ends_with(']') {
  113. let name = &line[1..line.len() - 1];
  114. // Defaults from global config
  115. if let Some(section) = current_section {
  116. conf.subsections.push(section);
  117. }
  118. current_section = Some(conf.default_subsection(name))
  119. }
  120. if line.is_empty() {
  121. continue;
  122. }
  123. if let Some(i) = line.find('=') {
  124. let key = &line[..i];
  125. let value = &line[i + 1..];
  126. if let Some(ref mut s) = current_section {
  127. s.match_kv(key, value);
  128. } else {
  129. conf.match_kv(key, value);
  130. }
  131. } else {
  132. // panic!("Invalid config")
  133. }
  134. }
  135. if let Some(section) = current_section {
  136. conf.subsections.push(section);
  137. }
  138. Ok(conf)
  139. }
  140. }