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.

1065 lines
36 KiB

4 years ago
4 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  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. /*! Configuration logic and `config.toml` interfaces. */
  22. extern crate bincode;
  23. extern crate serde;
  24. extern crate toml;
  25. extern crate xdg;
  26. use crate::conf::deserializers::non_empty_string;
  27. use crate::terminal::Color;
  28. use melib::search::Query;
  29. use std::collections::HashSet;
  30. mod overrides;
  31. pub use overrides::*;
  32. pub mod composing;
  33. pub mod notifications;
  34. pub mod pager;
  35. pub mod pgp;
  36. pub mod tags;
  37. #[macro_use]
  38. pub mod shortcuts;
  39. mod listing;
  40. pub mod terminal;
  41. mod themes;
  42. pub use themes::*;
  43. pub mod accounts;
  44. pub use self::accounts::Account;
  45. pub use self::composing::*;
  46. pub use self::pgp::*;
  47. pub use self::shortcuts::*;
  48. pub use self::tags::*;
  49. use self::default_vals::*;
  50. use self::listing::ListingSettings;
  51. use self::notifications::NotificationsSettings;
  52. use self::terminal::TerminalSettings;
  53. use crate::pager::PagerSettings;
  54. use crate::plugins::Plugin;
  55. use melib::conf::{AccountSettings, MailboxConf, ToggleFlag};
  56. use melib::error::*;
  57. use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
  58. use std::collections::HashMap;
  59. use std::env;
  60. use std::fs::OpenOptions;
  61. use std::io::{self, BufRead, Write};
  62. use std::os::unix::fs::PermissionsExt;
  63. use std::path::{Path, PathBuf};
  64. #[macro_export]
  65. macro_rules! split_command {
  66. ($cmd:expr) => {{
  67. $cmd.split_whitespace().collect::<Vec<&str>>()
  68. }};
  69. }
  70. #[macro_export]
  71. macro_rules! mailbox_acc_settings {
  72. ($context:ident[$account_idx:expr].$setting:ident.$field:ident) => {{
  73. $context.accounts[$account_idx]
  74. .settings
  75. .conf_override
  76. .$setting
  77. .$field
  78. .as_ref()
  79. .unwrap_or(&$context.settings.$setting.$field)
  80. }};
  81. }
  82. #[macro_export]
  83. macro_rules! mailbox_settings {
  84. ($context:ident[$account_idx:expr][$mailbox_path:expr].$setting:ident.$field:ident) => {{
  85. $context.accounts[$account_idx][$mailbox_path]
  86. .conf
  87. .conf_override
  88. .$setting
  89. .$field
  90. .as_ref()
  91. .or($context.accounts[$account_idx]
  92. .settings
  93. .conf_override
  94. .$setting
  95. .$field
  96. .as_ref())
  97. .unwrap_or(&$context.settings.$setting.$field)
  98. }};
  99. }
  100. #[derive(Default, Debug, Clone, Serialize, Deserialize)]
  101. pub struct MailUIConf {
  102. #[serde(default)]
  103. pub pager: PagerSettingsOverride,
  104. #[serde(default)]
  105. pub listing: ListingSettingsOverride,
  106. #[serde(default)]
  107. pub notifications: NotificationsSettingsOverride,
  108. #[serde(default)]
  109. pub shortcuts: ShortcutsOverride,
  110. #[serde(default)]
  111. pub composing: ComposingSettingsOverride,
  112. #[serde(default)]
  113. pub identity: Option<String>,
  114. #[serde(default)]
  115. pub tags: TagsSettingsOverride,
  116. #[serde(default)]
  117. pub themes: Option<Themes>,
  118. #[serde(default)]
  119. pub pgp: PGPSettingsOverride,
  120. }
  121. #[serde(default)]
  122. #[derive(Debug, Default, Clone, Serialize, Deserialize)]
  123. pub struct FileMailboxConf {
  124. #[serde(flatten)]
  125. pub conf_override: MailUIConf,
  126. #[serde(flatten)]
  127. pub mailbox_conf: MailboxConf,
  128. }
  129. impl FileMailboxConf {
  130. pub fn conf_override(&self) -> &MailUIConf {
  131. &self.conf_override
  132. }
  133. pub fn mailbox_conf(&self) -> &MailboxConf {
  134. &self.mailbox_conf
  135. }
  136. }
  137. use crate::conf::deserializers::extra_settings;
  138. #[derive(Debug, Clone, Default, Serialize, Deserialize)]
  139. pub struct FileAccount {
  140. root_mailbox: String,
  141. format: String,
  142. identity: String,
  143. #[serde(default = "none")]
  144. display_name: Option<String>,
  145. #[serde(default = "false_val")]
  146. read_only: bool,
  147. #[serde(default)]
  148. subscribed_mailboxes: Vec<String>,
  149. #[serde(default)]
  150. mailboxes: HashMap<String, FileMailboxConf>,
  151. #[serde(default)]
  152. search_backend: SearchBackend,
  153. #[serde(default = "false_val")]
  154. pub manual_refresh: bool,
  155. #[serde(default = "none")]
  156. pub refresh_command: Option<String>,
  157. #[serde(flatten)]
  158. pub conf_override: MailUIConf,
  159. #[serde(flatten)]
  160. #[serde(deserialize_with = "extra_settings")]
  161. pub extra: HashMap<String, String>, /* use custom deserializer to convert any given value (eg bool, number, etc) to string */
  162. }
  163. impl FileAccount {
  164. pub fn mailboxes(&self) -> &HashMap<String, FileMailboxConf> {
  165. &self.mailboxes
  166. }
  167. pub fn mailbox(&self) -> &str {
  168. &self.root_mailbox
  169. }
  170. pub fn search_backend(&self) -> &SearchBackend {
  171. &self.search_backend
  172. }
  173. }
  174. #[derive(Debug, Clone, Default, Serialize, Deserialize)]
  175. pub struct FileSettings {
  176. pub accounts: HashMap<String, FileAccount>,
  177. #[serde(default)]
  178. pub pager: PagerSettings,
  179. #[serde(default)]
  180. pub listing: ListingSettings,
  181. #[serde(default)]
  182. pub notifications: NotificationsSettings,
  183. #[serde(default)]
  184. pub shortcuts: Shortcuts,
  185. pub composing: ComposingSettings,
  186. #[serde(default)]
  187. pub tags: TagsSettings,
  188. #[serde(default)]
  189. pub pgp: PGPSettings,
  190. #[serde(default)]
  191. pub terminal: TerminalSettings,
  192. #[serde(default)]
  193. pub plugins: HashMap<String, Plugin>,
  194. #[serde(default)]
  195. pub log: LogSettings,
  196. }
  197. #[derive(Debug, Clone, Default, Serialize)]
  198. pub struct AccountConf {
  199. pub account: AccountSettings,
  200. pub conf: FileAccount,
  201. pub conf_override: MailUIConf,
  202. pub mailbox_confs: HashMap<String, FileMailboxConf>,
  203. }
  204. impl AccountConf {
  205. pub fn account(&self) -> &AccountSettings {
  206. &self.account
  207. }
  208. pub fn account_mut(&mut self) -> &mut AccountSettings {
  209. &mut self.account
  210. }
  211. pub fn conf(&self) -> &FileAccount {
  212. &self.conf
  213. }
  214. pub fn conf_mut(&mut self) -> &mut FileAccount {
  215. &mut self.conf
  216. }
  217. }
  218. impl From<FileAccount> for AccountConf {
  219. fn from(x: FileAccount) -> Self {
  220. let format = x.format.to_lowercase();
  221. let root_mailbox = x.root_mailbox.clone();
  222. let identity = x.identity.clone();
  223. let display_name = x.display_name.clone();
  224. let mailboxes = x
  225. .mailboxes
  226. .iter()
  227. .map(|(k, v)| (k.clone(), v.mailbox_conf.clone()))
  228. .collect();
  229. let acc = AccountSettings {
  230. name: String::new(),
  231. root_mailbox,
  232. format,
  233. identity,
  234. read_only: x.read_only,
  235. display_name,
  236. subscribed_mailboxes: x.subscribed_mailboxes.clone(),
  237. mailboxes,
  238. manual_refresh: x.manual_refresh,
  239. extra: x.extra.clone(),
  240. };
  241. let mailbox_confs = x.mailboxes.clone();
  242. AccountConf {
  243. account: acc,
  244. conf_override: x.conf_override.clone(),
  245. conf: x,
  246. mailbox_confs,
  247. }
  248. }
  249. }
  250. pub fn get_config_file() -> Result<PathBuf> {
  251. let xdg_dirs = xdg::BaseDirectories::with_prefix("meli").map_err(|err| {
  252. MeliError::new(format!(
  253. "Could not detect XDG directories for user: {}",
  254. err
  255. ))
  256. .set_source(Some(std::sync::Arc::new(Box::new(err))))
  257. })?;
  258. match env::var("MELI_CONFIG") {
  259. Ok(path) => Ok(PathBuf::from(path)),
  260. Err(_) => Ok(xdg_dirs
  261. .place_config_file("config.toml")
  262. .chain_err_summary(|| {
  263. format!(
  264. "Cannot create configuration directory in {}",
  265. xdg_dirs.get_config_home().display()
  266. )
  267. })?),
  268. }
  269. }
  270. impl FileSettings {
  271. pub fn new() -> Result<FileSettings> {
  272. let config_path = get_config_file()?;
  273. if !config_path.exists() {
  274. let path_string = config_path.display().to_string();
  275. if path_string.is_empty() {
  276. return Err(MeliError::new("No configuration found."));
  277. }
  278. println!(
  279. "No configuration found. Would you like to generate one in {}? [Y/n]",
  280. path_string
  281. );
  282. let mut buffer = String::new();
  283. let stdin = io::stdin();
  284. let mut handle = stdin.lock();
  285. loop {
  286. buffer.clear();
  287. handle
  288. .read_line(&mut buffer)
  289. .expect("Could not read from stdin.");
  290. match buffer.trim() {
  291. "" | "Y" | "y" | "yes" | "YES" | "Yes" => {
  292. create_config_file(&config_path)?;
  293. return Err(MeliError::new(
  294. "Edit the sample configuration and relaunch meli.",
  295. ));
  296. }
  297. "n" | "N" | "no" | "No" | "NO" => {
  298. return Err(MeliError::new("No configuration file found."));
  299. }
  300. _ => {
  301. println!(
  302. "No configuration found. Would you like to generate one in {}? [Y/n]",
  303. path_string
  304. );
  305. }
  306. }
  307. }
  308. }
  309. FileSettings::validate(config_path)
  310. }
  311. pub fn validate(path: PathBuf) -> Result<Self> {
  312. let s = pp::pp(&path)?;
  313. let mut s: FileSettings = toml::from_str(&s).map_err(|e| {
  314. MeliError::new(format!(
  315. "{}:\nConfig file contains errors: {}",
  316. path.display(),
  317. e.to_string()
  318. ))
  319. })?;
  320. let mut backends = melib::backends::Backends::new();
  321. let plugin_manager = crate::plugins::PluginManager::new();
  322. for (_, p) in s.plugins.clone() {
  323. if crate::plugins::PluginKind::Backend == p.kind() {
  324. crate::plugins::backend::PluginBackend::register(
  325. plugin_manager.listener(),
  326. p.clone(),
  327. &mut backends,
  328. );
  329. }
  330. }
  331. let Themes {
  332. light: default_light,
  333. dark: default_dark,
  334. ..
  335. } = Themes::default();
  336. for (k, v) in default_light.keys.into_iter() {
  337. if !s.terminal.themes.light.contains_key(&k) {
  338. s.terminal.themes.light.insert(k, v);
  339. }
  340. }
  341. for theme in s.terminal.themes.other_themes.values_mut() {
  342. for (k, v) in default_dark.keys.clone().into_iter() {
  343. if !theme.contains_key(&k) {
  344. theme.insert(k, v);
  345. }
  346. }
  347. }
  348. for (k, v) in default_dark.keys.into_iter() {
  349. if !s.terminal.themes.dark.contains_key(&k) {
  350. s.terminal.themes.dark.insert(k, v);
  351. }
  352. }
  353. match s.terminal.theme.as_str() {
  354. "dark" | "light" => {}
  355. t if s.terminal.themes.other_themes.contains_key(t) => {}
  356. t => {
  357. return Err(MeliError::new(format!("Theme `{}` was not found.", t)));
  358. }
  359. }
  360. s.terminal.themes.validate()?;
  361. for (name, acc) in &s.accounts {
  362. let FileAccount {
  363. root_mailbox,
  364. format,
  365. identity,
  366. read_only,
  367. display_name,
  368. subscribed_mailboxes,
  369. mailboxes,
  370. extra,
  371. manual_refresh,
  372. refresh_command: _,
  373. search_backend: _,
  374. conf_override: _,
  375. } = acc.clone();
  376. let lowercase_format = format.to_lowercase();
  377. let s = AccountSettings {
  378. name: name.to_string(),
  379. root_mailbox,
  380. format: format.clone(),
  381. identity,
  382. read_only,
  383. display_name,
  384. subscribed_mailboxes,
  385. manual_refresh,
  386. mailboxes: mailboxes
  387. .into_iter()
  388. .map(|(k, v)| (k, v.mailbox_conf))
  389. .collect(),
  390. extra,
  391. };
  392. backends.validate_config(&lowercase_format, &s)?;
  393. }
  394. Ok(s)
  395. }
  396. }
  397. #[derive(Debug, Clone, Default, Serialize)]
  398. pub struct Settings {
  399. pub accounts: HashMap<String, AccountConf>,
  400. pub pager: PagerSettings,
  401. pub listing: ListingSettings,
  402. pub notifications: NotificationsSettings,
  403. pub shortcuts: Shortcuts,
  404. pub tags: TagsSettings,
  405. pub composing: ComposingSettings,
  406. pub pgp: PGPSettings,
  407. pub terminal: TerminalSettings,
  408. pub plugins: HashMap<String, Plugin>,
  409. pub log: LogSettings,
  410. }
  411. impl Settings {
  412. pub fn new() -> Result<Settings> {
  413. let fs = FileSettings::new()?;
  414. let mut s: HashMap<String, AccountConf> = HashMap::new();
  415. for (id, x) in fs.accounts {
  416. let mut ac = AccountConf::from(x);
  417. ac.account.set_name(id.clone());
  418. s.insert(id, ac);
  419. }
  420. if let Some(ref log_path) = fs.log.log_file {
  421. melib::change_log_dest(log_path.into());
  422. }
  423. if fs.log.maximum_level != melib::LoggingLevel::default() {
  424. melib::change_log_level(fs.log.maximum_level);
  425. }
  426. Ok(Settings {
  427. accounts: s,
  428. pager: fs.pager,
  429. listing: fs.listing,
  430. notifications: fs.notifications,
  431. shortcuts: fs.shortcuts,
  432. tags: fs.tags,
  433. composing: fs.composing,
  434. pgp: fs.pgp,
  435. terminal: fs.terminal,
  436. plugins: fs.plugins,
  437. log: fs.log,
  438. })
  439. }
  440. pub fn without_accounts() -> Result<Settings> {
  441. let fs = FileSettings::new()?;
  442. if let Some(ref log_path) = fs.log.log_file {
  443. melib::change_log_dest(log_path.into());
  444. }
  445. if fs.log.maximum_level != melib::LoggingLevel::default() {
  446. melib::change_log_level(fs.log.maximum_level);
  447. }
  448. Ok(Settings {
  449. accounts: HashMap::new(),
  450. pager: fs.pager,
  451. listing: fs.listing,
  452. notifications: fs.notifications,
  453. shortcuts: fs.shortcuts,
  454. tags: fs.tags,
  455. composing: fs.composing,
  456. pgp: fs.pgp,
  457. terminal: fs.terminal,
  458. plugins: fs.plugins,
  459. log: fs.log,
  460. })
  461. }
  462. }
  463. #[derive(Copy, Debug, Clone, Hash, PartialEq)]
  464. pub enum IndexStyle {
  465. Plain,
  466. Threaded,
  467. Compact,
  468. Conversations,
  469. }
  470. impl Default for IndexStyle {
  471. fn default() -> Self {
  472. IndexStyle::Compact
  473. }
  474. }
  475. /*
  476. * Deserialize default functions
  477. */
  478. mod default_vals {
  479. pub(in crate::conf) fn false_val<T: std::convert::From<bool>>() -> T {
  480. false.into()
  481. }
  482. pub(in crate::conf) fn true_val<T: std::convert::From<bool>>() -> T {
  483. true.into()
  484. }
  485. pub(in crate::conf) fn zero_val<T: std::convert::From<usize>>() -> T {
  486. 0.into()
  487. }
  488. pub(in crate::conf) fn eighty_val<T: std::convert::From<usize>>() -> T {
  489. 80.into()
  490. }
  491. pub(in crate::conf) fn none<T>() -> Option<T> {
  492. None
  493. }
  494. pub(in crate::conf) fn internal_value_false<T: std::convert::From<super::ToggleFlag>>() -> T {
  495. super::ToggleFlag::InternalVal(false).into()
  496. }
  497. pub(in crate::conf) fn internal_value_true<T: std::convert::From<super::ToggleFlag>>() -> T {
  498. super::ToggleFlag::InternalVal(true).into()
  499. }
  500. }
  501. mod deserializers {
  502. use serde::{Deserialize, Deserializer};
  503. pub(in crate::conf) fn non_empty_string<'de, D, T: std::convert::From<Option<String>>>(
  504. deserializer: D,
  505. ) -> std::result::Result<T, D::Error>
  506. where
  507. D: Deserializer<'de>,
  508. {
  509. let s = <String>::deserialize(deserializer)?;
  510. if s.is_empty() {
  511. Ok(None.into())
  512. } else {
  513. Ok(Some(s).into())
  514. }
  515. }
  516. use toml::Value;
  517. fn any_of<'de, D>(deserializer: D) -> std::result::Result<String, D::Error>
  518. where
  519. D: Deserializer<'de>,
  520. {
  521. let v: Value = Deserialize::deserialize(deserializer)?;
  522. let mut ret = v.to_string();
  523. if ret.starts_with('"') && ret.ends_with('"') {
  524. ret.drain(0..1).count();
  525. ret.drain(ret.len() - 1..).count();
  526. }
  527. Ok(ret)
  528. }
  529. use std::collections::HashMap;
  530. pub(in crate::conf) fn extra_settings<'de, D>(
  531. deserializer: D,
  532. ) -> std::result::Result<HashMap<String, String>, D::Error>
  533. where
  534. D: Deserializer<'de>,
  535. {
  536. /* Why is this needed? If the user gives a configuration value such as key = true, the
  537. * parsing will fail since it expects string values. We want to accept key = true as well
  538. * as key = "true". */
  539. #[derive(Deserialize)]
  540. struct Wrapper(#[serde(deserialize_with = "any_of")] String);
  541. let v = <HashMap<String, Wrapper>>::deserialize(deserializer)?;
  542. Ok(v.into_iter().map(|(k, Wrapper(v))| (k, v)).collect())
  543. }
  544. }
  545. impl<'de> Deserialize<'de> for IndexStyle {
  546. fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
  547. where
  548. D: Deserializer<'de>,
  549. {
  550. let s = <String>::deserialize(deserializer)?;
  551. match s.as_str() {
  552. "Plain" | "plain" => Ok(IndexStyle::Plain),
  553. "Threaded" | "threaded" => Ok(IndexStyle::Threaded),
  554. "Compact" | "compact" => Ok(IndexStyle::Compact),
  555. "Conversations" | "conversations" => Ok(IndexStyle::Conversations),
  556. _ => Err(de::Error::custom("invalid `index_style` value")),
  557. }
  558. }
  559. }
  560. impl Serialize for IndexStyle {
  561. fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
  562. where
  563. S: Serializer,
  564. {
  565. match self {
  566. IndexStyle::Plain => serializer.serialize_str("plain"),
  567. IndexStyle::Threaded => serializer.serialize_str("threaded"),
  568. IndexStyle::Compact => serializer.serialize_str("compact"),
  569. IndexStyle::Conversations => serializer.serialize_str("conversations"),
  570. }
  571. }
  572. }
  573. #[derive(Debug, Clone, PartialEq)]
  574. pub enum SearchBackend {
  575. None,
  576. #[cfg(feature = "sqlite3")]
  577. Sqlite3,
  578. }
  579. impl Default for SearchBackend {
  580. fn default() -> Self {
  581. #[cfg(feature = "sqlite3")]
  582. {
  583. SearchBackend::Sqlite3
  584. }
  585. #[cfg(not(feature = "sqlite3"))]
  586. {
  587. SearchBackend::None
  588. }
  589. }
  590. }
  591. impl<'de> Deserialize<'de> for SearchBackend {
  592. fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
  593. where
  594. D: Deserializer<'de>,
  595. {
  596. let s = <String>::deserialize(deserializer)?;
  597. match s.as_str() {
  598. #[cfg(feature = "sqlite3")]
  599. "sqlite3" => Ok(SearchBackend::Sqlite3),
  600. "nothing" | "none" | "" => Ok(SearchBackend::None),
  601. _ => Err(de::Error::custom("invalid `search_backend` value")),
  602. }
  603. }
  604. }
  605. impl Serialize for SearchBackend {
  606. fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
  607. where
  608. S: Serializer,
  609. {
  610. match self {
  611. #[cfg(feature = "sqlite3")]
  612. SearchBackend::Sqlite3 => serializer.serialize_str("sqlite3"),
  613. SearchBackend::None => serializer.serialize_str("none"),
  614. }
  615. }
  616. }
  617. pub fn create_config_file(p: &Path) -> Result<()> {
  618. let mut file = OpenOptions::new()
  619. .write(true)
  620. .create_new(true)
  621. .open(p)
  622. .expect("Could not create config file.");
  623. file.write_all(include_bytes!("../samples/sample-config.toml"))
  624. .expect("Could not write to config file.");
  625. println!("Written example configuration to {}", p.display());
  626. let metadata = file.metadata()?;
  627. let mut permissions = metadata.permissions();
  628. permissions.set_mode(0o600); // Read/write for owner only.
  629. file.set_permissions(permissions)?;
  630. Ok(())
  631. }
  632. mod pp {
  633. //! Preprocess configuration files by unfolding `include` macros.
  634. use melib::{
  635. error::{MeliError, Result},
  636. parsec::*,
  637. ShellExpandTrait,
  638. };
  639. use std::io::Read;
  640. use std::path::{Path, PathBuf};
  641. /// Try to parse line into a path to be included.
  642. fn include_directive<'a>() -> impl Parser<'a, Option<&'a str>> {
  643. move |input: &'a str| {
  644. enum State {
  645. Start,
  646. Path,
  647. }
  648. use State::*;
  649. let mut state = State::Start;
  650. let mut i = 0;
  651. while i < input.len() {
  652. match (&state, input.as_bytes()[i]) {
  653. (Start, b'#') => {
  654. return Ok(("", None));
  655. }
  656. (Start, b) if (b as char).is_whitespace() => { /* consume */ }
  657. (Start, _) if input.as_bytes()[i..].starts_with(b"include(") => {
  658. i += "include(".len();
  659. state = Path;
  660. continue;
  661. }
  662. (Start, _) => {
  663. return Ok(("", None));
  664. }
  665. (Path, b'"') | (Path, b'\'') | (Path, b'`') => {
  666. let mut end = i + 1;
  667. while end < input.len() && input.as_bytes()[end] != input.as_bytes()[i] {
  668. end += 1;
  669. }
  670. if end == input.len() {
  671. return Err(input);
  672. }
  673. let ret = &input[i + 1..end];
  674. end += 1;
  675. if end < input.len() && input.as_bytes()[end] != b')' {
  676. /* Nothing else allowed in line */
  677. return Err(input);
  678. }
  679. end += 1;
  680. while end < input.len() {
  681. if !(input.as_bytes()[end] as char).is_whitespace() {
  682. /* Nothing else allowed in line */
  683. return Err(input);
  684. }
  685. end += 1;
  686. }
  687. return Ok(("", Some(ret)));
  688. }
  689. (Path, _) => return Err(input),
  690. }
  691. i += 1;
  692. }
  693. return Ok(("", None));
  694. }
  695. }
  696. /// Expands `include` macros in path.
  697. fn pp_helper(path: &Path, level: u8) -> Result<String> {
  698. if level > 7 {
  699. return Err(MeliError::new(format!("Maximum recursion limit reached while unfolding include directives in {}. Have you included a config file within itself?", path.display())));
  700. }
  701. let mut contents = String::new();
  702. let mut file = std::fs::File::open(path)?;
  703. file.read_to_string(&mut contents)?;
  704. let mut ret = String::with_capacity(contents.len());
  705. for (i, l) in contents.lines().enumerate() {
  706. if let (_, Some(sub_path)) = include_directive().parse(l).map_err(|l| {
  707. MeliError::new(format!(
  708. "Malformed include directive in line {} of file {}: {}\nConfiguration uses the standard m4 macro include(`filename`).",
  709. i,
  710. path.display(),
  711. l
  712. ))
  713. })? {
  714. let mut p = Path::new(sub_path).expand();
  715. if p.is_relative() {
  716. /* We checked that path is ok above so we can do unwrap here */
  717. let prefix = path.parent().unwrap();
  718. p = prefix.join(p)
  719. }
  720. ret.push_str(&pp_helper(&p, level + 1)?);
  721. } else {
  722. ret.push_str(l);
  723. ret.push('\n');
  724. }
  725. }
  726. Ok(ret)
  727. }
  728. /// Expands `include` macros in configuration file and other configuration files (eg. themes)
  729. /// in the filesystem.
  730. pub fn pp<P: AsRef<Path>>(path: P) -> Result<String> {
  731. let p_buf: PathBuf = if path.as_ref().is_relative() {
  732. path.as_ref().expand().canonicalize()?
  733. } else {
  734. path.as_ref().expand()
  735. };
  736. let mut ret = pp_helper(&p_buf, 0)?;
  737. drop(p_buf);
  738. if let Ok(xdg_dirs) = xdg::BaseDirectories::with_prefix("meli") {
  739. for theme_mailbox in xdg_dirs.find_config_files("themes") {
  740. let read_dir = std::fs::read_dir(theme_mailbox)?;
  741. for theme in read_dir {
  742. ret.push_str(&pp_helper(&theme?.path(), 0)?);
  743. }
  744. }
  745. }
  746. Ok(ret)
  747. }
  748. }
  749. #[derive(Debug, Clone, Default, Serialize, Deserialize)]
  750. pub struct LogSettings {
  751. #[serde(default)]
  752. log_file: Option<PathBuf>,
  753. #[serde(default)]
  754. maximum_level: melib::LoggingLevel,
  755. }
  756. pub use dotaddressable::*;
  757. mod dotaddressable {
  758. use super::*;
  759. pub trait DotAddressable: Serialize {
  760. fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
  761. if !path.is_empty() {
  762. Err(MeliError::new(format!(
  763. "{} has no fields, it is of type {}",
  764. parent_field,
  765. std::any::type_name::<Self>()
  766. )))
  767. } else {
  768. Ok(toml::to_string(self).map_err(|err| err.to_string())?)
  769. }
  770. }
  771. }
  772. impl DotAddressable for bool {}
  773. impl DotAddressable for String {}
  774. impl DotAddressable for IndexStyle {}
  775. impl DotAddressable for u64 {}
  776. impl DotAddressable for crate::terminal::Color {}
  777. impl DotAddressable for crate::terminal::Attr {}
  778. impl DotAddressable for crate::terminal::Key {}
  779. impl DotAddressable for usize {}
  780. impl DotAddressable for Query {}
  781. impl DotAddressable for melib::LoggingLevel {}
  782. impl DotAddressable for PathBuf {}
  783. impl DotAddressable for ToggleFlag {}
  784. impl DotAddressable for SearchBackend {}
  785. impl DotAddressable for melib::SpecialUsageMailbox {}
  786. impl<T: DotAddressable> DotAddressable for Option<T> {}
  787. impl<T: DotAddressable> DotAddressable for Vec<T> {}
  788. impl<K: DotAddressable + std::cmp::Eq + std::hash::Hash, V: DotAddressable> DotAddressable
  789. for HashMap<K, V>
  790. {
  791. }
  792. impl<K: DotAddressable + std::cmp::Eq + std::hash::Hash> DotAddressable for HashSet<K> {}
  793. impl DotAddressable for LogSettings {
  794. fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
  795. match path.first() {
  796. Some(field) => {
  797. let tail = &path[1..];
  798. match *field {
  799. "log_file" => self.log_file.lookup(field, tail),
  800. "maximum_level" => self.maximum_level.lookup(field, tail),
  801. other => Err(MeliError::new(format!(
  802. "{} has no field named {}",
  803. parent_field, other
  804. ))),
  805. }
  806. }
  807. None => Ok(toml::to_string(self).map_err(|err| err.to_string())?),
  808. }
  809. }
  810. }
  811. impl DotAddressable for Settings {
  812. fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
  813. match path.first() {
  814. Some(field) => {
  815. let tail = &path[1..];
  816. match *field {
  817. "accounts" => self.accounts.lookup(field, tail),
  818. "pager" => self.pager.lookup(field, tail),
  819. "listing" => self.listing.lookup(field, tail),
  820. "notifications" => Err(MeliError::new("unimplemented")),
  821. "shortcuts" => self.shortcuts.lookup(field, tail),
  822. "tags" => Err(MeliError::new("unimplemented")),
  823. "composing" => Err(MeliError::new("unimplemented")),
  824. "pgp" => Err(MeliError::new("unimplemented")),
  825. "terminal" => self.terminal.lookup(field, tail),
  826. "plugins" => Err(MeliError::new("unimplemented")),
  827. "log" => self.log.lookup(field, tail),
  828. other => Err(MeliError::new(format!(
  829. "{} has no field named {}",
  830. parent_field, other
  831. ))),
  832. }
  833. }
  834. None => Ok(toml::to_string(self).map_err(|err| err.to_string())?),
  835. }
  836. }
  837. }
  838. impl DotAddressable for AccountConf {
  839. fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
  840. match path.first() {
  841. Some(field) => {
  842. let tail = &path[1..];
  843. match *field {
  844. "account" => self.account.lookup(field, tail),
  845. "conf" => self.conf.lookup(field, tail),
  846. "conf_override" => self.conf_override.lookup(field, tail),
  847. "mailbox_confs" => self.mailbox_confs.lookup(field, tail),
  848. other => Err(MeliError::new(format!(
  849. "{} has no field named {}",
  850. parent_field, other
  851. ))),
  852. }
  853. }
  854. None => Ok(toml::to_string(self).map_err(|err| err.to_string())?),
  855. }
  856. }
  857. }
  858. impl DotAddressable for MailUIConf {
  859. fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
  860. match path.first() {
  861. Some(field) => {
  862. let _tail = &path[1..];
  863. match *field {
  864. "pager" => Err(MeliError::new("unimplemented")), //self.pager.lookup(field, tail),
  865. "listing" => Err(MeliError::new("unimplemented")), // self.listing.lookup(field, tail),
  866. "notifications" => Err(MeliError::new("unimplemented")), // self.notifications.lookup(field, tail),
  867. "shortcuts" => Err(MeliError::new("unimplemented")), //self.shortcuts.lookup(field, tail),
  868. "composing" => Err(MeliError::new("unimplemented")), //self.composing.lookup(field, tail),
  869. "identity" => Err(MeliError::new("unimplemented")), //self.identity.lookup(field, tail)<String>,
  870. "tags" => Err(MeliError::new("unimplemented")), //self.tags.lookup(field, tail),
  871. "themes" => Err(MeliError::new("unimplemented")), //self.themes.lookup(field, tail)<Themes>,
  872. "pgp" => Err(MeliError::new("unimplemented")), //self.pgp.lookup(field, tail),
  873. other => Err(MeliError::new(format!(
  874. "{} has no field named {}",
  875. parent_field, other
  876. ))),
  877. }
  878. }
  879. None => Ok(toml::to_string(self).map_err(|err| err.to_string())?),
  880. }
  881. }
  882. }
  883. impl DotAddressable for FileMailboxConf {
  884. fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
  885. match path.first() {
  886. Some(field) => {
  887. let tail = &path[1..];
  888. match *field {
  889. "conf_override" => self.conf_override.lookup(field, tail),
  890. "mailbox_conf" => self.mailbox_conf.lookup(field, tail),
  891. other => Err(MeliError::new(format!(
  892. "{} has no field named {}",
  893. parent_field, other
  894. ))),
  895. }
  896. }
  897. None => Ok(toml::to_string(self).map_err(|err| err.to_string())?),
  898. }
  899. }
  900. }
  901. impl DotAddressable for FileAccount {
  902. fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
  903. match path.first() {
  904. Some(field) => {
  905. let tail = &path[1..];
  906. match *field {
  907. "root_mailbox" => self.root_mailbox.lookup(field, tail),
  908. "format" => self.format.lookup(field, tail),
  909. "identity" => self.identity.lookup(field, tail),
  910. "display_name" => self.display_name.lookup(field, tail),
  911. "read_only" => self.read_only.lookup(field, tail),
  912. "subscribed_mailboxes" => self.subscribed_mailboxes.lookup(field, tail),
  913. "mailboxes" => self.mailboxes.lookup(field, tail),
  914. "search_backend" => self.search_backend.lookup(field, tail),
  915. "manual_refresh" => self.manual_refresh.lookup(field, tail),
  916. "refresh_command" => self.refresh_command.lookup(field, tail),
  917. "conf_override" => self.conf_override.lookup(field, tail),
  918. "extra" => self.extra.lookup(field, tail),
  919. other => Err(MeliError::new(format!(
  920. "{} has no field named {}",
  921. parent_field, other
  922. ))),
  923. }
  924. }
  925. None => Ok(toml::to_string(self).map_err(|err| err.to_string())?),
  926. }
  927. }
  928. }
  929. impl DotAddressable for melib::AccountSettings {
  930. fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
  931. match path.first() {
  932. Some(field) => {
  933. let tail = &path[1..];
  934. match *field {
  935. "name" => self.name.lookup(field, tail),
  936. "root_mailbox" => self.root_mailbox.lookup(field, tail),
  937. "format" => self.format.lookup(field, tail),
  938. "identity" => self.identity.lookup(field, tail),
  939. "read_only" => self.read_only.lookup(field, tail),
  940. "display_name" => self.display_name.lookup(field, tail),
  941. "subscribed_mailboxes" => self.subscribed_mailboxes.lookup(field, tail),
  942. "mailboxes" => self.mailboxes.lookup(field, tail),
  943. "manual_refresh" => self.manual_refresh.lookup(field, tail),
  944. "extra" => self.extra.lookup(field, tail),
  945. other => Err(MeliError::new(format!(
  946. "{} has no field named {}",
  947. parent_field, other
  948. ))),
  949. }
  950. }
  951. None => Ok(toml::to_string(self).map_err(|err| err.to_string())?),
  952. }
  953. }
  954. }
  955. impl DotAddressable for melib::MailboxConf {
  956. fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
  957. match path.first() {
  958. Some(field) => {
  959. let tail = &path[1..];
  960. match *field {
  961. "alias" => self.alias.lookup(field, tail),
  962. "autoload" => self.autoload.lookup(field, tail),
  963. "subscribe" => self.subscribe.lookup(field, tail),
  964. "ignore" => self.ignore.lookup(field, tail),
  965. "usage" => self.usage.lookup(field, tail),
  966. "extra" => self.extra.lookup(field, tail),
  967. other => Err(MeliError::new(format!(
  968. "{} has no field named {}",
  969. parent_field, other
  970. ))),
  971. }
  972. }
  973. None => Ok(toml::to_string(self).map_err(|err| err.to_string())?),
  974. }
  975. }
  976. }
  977. }