You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

388 lines
10.0KB

  1. /*
  2. * meli - configuration module.
  3. *
  4. * Copyright 2017 Manos Pitsidianakis
  5. *
  6. * This file is part of meli.
  7. *
  8. * meli is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * meli is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with meli. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. extern crate bincode;
  22. extern crate config;
  23. extern crate serde;
  24. extern crate xdg;
  25. pub mod mailer;
  26. pub mod notifications;
  27. pub mod pager;
  28. pub mod shortcuts;
  29. pub mod accounts;
  30. pub use self::accounts::Account;
  31. use self::config::{Config, File, FileFormat};
  32. pub use self::mailer::*;
  33. pub use self::shortcuts::*;
  34. use self::default_vals::*;
  35. use self::notifications::NotificationsSettings;
  36. use crate::pager::PagerSettings;
  37. use melib::conf::AccountSettings;
  38. use melib::error::*;
  39. use self::serde::{de, Deserialize, Deserializer};
  40. use std::collections::HashMap;
  41. use std::env;
  42. use std::fs::OpenOptions;
  43. use std::io::{self, BufRead, Write};
  44. use std::path::PathBuf;
  45. #[macro_export]
  46. macro_rules! split_command {
  47. ($cmd:expr) => {{
  48. $cmd.split_whitespace().collect::<Vec<&str>>()
  49. }};
  50. }
  51. #[derive(Debug, Clone, PartialEq)]
  52. pub enum ToggleFlag {
  53. Unset,
  54. InternalVal(bool),
  55. False,
  56. True,
  57. }
  58. impl Default for ToggleFlag {
  59. fn default() -> Self {
  60. ToggleFlag::Unset
  61. }
  62. }
  63. impl ToggleFlag {
  64. pub fn is_unset(&self) -> bool {
  65. ToggleFlag::Unset == *self
  66. }
  67. pub fn is_internal(&self) -> bool {
  68. if let ToggleFlag::InternalVal(_) = *self {
  69. true
  70. } else {
  71. false
  72. }
  73. }
  74. pub fn is_false(&self) -> bool {
  75. ToggleFlag::False == *self || ToggleFlag::InternalVal(false) == *self
  76. }
  77. pub fn is_true(&self) -> bool {
  78. ToggleFlag::True == *self || ToggleFlag::InternalVal(true) == *self
  79. }
  80. }
  81. #[derive(Debug, Clone, Deserialize)]
  82. pub struct FolderConf {
  83. rename: Option<String>,
  84. #[serde(default = "true_val")]
  85. autoload: bool,
  86. #[serde(deserialize_with = "toggleflag_de", default)]
  87. ignore: ToggleFlag,
  88. }
  89. impl Default for FolderConf {
  90. fn default() -> Self {
  91. FolderConf {
  92. rename: None,
  93. autoload: true,
  94. ignore: ToggleFlag::Unset,
  95. }
  96. }
  97. }
  98. impl FolderConf {
  99. pub fn rename(&self) -> Option<&str> {
  100. self.rename.as_ref().map(String::as_str)
  101. }
  102. }
  103. #[derive(Debug, Clone, Default, Deserialize)]
  104. pub struct FileAccount {
  105. root_folder: String,
  106. format: String,
  107. sent_folder: String,
  108. draft_folder: String,
  109. identity: String,
  110. #[serde(default = "none")]
  111. display_name: Option<String>,
  112. #[serde(deserialize_with = "index_from_str")]
  113. index: IndexStyle,
  114. /// A command to pipe html output before displaying it in a pager
  115. /// Default: None
  116. #[serde(default = "none", deserialize_with = "non_empty_string")]
  117. html_filter: Option<String>,
  118. folders: Option<HashMap<String, FolderConf>>,
  119. }
  120. impl From<FileAccount> for AccountConf {
  121. fn from(x: FileAccount) -> Self {
  122. let format = x.format.to_lowercase();
  123. let sent_folder = x.sent_folder.clone();
  124. let root_folder = x.root_folder.clone();
  125. let identity = x.identity.clone();
  126. let display_name = x.display_name.clone();
  127. let acc = AccountSettings {
  128. name: String::new(),
  129. root_folder,
  130. format,
  131. sent_folder,
  132. identity,
  133. display_name,
  134. };
  135. let folder_confs = x.folders.clone().unwrap_or_else(Default::default);
  136. AccountConf {
  137. account: acc,
  138. conf: x,
  139. folder_confs,
  140. }
  141. }
  142. }
  143. impl FileAccount {
  144. pub fn folders(&self) -> Option<&HashMap<String, FolderConf>> {
  145. self.folders.as_ref()
  146. }
  147. pub fn folder(&self) -> &str {
  148. &self.root_folder
  149. }
  150. pub fn index(&self) -> IndexStyle {
  151. self.index
  152. }
  153. pub fn sent_folder(&self) -> &str {
  154. self.sent_folder.as_str()
  155. }
  156. pub fn html_filter(&self) -> Option<&str> {
  157. self.html_filter.as_ref().map(String::as_str)
  158. }
  159. }
  160. #[derive(Debug, Clone, Default, Deserialize)]
  161. struct FileSettings {
  162. accounts: HashMap<String, FileAccount>,
  163. #[serde(default)]
  164. pager: PagerSettings,
  165. #[serde(default)]
  166. notifications: NotificationsSettings,
  167. #[serde(default)]
  168. shortcuts: Shortcuts,
  169. mailer: MailerSettings,
  170. }
  171. #[derive(Debug, Clone, Default)]
  172. pub struct AccountConf {
  173. account: AccountSettings,
  174. conf: FileAccount,
  175. folder_confs: HashMap<String, FolderConf>,
  176. }
  177. impl AccountConf {
  178. pub fn account(&self) -> &AccountSettings {
  179. &self.account
  180. }
  181. pub fn conf(&self) -> &FileAccount {
  182. &self.conf
  183. }
  184. pub fn conf_mut(&mut self) -> &mut FileAccount {
  185. &mut self.conf
  186. }
  187. }
  188. #[derive(Debug, Clone, Default)]
  189. pub struct Settings {
  190. pub accounts: HashMap<String, AccountConf>,
  191. pub pager: PagerSettings,
  192. pub notifications: NotificationsSettings,
  193. pub shortcuts: Shortcuts,
  194. pub mailer: MailerSettings,
  195. }
  196. impl FileSettings {
  197. pub fn new() -> Result<FileSettings> {
  198. let config_path = match env::var("MELI_CONFIG") {
  199. Ok(path) => PathBuf::from(path),
  200. Err(_) => {
  201. let xdg_dirs = xdg::BaseDirectories::with_prefix("meli").unwrap();
  202. xdg_dirs
  203. .place_config_file("config")
  204. .expect("cannot create configuration directory")
  205. }
  206. };
  207. if !config_path.exists() {
  208. println!(
  209. "No configuration found. Would you like to generate one in {}? [Y/n]",
  210. config_path.display()
  211. );
  212. let mut buffer = String::new();
  213. let stdin = io::stdin();
  214. let mut handle = stdin.lock();
  215. loop {
  216. buffer.clear();
  217. handle
  218. .read_line(&mut buffer)
  219. .expect("Could not read from stdin.");
  220. match buffer.trim() {
  221. "Y" | "y" | "yes" | "YES" | "Yes" => {
  222. let mut file = OpenOptions::new()
  223. .write(true)
  224. .create_new(true)
  225. .open(config_path.as_path())
  226. .expect("Could not create config file.");
  227. file.write_all(include_bytes!("../../sample-config"))
  228. .expect("Could not write to config file.");
  229. println!("Written config to {}", config_path.display());
  230. std::process::exit(1);
  231. }
  232. "n" | "N" | "no" | "No" | "NO" => {
  233. std::process::exit(1);
  234. }
  235. _ => {
  236. println!(
  237. "No configuration found. Would you like to generate one in {}? [Y/n]",
  238. config_path.display()
  239. );
  240. }
  241. }
  242. }
  243. }
  244. let mut s = Config::new();
  245. if s.merge(File::new(config_path.to_str().unwrap(), FileFormat::Toml))
  246. .is_err()
  247. {
  248. println!("Config file contains errors.");
  249. std::process::exit(1);
  250. }
  251. /* No point in returning without a config file. */
  252. match s.try_into() {
  253. Ok(v) => Ok(v),
  254. Err(e) => Err(MeliError::new(e.to_string())),
  255. }
  256. }
  257. }
  258. impl Settings {
  259. pub fn new() -> Settings {
  260. let fs = FileSettings::new().unwrap_or_else(|e| {
  261. println!("Configuration error: {}", e);
  262. std::process::exit(1);
  263. });
  264. let mut s: HashMap<String, AccountConf> = HashMap::new();
  265. for (id, x) in fs.accounts {
  266. let mut ac = AccountConf::from(x);
  267. ac.account.set_name(id.clone());
  268. s.insert(id, ac);
  269. }
  270. Settings {
  271. accounts: s,
  272. pager: fs.pager,
  273. notifications: fs.notifications,
  274. shortcuts: fs.shortcuts,
  275. mailer: fs.mailer,
  276. }
  277. }
  278. }
  279. #[derive(Copy, Debug, Clone, Deserialize)]
  280. pub enum IndexStyle {
  281. Plain,
  282. Threaded,
  283. Compact,
  284. }
  285. impl Default for IndexStyle {
  286. fn default() -> Self {
  287. IndexStyle::Compact
  288. }
  289. }
  290. fn index_from_str<'de, D>(deserializer: D) -> std::result::Result<IndexStyle, D::Error>
  291. where
  292. D: Deserializer<'de>,
  293. {
  294. let s = <String>::deserialize(deserializer)?;
  295. match s.as_str() {
  296. "Plain" | "plain" => Ok(IndexStyle::Plain),
  297. "Threaded" | "threaded" => Ok(IndexStyle::Threaded),
  298. "Compact" | "compact" => Ok(IndexStyle::Compact),
  299. _ => Err(de::Error::custom("invalid `index` value")),
  300. }
  301. }
  302. fn non_empty_string<'de, D>(deserializer: D) -> std::result::Result<Option<String>, D::Error>
  303. where
  304. D: Deserializer<'de>,
  305. {
  306. let s = <String>::deserialize(deserializer)?;
  307. if s.is_empty() {
  308. Ok(None)
  309. } else {
  310. Ok(Some(s))
  311. }
  312. }
  313. fn toggleflag_de<'de, D>(deserializer: D) -> std::result::Result<ToggleFlag, D::Error>
  314. where
  315. D: Deserializer<'de>,
  316. {
  317. let s = <bool>::deserialize(deserializer);
  318. Ok(match s {
  319. Err(_) => ToggleFlag::Unset,
  320. Ok(true) => ToggleFlag::True,
  321. Ok(false) => ToggleFlag::False,
  322. })
  323. }
  324. /*
  325. * Deserialize default functions
  326. */
  327. mod default_vals {
  328. pub(in crate::conf) fn false_val() -> bool {
  329. true
  330. }
  331. pub(in crate::conf) fn true_val() -> bool {
  332. true
  333. }
  334. pub(in crate::conf) fn zero_val() -> usize {
  335. 0
  336. }
  337. pub(in crate::conf) fn eighty_percent() -> usize {
  338. 80
  339. }
  340. pub(in crate::conf) fn none() -> Option<String> {
  341. None
  342. }
  343. }