2018-08-19 13:12:48 +03:00
/*
* meli - configuration module .
*
* Copyright 2017 Manos Pitsidianakis
*
* This file is part of meli .
*
* meli is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* meli is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with meli . If not , see < http ://www.gnu.org/licenses/>.
* /
2020-02-04 15:52:12 +02:00
/*! Configuration logic and `config.toml` interfaces. */
2019-03-14 12:19:25 +02:00
extern crate bincode ;
2018-08-19 13:12:48 +03:00
extern crate serde ;
2019-08-25 14:14:54 +03:00
extern crate toml ;
2018-08-19 13:12:48 +03:00
extern crate xdg ;
2019-02-15 09:06:42 +02:00
2020-06-20 23:28:50 +03:00
use crate ::conf ::deserializers ::non_empty_string ;
use crate ::terminal ::Color ;
use melib ::search ::Query ;
use std ::collections ::HashSet ;
mod overrides ;
pub use overrides ::* ;
2019-09-27 12:48:48 +03:00
pub mod composing ;
2019-03-02 21:40:57 +02:00
pub mod notifications ;
2019-03-14 12:19:25 +02:00
pub mod pager ;
2019-09-26 12:10:36 +03:00
pub mod pgp ;
2019-12-01 12:05:14 +02:00
pub mod tags ;
2019-11-28 22:16:56 +02:00
#[ macro_use ]
2019-03-03 14:24:15 +02:00
pub mod shortcuts ;
2020-01-08 21:41:57 +02:00
mod listing ;
2019-10-06 10:58:47 +03:00
pub mod terminal ;
2020-01-19 14:43:36 +02:00
mod themes ;
pub use themes ::* ;
2018-08-19 13:12:48 +03:00
2019-02-10 18:26:49 +02:00
pub mod accounts ;
pub use self ::accounts ::Account ;
2019-09-27 12:48:48 +03:00
pub use self ::composing ::* ;
2019-09-26 12:10:36 +03:00
pub use self ::pgp ::* ;
2019-03-14 12:19:25 +02:00
pub use self ::shortcuts ::* ;
2019-12-01 12:05:14 +02:00
pub use self ::tags ::* ;
2019-02-10 18:26:49 +02:00
2019-04-06 00:30:06 +03:00
use self ::default_vals ::* ;
2020-06-20 23:28:50 +03:00
use self ::listing ::ListingSettings ;
use self ::notifications ::NotificationsSettings ;
2019-10-06 10:58:47 +03:00
use self ::terminal ::TerminalSettings ;
2020-06-20 23:28:50 +03:00
use crate ::pager ::PagerSettings ;
2020-02-26 10:54:10 +02:00
use melib ::conf ::{ AccountSettings , MailboxConf , ToggleFlag } ;
2018-08-30 15:54:30 +03:00
use melib ::error ::* ;
2018-08-19 13:12:48 +03:00
2019-08-25 21:44:19 +03:00
use serde ::{ de , Deserialize , Deserializer , Serialize , Serializer } ;
2020-08-17 15:31:30 +03:00
use indexmap ::IndexMap ;
2018-08-19 13:12:48 +03:00
use std ::collections ::HashMap ;
2018-09-05 08:22:10 +03:00
use std ::env ;
2019-11-27 22:13:14 +02:00
use std ::fs ::OpenOptions ;
use std ::io ::{ self , BufRead , Write } ;
2019-11-24 17:00:55 +02:00
use std ::os ::unix ::fs ::PermissionsExt ;
use std ::path ::{ Path , PathBuf } ;
2018-09-05 08:22:10 +03:00
2019-04-06 00:30:06 +03:00
#[ macro_export ]
macro_rules ! split_command {
( $cmd :expr ) = > { {
$cmd . split_whitespace ( ) . collect ::< Vec < & str > > ( )
} } ;
2018-09-05 08:22:10 +03:00
}
2020-03-01 22:51:58 +02:00
#[ macro_export ]
2020-09-12 23:02:06 +03:00
macro_rules ! account_settings {
2020-08-17 15:31:30 +03:00
( $context :ident [ $account_hash :expr ] . $setting :ident . $field :ident ) = > { {
$context . accounts [ & $account_hash ]
2020-03-18 19:13:07 +02:00
. settings
2020-03-01 22:51:58 +02:00
. conf_override
2020-03-18 19:13:07 +02:00
. $setting
2020-03-01 22:51:58 +02:00
. $field
. as_ref ( )
2020-03-18 19:13:07 +02:00
. unwrap_or ( & $context . settings . $setting . $field )
2020-03-01 22:51:58 +02:00
} } ;
}
#[ macro_export ]
macro_rules ! mailbox_settings {
2020-08-17 15:31:30 +03:00
( $context :ident [ $account_hash :expr ] [ $mailbox_path :expr ] . $setting :ident . $field :ident ) = > { {
$context . accounts [ & $account_hash ] [ $mailbox_path ]
2020-03-01 22:51:58 +02:00
. conf
. conf_override
2020-03-18 19:13:07 +02:00
. $setting
2020-03-01 22:51:58 +02:00
. $field
. as_ref ( )
2020-08-17 15:31:30 +03:00
. or ( $context . accounts [ & $account_hash ]
2020-03-18 19:13:07 +02:00
. settings
. conf_override
. $setting
. $field
. as_ref ( ) )
. unwrap_or ( & $context . settings . $setting . $field )
2020-03-01 22:51:58 +02:00
} } ;
}
2019-09-09 21:38:37 +03:00
#[ derive(Default, Debug, Clone, Serialize, Deserialize) ]
2020-08-03 22:53:06 +03:00
#[ serde(deny_unknown_fields) ]
2019-09-09 21:38:37 +03:00
pub struct MailUIConf {
2020-03-18 19:13:07 +02:00
#[ serde(default) ]
pub pager : PagerSettingsOverride ,
#[ serde(default) ]
pub listing : ListingSettingsOverride ,
#[ serde(default) ]
pub notifications : NotificationsSettingsOverride ,
#[ serde(default) ]
pub shortcuts : ShortcutsOverride ,
#[ serde(default) ]
pub composing : ComposingSettingsOverride ,
#[ serde(default) ]
2019-09-09 21:38:37 +03:00
pub identity : Option < String > ,
2020-03-18 19:13:07 +02:00
#[ serde(default) ]
pub tags : TagsSettingsOverride ,
#[ serde(default) ]
2020-06-02 02:47:02 +03:00
pub themes : Option < Themes > ,
2020-03-18 19:13:07 +02:00
#[ serde(default) ]
pub pgp : PGPSettingsOverride ,
2019-09-09 21:38:37 +03:00
}
2019-11-15 19:51:42 +02:00
#[ derive(Debug, Default, Clone, Serialize, Deserialize) ]
2021-09-04 16:52:17 +03:00
#[ serde(default) ]
2020-02-26 10:54:10 +02:00
pub struct FileMailboxConf {
2019-09-09 21:38:37 +03:00
#[ serde(flatten) ]
pub conf_override : MailUIConf ,
2019-12-01 22:29:31 +02:00
#[ serde(flatten) ]
2020-02-26 10:54:10 +02:00
pub mailbox_conf : MailboxConf ,
2019-05-13 21:08:18 +03:00
}
2020-02-26 10:54:10 +02:00
impl FileMailboxConf {
2019-11-15 19:51:42 +02:00
pub fn conf_override ( & self ) -> & MailUIConf {
& self . conf_override
2019-05-13 21:08:18 +03:00
}
2018-09-05 08:22:10 +03:00
2020-02-26 10:54:10 +02:00
pub fn mailbox_conf ( & self ) -> & MailboxConf {
& self . mailbox_conf
2018-09-05 08:22:10 +03:00
}
}
2018-08-19 13:12:48 +03:00
2019-10-24 18:16:41 +03:00
use crate ::conf ::deserializers ::extra_settings ;
2019-08-25 21:44:19 +03:00
#[ derive(Debug, Clone, Default, Serialize, Deserialize) ]
2018-08-19 13:12:48 +03:00
pub struct FileAccount {
2020-02-26 10:54:10 +02:00
root_mailbox : String ,
2018-08-19 13:12:48 +03:00
format : String ,
2018-08-30 15:54:30 +03:00
identity : String ,
2019-04-06 00:30:06 +03:00
#[ serde(default = " none " ) ]
2018-08-30 15:54:30 +03:00
display_name : Option < String > ,
2019-04-06 00:30:06 +03:00
2019-07-18 20:16:51 +03:00
#[ serde(default = " false_val " ) ]
read_only : bool ,
2020-02-08 23:45:49 +02:00
#[ serde(default) ]
2020-02-26 10:54:10 +02:00
subscribed_mailboxes : Vec < String > ,
2019-11-15 19:51:42 +02:00
#[ serde(default) ]
2020-08-17 15:31:30 +03:00
mailboxes : IndexMap < String , FileMailboxConf > ,
2019-11-07 22:35:30 +02:00
#[ serde(default) ]
2020-07-16 23:57:00 +03:00
search_backend : SearchBackend ,
2020-05-29 20:25:11 +03:00
#[ serde(default = " false_val " ) ]
2019-12-14 18:55:46 +02:00
pub manual_refresh : bool ,
2020-01-08 21:41:57 +02:00
#[ serde(default = " none " ) ]
pub refresh_command : Option < String > ,
2019-12-14 18:55:46 +02:00
#[ serde(flatten) ]
2020-03-18 19:13:07 +02:00
pub conf_override : MailUIConf ,
#[ serde(flatten) ]
2019-12-14 18:55:46 +02:00
#[ serde(deserialize_with = " extra_settings " ) ]
2020-08-17 15:31:30 +03:00
pub extra : IndexMap < String , String > , /* use custom deserializer to convert any given value (eg bool, number, etc) to string */
2018-08-19 13:12:48 +03:00
}
impl FileAccount {
2020-08-17 15:31:30 +03:00
pub fn mailboxes ( & self ) -> & IndexMap < String , FileMailboxConf > {
2020-02-26 10:54:10 +02:00
& self . mailboxes
2018-08-19 14:54:32 +03:00
}
2019-09-16 14:09:08 +03:00
2020-02-26 10:54:10 +02:00
pub fn mailbox ( & self ) -> & str {
& self . root_mailbox
2018-08-19 13:12:48 +03:00
}
2019-09-16 14:09:08 +03:00
2020-07-16 23:57:00 +03:00
pub fn search_backend ( & self ) -> & SearchBackend {
& self . search_backend
2019-11-07 22:35:30 +02:00
}
2018-08-19 13:12:48 +03:00
}
2019-08-25 21:44:19 +03:00
#[ derive(Debug, Clone, Default, Serialize, Deserialize) ]
2020-08-03 22:53:06 +03:00
#[ serde(deny_unknown_fields) ]
2019-11-22 18:43:24 +02:00
pub struct FileSettings {
2020-08-17 15:31:30 +03:00
pub accounts : IndexMap < String , FileAccount > ,
2019-06-10 18:29:49 +03:00
#[ serde(default) ]
2020-01-24 16:15:31 +02:00
pub pager : PagerSettings ,
2019-06-10 18:29:49 +03:00
#[ serde(default) ]
2020-01-24 16:15:31 +02:00
pub listing : ListingSettings ,
2020-01-08 21:41:57 +02:00
#[ serde(default) ]
2020-01-24 16:15:31 +02:00
pub notifications : NotificationsSettings ,
2019-06-10 18:29:49 +03:00
#[ serde(default) ]
2020-01-24 16:15:31 +02:00
pub shortcuts : Shortcuts ,
pub composing : ComposingSettings ,
2019-09-26 12:10:36 +03:00
#[ serde(default) ]
2020-01-24 16:15:31 +02:00
pub tags : TagsSettings ,
2019-12-01 12:05:14 +02:00
#[ serde(default) ]
2020-01-24 16:15:31 +02:00
pub pgp : PGPSettings ,
2019-10-06 10:58:47 +03:00
#[ serde(default) ]
2020-01-24 16:15:31 +02:00
pub terminal : TerminalSettings ,
2019-12-23 17:08:57 +02:00
#[ serde(default) ]
2020-05-28 16:02:57 +03:00
pub log : LogSettings ,
2018-08-19 13:12:48 +03:00
}
2020-07-17 13:12:57 +03:00
#[ derive(Debug, Clone, Default, Serialize) ]
2018-08-19 14:08:20 +03:00
pub struct AccountConf {
2020-07-25 19:12:48 +03:00
pub account : AccountSettings ,
pub conf : FileAccount ,
2020-03-18 19:13:07 +02:00
pub conf_override : MailUIConf ,
2020-08-17 15:31:30 +03:00
pub mailbox_confs : IndexMap < String , FileMailboxConf > ,
2018-08-19 13:30:43 +03:00
}
2018-08-19 14:08:20 +03:00
impl AccountConf {
2018-08-19 13:30:43 +03:00
pub fn account ( & self ) -> & AccountSettings {
& self . account
}
2020-03-18 19:13:07 +02:00
pub fn account_mut ( & mut self ) -> & mut AccountSettings {
& mut self . account
}
2018-08-19 13:30:43 +03:00
pub fn conf ( & self ) -> & FileAccount {
& self . conf
}
2018-08-19 14:08:20 +03:00
pub fn conf_mut ( & mut self ) -> & mut FileAccount {
& mut self . conf
}
2018-08-19 13:30:43 +03:00
}
2020-07-25 18:39:20 +03:00
impl From < FileAccount > for AccountConf {
fn from ( x : FileAccount ) -> Self {
let format = x . format . to_lowercase ( ) ;
let root_mailbox = x . root_mailbox . clone ( ) ;
let identity = x . identity . clone ( ) ;
let display_name = x . display_name . clone ( ) ;
let mailboxes = x
. mailboxes
. iter ( )
. map ( | ( k , v ) | ( k . clone ( ) , v . mailbox_conf . clone ( ) ) )
. collect ( ) ;
let acc = AccountSettings {
name : String ::new ( ) ,
root_mailbox ,
format ,
identity ,
read_only : x . read_only ,
display_name ,
subscribed_mailboxes : x . subscribed_mailboxes . clone ( ) ,
mailboxes ,
manual_refresh : x . manual_refresh ,
2020-08-17 15:31:30 +03:00
extra : x . extra . clone ( ) . into_iter ( ) . collect ( ) ,
2020-07-25 18:39:20 +03:00
} ;
let mailbox_confs = x . mailboxes . clone ( ) ;
AccountConf {
account : acc ,
conf_override : x . conf_override . clone ( ) ,
conf : x ,
mailbox_confs ,
}
}
2018-08-19 13:12:48 +03:00
}
2020-06-07 18:02:20 +03:00
pub fn get_config_file ( ) -> Result < PathBuf > {
let xdg_dirs = xdg ::BaseDirectories ::with_prefix ( " meli " ) . map_err ( | err | {
MeliError ::new ( format! (
" Could not detect XDG directories for user: {} " ,
err
) )
. set_source ( Some ( std ::sync ::Arc ::new ( Box ::new ( err ) ) ) )
} ) ? ;
match env ::var ( " MELI_CONFIG " ) {
Ok ( path ) = > Ok ( PathBuf ::from ( path ) ) ,
Err ( _ ) = > Ok ( xdg_dirs
. place_config_file ( " config.toml " )
. chain_err_summary ( | | {
format! (
" Cannot create configuration directory in {} " ,
xdg_dirs . get_config_home ( ) . display ( )
)
} ) ? ) ,
}
}
2021-09-04 18:50:34 +03:00
struct Ask {
message : String ,
}
impl Ask {
fn run ( self ) -> bool {
let mut buffer = String ::new ( ) ;
let stdin = io ::stdin ( ) ;
let mut handle = stdin . lock ( ) ;
print! ( " {} [Y/n] " , & self . message ) ;
loop {
buffer . clear ( ) ;
handle
. read_line ( & mut buffer )
. expect ( " Could not read from stdin. " ) ;
match buffer . trim ( ) {
" " | " Y " | " y " | " yes " | " YES " | " Yes " = > {
return true ;
}
" n " | " N " | " no " | " No " | " NO " = > {
return false ;
}
_ = > {
print! ( " \n {} [Y/n] " , & self . message ) ;
}
}
}
}
}
2018-08-19 13:12:48 +03:00
impl FileSettings {
2018-08-30 15:54:30 +03:00
pub fn new ( ) -> Result < FileSettings > {
2020-06-07 18:02:20 +03:00
let config_path = get_config_file ( ) ? ;
2018-09-05 08:22:10 +03:00
if ! config_path . exists ( ) {
2020-07-10 15:55:15 +03:00
let path_string = config_path . display ( ) . to_string ( ) ;
if path_string . is_empty ( ) {
return Err ( MeliError ::new ( " No configuration found. " ) ) ;
}
2021-09-04 18:50:34 +03:00
let ask = Ask {
message : format ! (
" No configuration found. Would you like to generate one in {}? " ,
path_string
) ,
} ;
if ask . run ( ) {
create_config_file ( & config_path ) ? ;
return Err ( MeliError ::new (
" Edit the sample configuration and relaunch meli. " ,
) ) ;
2019-06-10 18:29:49 +03:00
}
2021-09-04 18:50:34 +03:00
return Err ( MeliError ::new ( " No configuration file found. " ) ) ;
2018-09-05 08:22:10 +03:00
}
2019-08-25 14:14:54 +03:00
2021-09-04 18:50:34 +03:00
FileSettings ::validate ( config_path , true )
2018-08-19 13:12:48 +03:00
}
2019-11-22 18:43:24 +02:00
2021-09-04 18:50:34 +03:00
pub fn validate ( path : PathBuf , interactive : bool ) -> Result < Self > {
2020-06-07 18:02:20 +03:00
let s = pp ::pp ( & path ) ? ;
2021-09-04 18:50:34 +03:00
let map : toml ::map ::Map < String , toml ::value ::Value > = toml ::from_str ( & s ) . map_err ( | e | {
MeliError ::new ( format! (
" {}: \n Config file is invalid TOML: {} " ,
path . display ( ) ,
e . to_string ( )
) )
} ) ? ;
/*
* Check that a global composing option is set and return a user - friendly error message because the
* default serde one is confusing .
* /
if ! map . contains_key ( " composing " ) {
let err_msg = r #" You must set a global `composing` option. If you override `composing` in each account, you can use a dummy global like follows:
[ composing ]
send_mail = ' / bin / false '
This is required so that you don ' t accidentally start meli and find out later that you can ' t send emails . " #;
if interactive {
println! ( " {} " , err_msg ) ;
let ask = Ask {
message : format ! (
" Would you like to append this dummy value in your configuration file {} and continue? " ,
path . display ( )
)
} ;
if ask . run ( ) {
let mut file = OpenOptions ::new ( ) . append ( true ) . open ( & path ) ? ;
file . write_all ( " [composing] \n send_mail = '/bin/false' \n " . as_bytes ( ) )
. map_err ( | err | {
MeliError ::new ( format! (
" Could not append to {}: {} " ,
path . display ( ) ,
err
) )
} ) ? ;
return FileSettings ::validate ( path , interactive ) ;
}
}
return Err ( MeliError ::new ( format! (
" {} \n \n Edit the {} and relaunch meli. " ,
if interactive { " " } else { err_msg } ,
path . display ( )
) ) ) ;
}
2020-01-24 16:05:25 +02:00
let mut s : FileSettings = toml ::from_str ( & s ) . map_err ( | e | {
2019-12-01 22:29:31 +02:00
MeliError ::new ( format! (
" {}: \n Config file contains errors: {} " ,
2020-06-07 18:02:20 +03:00
path . display ( ) ,
2019-12-01 22:29:31 +02:00
e . to_string ( )
) )
2019-11-27 14:22:53 +02:00
} ) ? ;
2020-08-20 17:37:19 +03:00
let backends = melib ::backends ::Backends ::new ( ) ;
2020-06-02 02:47:02 +03:00
let Themes {
2020-01-24 16:15:31 +02:00
light : default_light ,
dark : default_dark ,
..
2020-06-02 02:47:02 +03:00
} = Themes ::default ( ) ;
2020-06-02 15:40:05 +03:00
for ( k , v ) in default_light . keys . into_iter ( ) {
2020-01-24 16:15:31 +02:00
if ! s . terminal . themes . light . contains_key ( & k ) {
s . terminal . themes . light . insert ( k , v ) ;
}
}
for theme in s . terminal . themes . other_themes . values_mut ( ) {
2020-06-02 15:40:05 +03:00
for ( k , v ) in default_dark . keys . clone ( ) . into_iter ( ) {
2020-01-24 16:15:31 +02:00
if ! theme . contains_key ( & k ) {
theme . insert ( k , v ) ;
2020-01-24 16:05:25 +02:00
}
}
}
2020-06-02 15:40:05 +03:00
for ( k , v ) in default_dark . keys . into_iter ( ) {
2020-01-24 16:15:31 +02:00
if ! s . terminal . themes . dark . contains_key ( & k ) {
s . terminal . themes . dark . insert ( k , v ) ;
}
}
2020-01-24 16:05:25 +02:00
match s . terminal . theme . as_str ( ) {
" dark " | " light " = > { }
t if s . terminal . themes . other_themes . contains_key ( t ) = > { }
t = > {
return Err ( MeliError ::new ( format! ( " Theme ` {} ` was not found. " , t ) ) ) ;
}
}
2020-01-24 16:15:31 +02:00
s . terminal . themes . validate ( ) ? ;
for ( name , acc ) in & s . accounts {
2019-11-27 14:22:53 +02:00
let FileAccount {
2020-02-26 10:54:10 +02:00
root_mailbox ,
2019-11-27 14:22:53 +02:00
format ,
identity ,
read_only ,
display_name ,
2020-02-26 10:54:10 +02:00
subscribed_mailboxes ,
mailboxes ,
2019-11-27 14:22:53 +02:00
extra ,
2019-12-14 18:55:46 +02:00
manual_refresh ,
2020-01-08 21:41:57 +02:00
refresh_command : _ ,
2020-07-16 23:57:00 +03:00
search_backend : _ ,
2020-03-18 19:13:07 +02:00
conf_override : _ ,
2020-01-24 16:15:31 +02:00
} = acc . clone ( ) ;
2019-11-27 14:22:53 +02:00
let lowercase_format = format . to_lowercase ( ) ;
let s = AccountSettings {
2020-01-24 16:15:31 +02:00
name : name . to_string ( ) ,
2020-02-26 10:54:10 +02:00
root_mailbox ,
2019-11-27 14:22:53 +02:00
format : format . clone ( ) ,
identity ,
read_only ,
display_name ,
2020-02-26 10:54:10 +02:00
subscribed_mailboxes ,
2019-12-14 18:55:46 +02:00
manual_refresh ,
2020-02-26 10:54:10 +02:00
mailboxes : mailboxes
2019-11-27 14:22:53 +02:00
. into_iter ( )
2020-02-26 10:54:10 +02:00
. map ( | ( k , v ) | ( k , v . mailbox_conf ) )
2019-11-27 14:22:53 +02:00
. collect ( ) ,
2020-08-17 15:31:30 +03:00
extra : extra . into_iter ( ) . collect ( ) ,
2019-11-27 14:22:53 +02:00
} ;
backends . validate_config ( & lowercase_format , & s ) ? ;
2019-11-22 18:43:24 +02:00
}
2020-01-24 16:15:31 +02:00
Ok ( s )
2019-11-22 18:43:24 +02:00
}
2018-08-19 13:12:48 +03:00
}
2020-07-25 18:39:20 +03:00
#[ derive(Debug, Clone, Default, Serialize) ]
pub struct Settings {
2020-08-17 15:31:30 +03:00
pub accounts : IndexMap < String , AccountConf > ,
2020-07-25 18:39:20 +03:00
pub pager : PagerSettings ,
pub listing : ListingSettings ,
pub notifications : NotificationsSettings ,
pub shortcuts : Shortcuts ,
pub tags : TagsSettings ,
pub composing : ComposingSettings ,
pub pgp : PGPSettings ,
pub terminal : TerminalSettings ,
pub log : LogSettings ,
}
2018-08-19 13:12:48 +03:00
impl Settings {
2019-11-16 00:33:22 +02:00
pub fn new ( ) -> Result < Settings > {
let fs = FileSettings ::new ( ) ? ;
2020-08-17 15:31:30 +03:00
let mut s : IndexMap < String , AccountConf > = IndexMap ::new ( ) ;
2018-08-19 13:12:48 +03:00
for ( id , x ) in fs . accounts {
2018-08-30 15:54:30 +03:00
let mut ac = AccountConf ::from ( x ) ;
ac . account . set_name ( id . clone ( ) ) ;
s . insert ( id , ac ) ;
2018-08-19 13:12:48 +03:00
}
2020-05-28 16:02:57 +03:00
if let Some ( ref log_path ) = fs . log . log_file {
melib ::change_log_dest ( log_path . into ( ) ) ;
}
if fs . log . maximum_level ! = melib ::LoggingLevel ::default ( ) {
melib ::change_log_level ( fs . log . maximum_level ) ;
}
2019-11-16 00:33:22 +02:00
Ok ( Settings {
2018-08-19 13:12:48 +03:00
accounts : s ,
pager : fs . pager ,
2020-01-08 21:41:57 +02:00
listing : fs . listing ,
2019-03-02 21:40:57 +02:00
notifications : fs . notifications ,
2019-03-03 14:24:15 +02:00
shortcuts : fs . shortcuts ,
2019-12-01 12:05:14 +02:00
tags : fs . tags ,
2019-09-27 12:48:48 +03:00
composing : fs . composing ,
2019-09-26 12:10:36 +03:00
pgp : fs . pgp ,
2019-10-06 10:58:47 +03:00
terminal : fs . terminal ,
2020-05-28 16:02:57 +03:00
log : fs . log ,
2019-11-16 00:33:22 +02:00
} )
2018-08-19 13:12:48 +03:00
}
2020-06-10 18:06:28 +03:00
pub fn without_accounts ( ) -> Result < Settings > {
let fs = FileSettings ::new ( ) ? ;
if let Some ( ref log_path ) = fs . log . log_file {
melib ::change_log_dest ( log_path . into ( ) ) ;
}
if fs . log . maximum_level ! = melib ::LoggingLevel ::default ( ) {
melib ::change_log_level ( fs . log . maximum_level ) ;
}
Ok ( Settings {
2020-08-17 15:31:30 +03:00
accounts : IndexMap ::new ( ) ,
2020-06-10 18:06:28 +03:00
pager : fs . pager ,
listing : fs . listing ,
notifications : fs . notifications ,
shortcuts : fs . shortcuts ,
tags : fs . tags ,
composing : fs . composing ,
pgp : fs . pgp ,
terminal : fs . terminal ,
log : fs . log ,
} )
}
2018-08-19 13:12:48 +03:00
}
2019-02-18 23:14:06 +02:00
2019-09-09 21:38:37 +03:00
#[ derive(Copy, Debug, Clone, Hash, PartialEq) ]
2019-02-18 23:14:06 +02:00
pub enum IndexStyle {
Plain ,
Threaded ,
Compact ,
2019-09-14 12:43:19 +03:00
Conversations ,
2019-02-18 23:14:06 +02:00
}
impl Default for IndexStyle {
fn default ( ) -> Self {
IndexStyle ::Compact
}
}
2019-04-06 00:30:06 +03:00
/*
* Deserialize default functions
* /
mod default_vals {
2020-03-18 19:13:07 +02:00
pub ( in crate ::conf ) fn false_val < T : std ::convert ::From < bool > > ( ) -> T {
false . into ( )
2019-04-06 00:30:06 +03:00
}
2020-03-18 19:13:07 +02:00
pub ( in crate ::conf ) fn true_val < T : std ::convert ::From < bool > > ( ) -> T {
true . into ( )
2019-04-06 00:30:06 +03:00
}
2020-03-18 19:13:07 +02:00
pub ( in crate ::conf ) fn zero_val < T : std ::convert ::From < usize > > ( ) -> T {
0. into ( )
2019-04-06 00:30:06 +03:00
}
2020-03-18 19:13:07 +02:00
pub ( in crate ::conf ) fn eighty_val < T : std ::convert ::From < usize > > ( ) -> T {
80. into ( )
2019-04-06 00:30:06 +03:00
}
2019-08-23 21:32:32 +03:00
pub ( in crate ::conf ) fn none < T > ( ) -> Option < T > {
2019-04-06 00:30:06 +03:00
None
}
2019-09-15 23:36:30 +03:00
2020-03-18 19:13:07 +02:00
pub ( in crate ::conf ) fn internal_value_false < T : std ::convert ::From < super ::ToggleFlag > > ( ) -> T {
super ::ToggleFlag ::InternalVal ( false ) . into ( )
2019-09-15 23:36:30 +03:00
}
2020-01-02 00:07:19 +02:00
2020-03-18 19:13:07 +02:00
pub ( in crate ::conf ) fn internal_value_true < T : std ::convert ::From < super ::ToggleFlag > > ( ) -> T {
super ::ToggleFlag ::InternalVal ( true ) . into ( )
2020-01-02 00:07:19 +02:00
}
2019-04-06 00:30:06 +03:00
}
2019-09-09 21:38:37 +03:00
2019-10-03 19:51:34 +03:00
mod deserializers {
use serde ::{ Deserialize , Deserializer } ;
2020-03-18 19:13:07 +02:00
pub ( in crate ::conf ) fn non_empty_string < ' de , D , T : std ::convert ::From < Option < String > > > (
2019-10-03 19:51:34 +03:00
deserializer : D ,
2020-03-18 19:13:07 +02:00
) -> std ::result ::Result < T , D ::Error >
2019-10-03 19:51:34 +03:00
where
D : Deserializer < ' de > ,
{
let s = < String > ::deserialize ( deserializer ) ? ;
if s . is_empty ( ) {
2020-03-18 19:13:07 +02:00
Ok ( None . into ( ) )
2019-10-03 19:51:34 +03:00
} else {
2020-03-18 19:13:07 +02:00
Ok ( Some ( s ) . into ( ) )
2019-10-03 19:51:34 +03:00
}
}
2019-10-24 18:16:41 +03:00
use toml ::Value ;
fn any_of < ' de , D > ( deserializer : D ) -> std ::result ::Result < String , D ::Error >
where
D : Deserializer < ' de > ,
{
let v : Value = Deserialize ::deserialize ( deserializer ) ? ;
let mut ret = v . to_string ( ) ;
if ret . starts_with ( '"' ) & & ret . ends_with ( '"' ) {
ret . drain ( 0 .. 1 ) . count ( ) ;
ret . drain ( ret . len ( ) - 1 .. ) . count ( ) ;
}
Ok ( ret )
}
2020-08-17 15:31:30 +03:00
use indexmap ::IndexMap ;
2019-10-24 18:16:41 +03:00
pub ( in crate ::conf ) fn extra_settings < ' de , D > (
deserializer : D ,
2020-08-17 15:31:30 +03:00
) -> std ::result ::Result < IndexMap < String , String > , D ::Error >
2019-10-24 18:16:41 +03:00
where
D : Deserializer < ' de > ,
{
/* Why is this needed? If the user gives a configuration value such as key = true, the
* parsing will fail since it expects string values . We want to accept key = true as well
* as key = " true " . * /
#[ derive(Deserialize) ]
struct Wrapper ( #[ serde(deserialize_with = " any_of " ) ] String ) ;
2020-08-17 15:31:30 +03:00
let v = < IndexMap < String , Wrapper > > ::deserialize ( deserializer ) ? ;
2019-10-24 18:16:41 +03:00
Ok ( v . into_iter ( ) . map ( | ( k , Wrapper ( v ) ) | ( k , v ) ) . collect ( ) )
}
2019-10-03 19:51:34 +03:00
}
2019-09-09 21:38:37 +03:00
impl < ' de > Deserialize < ' de > for IndexStyle {
fn deserialize < D > ( deserializer : D ) -> std ::result ::Result < Self , D ::Error >
where
D : Deserializer < ' de > ,
{
let s = < String > ::deserialize ( deserializer ) ? ;
match s . as_str ( ) {
" Plain " | " plain " = > Ok ( IndexStyle ::Plain ) ,
" Threaded " | " threaded " = > Ok ( IndexStyle ::Threaded ) ,
" Compact " | " compact " = > Ok ( IndexStyle ::Compact ) ,
2019-09-14 12:43:19 +03:00
" Conversations " | " conversations " = > Ok ( IndexStyle ::Conversations ) ,
_ = > Err ( de ::Error ::custom ( " invalid `index_style` value " ) ) ,
2019-09-09 21:38:37 +03:00
}
}
}
impl Serialize for IndexStyle {
fn serialize < S > ( & self , serializer : S ) -> std ::result ::Result < S ::Ok , S ::Error >
where
S : Serializer ,
{
match self {
IndexStyle ::Plain = > serializer . serialize_str ( " plain " ) ,
IndexStyle ::Threaded = > serializer . serialize_str ( " threaded " ) ,
IndexStyle ::Compact = > serializer . serialize_str ( " compact " ) ,
2019-09-14 12:43:19 +03:00
IndexStyle ::Conversations = > serializer . serialize_str ( " conversations " ) ,
2019-09-09 21:38:37 +03:00
}
}
}
2019-10-30 21:09:48 +02:00
2020-09-18 21:38:50 +03:00
#[ derive(Debug, Copy, Clone, PartialEq) ]
2020-07-16 23:57:00 +03:00
pub enum SearchBackend {
2019-11-07 22:35:30 +02:00
None ,
2020-09-18 21:38:50 +03:00
Auto ,
2019-10-30 21:09:48 +02:00
#[ cfg(feature = " sqlite3 " ) ]
Sqlite3 ,
}
2020-07-16 23:57:00 +03:00
impl Default for SearchBackend {
2019-10-30 21:09:48 +02:00
fn default ( ) -> Self {
2020-09-18 21:38:50 +03:00
SearchBackend ::Auto
2019-11-07 22:35:30 +02:00
}
}
2020-07-16 23:57:00 +03:00
impl < ' de > Deserialize < ' de > for SearchBackend {
2019-11-07 22:35:30 +02:00
fn deserialize < D > ( deserializer : D ) -> std ::result ::Result < Self , D ::Error >
where
D : Deserializer < ' de > ,
{
let s = < String > ::deserialize ( deserializer ) ? ;
match s . as_str ( ) {
#[ cfg(feature = " sqlite3 " ) ]
2020-09-18 21:38:50 +03:00
sqlite3 if sqlite3 . eq_ignore_ascii_case ( " sqlite3 " ) = > Ok ( SearchBackend ::Sqlite3 ) ,
none if none . eq_ignore_ascii_case ( " none " )
| | none . eq_ignore_ascii_case ( " nothing " )
| | none . is_empty ( ) = >
{
Ok ( SearchBackend ::None )
}
auto if auto . eq_ignore_ascii_case ( " auto " ) = > Ok ( SearchBackend ::Auto ) ,
2020-07-16 23:57:00 +03:00
_ = > Err ( de ::Error ::custom ( " invalid `search_backend` value " ) ) ,
2019-11-07 22:35:30 +02:00
}
}
}
2020-07-16 23:57:00 +03:00
impl Serialize for SearchBackend {
2019-11-07 22:35:30 +02:00
fn serialize < S > ( & self , serializer : S ) -> std ::result ::Result < S ::Ok , S ::Error >
where
S : Serializer ,
{
match self {
#[ cfg(feature = " sqlite3 " ) ]
2020-07-16 23:57:00 +03:00
SearchBackend ::Sqlite3 = > serializer . serialize_str ( " sqlite3 " ) ,
SearchBackend ::None = > serializer . serialize_str ( " none " ) ,
2020-09-18 21:38:50 +03:00
SearchBackend ::Auto = > serializer . serialize_str ( " auto " ) ,
2019-10-30 21:09:48 +02:00
}
}
}
2019-11-23 17:56:38 +02:00
2019-11-24 17:00:55 +02:00
pub fn create_config_file ( p : & Path ) -> Result < ( ) > {
let mut file = OpenOptions ::new ( )
. write ( true )
. create_new ( true )
. open ( p )
. expect ( " Could not create config file. " ) ;
2020-10-17 15:02:38 +03:00
file . write_all ( include_bytes! ( " ../docs/samples/sample-config.toml " ) )
2019-11-24 17:00:55 +02:00
. expect ( " Could not write to config file. " ) ;
println! ( " Written example configuration to {} " , p . display ( ) ) ;
let metadata = file . metadata ( ) ? ;
let mut permissions = metadata . permissions ( ) ;
permissions . set_mode ( 0o600 ) ; // Read/write for owner only.
file . set_permissions ( permissions ) ? ;
Ok ( ( ) )
}
2019-11-27 22:13:14 +02:00
mod pp {
2020-02-04 15:52:12 +02:00
//! Preprocess configuration files by unfolding `include` macros.
2019-11-27 22:13:14 +02:00
use melib ::{
error ::{ MeliError , Result } ,
parsec ::* ,
2020-02-08 23:51:33 +02:00
ShellExpandTrait ,
2019-11-27 22:13:14 +02:00
} ;
use std ::io ::Read ;
2019-11-29 12:15:05 +02:00
use std ::path ::{ Path , PathBuf } ;
2019-11-27 22:13:14 +02:00
2020-02-04 15:52:12 +02:00
/// Try to parse line into a path to be included.
2019-11-27 22:13:14 +02:00
fn include_directive < ' a > ( ) -> impl Parser < ' a , Option < & ' a str > > {
move | input : & ' a str | {
enum State {
Start ,
Path ,
}
use State ::* ;
let mut state = State ::Start ;
let mut i = 0 ;
while i < input . len ( ) {
match ( & state , input . as_bytes ( ) [ i ] ) {
( Start , b '#' ) = > {
return Ok ( ( " " , None ) ) ;
}
2020-01-29 05:54:13 +02:00
( Start , b ) if ( b as char ) . is_whitespace ( ) = > { /* consume */ }
( Start , _ ) if input . as_bytes ( ) [ i .. ] . starts_with ( b " include( " ) = > {
i + = " include( " . len ( ) ;
2019-11-27 22:13:14 +02:00
state = Path ;
continue ;
}
2020-01-29 05:54:13 +02:00
( Start , _ ) = > {
2019-11-27 22:13:14 +02:00
return Ok ( ( " " , None ) ) ;
}
2020-01-29 05:54:13 +02:00
( Path , b '"' ) | ( Path , b '\'' ) | ( Path , b '`' ) = > {
2019-11-27 22:13:14 +02:00
let mut end = i + 1 ;
while end < input . len ( ) & & input . as_bytes ( ) [ end ] ! = input . as_bytes ( ) [ i ] {
end + = 1 ;
}
if end = = input . len ( ) {
return Err ( input ) ;
}
let ret = & input [ i + 1 .. end ] ;
end + = 1 ;
2020-01-29 05:54:13 +02:00
if end < input . len ( ) & & input . as_bytes ( ) [ end ] ! = b ')' {
/* Nothing else allowed in line */
return Err ( input ) ;
}
end + = 1 ;
2019-11-27 22:13:14 +02:00
while end < input . len ( ) {
if ! ( input . as_bytes ( ) [ end ] as char ) . is_whitespace ( ) {
/* Nothing else allowed in line */
return Err ( input ) ;
}
end + = 1 ;
}
return Ok ( ( " " , Some ( ret ) ) ) ;
}
( Path , _ ) = > return Err ( input ) ,
}
i + = 1 ;
}
return Ok ( ( " " , None ) ) ;
}
}
2020-02-04 15:52:12 +02:00
/// Expands `include` macros in path.
2019-11-29 12:15:05 +02:00
fn pp_helper ( path : & Path , level : u8 ) -> Result < String > {
2019-11-27 22:13:14 +02:00
if level > 7 {
2019-11-29 12:15:05 +02:00
return Err ( MeliError ::new ( format! ( " Maximum recursion limit reached while unfolding include directives in {} . Have you included a config file within itself? " , path . display ( ) ) ) ) ;
2019-11-27 22:13:14 +02:00
}
let mut contents = String ::new ( ) ;
let mut file = std ::fs ::File ::open ( path ) ? ;
file . read_to_string ( & mut contents ) ? ;
2020-01-29 05:54:13 +02:00
let mut ret = String ::with_capacity ( contents . len ( ) ) ;
2019-11-27 22:13:14 +02:00
for ( i , l ) in contents . lines ( ) . enumerate ( ) {
2020-01-29 05:54:13 +02:00
if let ( _ , Some ( sub_path ) ) = include_directive ( ) . parse ( l ) . map_err ( | l | {
2019-11-27 22:13:14 +02:00
MeliError ::new ( format! (
2020-01-29 05:54:13 +02:00
" Malformed include directive in line {} of file {}: {} \n Configuration uses the standard m4 macro include(`filename`). " ,
2019-11-29 12:15:05 +02:00
i ,
path . display ( ) ,
l
2019-11-27 22:13:14 +02:00
) )
} ) ? {
2020-02-08 23:51:33 +02:00
let mut p = Path ::new ( sub_path ) . expand ( ) ;
if p . is_relative ( ) {
2019-11-29 12:15:05 +02:00
/* We checked that path is ok above so we can do unwrap here */
let prefix = path . parent ( ) . unwrap ( ) ;
2020-02-08 23:51:33 +02:00
p = prefix . join ( p )
}
2019-11-29 12:15:05 +02:00
2020-07-13 18:49:27 +03:00
ret . push_str ( & pp_helper ( & p , level + 1 ) ? ) ;
2020-01-29 05:54:13 +02:00
} else {
ret . push_str ( l ) ;
ret . push ( '\n' ) ;
2019-11-27 22:13:14 +02:00
}
}
2020-01-29 05:54:13 +02:00
Ok ( ret )
2019-11-27 22:13:14 +02:00
}
2019-11-29 12:15:05 +02:00
2020-02-04 15:52:12 +02:00
/// Expands `include` macros in configuration file and other configuration files (eg. themes)
/// in the filesystem.
2020-01-24 16:05:25 +02:00
pub fn pp < P : AsRef < Path > > ( path : P ) -> Result < String > {
let p_buf : PathBuf = if path . as_ref ( ) . is_relative ( ) {
2020-02-08 23:51:33 +02:00
path . as_ref ( ) . expand ( ) . canonicalize ( ) ?
2019-11-29 12:15:05 +02:00
} else {
2020-02-08 23:51:33 +02:00
path . as_ref ( ) . expand ( )
2019-11-29 12:15:05 +02:00
} ;
2020-01-24 16:15:31 +02:00
let mut ret = pp_helper ( & p_buf , 0 ) ? ;
2019-11-29 12:15:05 +02:00
drop ( p_buf ) ;
2020-01-24 16:15:31 +02:00
if let Ok ( xdg_dirs ) = xdg ::BaseDirectories ::with_prefix ( " meli " ) {
2020-02-26 10:54:10 +02:00
for theme_mailbox in xdg_dirs . find_config_files ( " themes " ) {
let read_dir = std ::fs ::read_dir ( theme_mailbox ) ? ;
2020-01-24 16:15:31 +02:00
for theme in read_dir {
2020-07-13 18:49:27 +03:00
ret . push_str ( & pp_helper ( & theme ? . path ( ) , 0 ) ? ) ;
2020-01-24 16:15:31 +02:00
}
}
}
Ok ( ret )
2019-11-27 22:13:14 +02:00
}
}
2020-05-28 16:02:57 +03:00
#[ derive(Debug, Clone, Default, Serialize, Deserialize) ]
2020-08-03 22:53:06 +03:00
#[ serde(deny_unknown_fields) ]
2020-05-28 16:02:57 +03:00
pub struct LogSettings {
#[ serde(default) ]
log_file : Option < PathBuf > ,
#[ serde(default) ]
maximum_level : melib ::LoggingLevel ,
}
2020-07-17 13:12:57 +03:00
2020-07-25 19:12:48 +03:00
pub use dotaddressable ::* ;
mod dotaddressable {
use super ::* ;
pub trait DotAddressable : Serialize {
fn lookup ( & self , parent_field : & str , path : & [ & str ] ) -> Result < String > {
if ! path . is_empty ( ) {
Err ( MeliError ::new ( format! (
" {} has no fields, it is of type {} " ,
parent_field ,
std ::any ::type_name ::< Self > ( )
) ) )
} else {
Ok ( toml ::to_string ( self ) . map_err ( | err | err . to_string ( ) ) ? )
}
2020-07-17 13:12:57 +03:00
}
}
2020-07-25 19:12:48 +03:00
impl DotAddressable for bool { }
impl DotAddressable for String { }
2020-11-24 23:39:02 +02:00
impl DotAddressable for char { }
2020-07-25 19:12:48 +03:00
impl DotAddressable for IndexStyle { }
impl DotAddressable for u64 { }
impl DotAddressable for crate ::terminal ::Color { }
impl DotAddressable for crate ::terminal ::Attr { }
impl DotAddressable for crate ::terminal ::Key { }
impl DotAddressable for usize { }
impl DotAddressable for Query { }
impl DotAddressable for melib ::LoggingLevel { }
impl DotAddressable for PathBuf { }
impl DotAddressable for ToggleFlag { }
impl DotAddressable for SearchBackend { }
impl DotAddressable for melib ::SpecialUsageMailbox { }
impl < T : DotAddressable > DotAddressable for Option < T > { }
impl < T : DotAddressable > DotAddressable for Vec < T > { }
impl < K : DotAddressable + std ::cmp ::Eq + std ::hash ::Hash , V : DotAddressable > DotAddressable
for HashMap < K , V >
{
}
2020-08-17 15:31:30 +03:00
impl < K : DotAddressable + std ::cmp ::Eq + std ::hash ::Hash , V : DotAddressable > DotAddressable
for IndexMap < K , V >
{
}
2020-07-25 19:12:48 +03:00
impl < K : DotAddressable + std ::cmp ::Eq + std ::hash ::Hash > DotAddressable for HashSet < K > { }
impl DotAddressable for LogSettings {
fn lookup ( & self , parent_field : & str , path : & [ & str ] ) -> Result < String > {
match path . first ( ) {
Some ( field ) = > {
let tail = & path [ 1 .. ] ;
match * field {
" log_file " = > self . log_file . lookup ( field , tail ) ,
" maximum_level " = > self . maximum_level . lookup ( field , tail ) ,
other = > Err ( MeliError ::new ( format! (
" {} has no field named {} " ,
parent_field , other
) ) ) ,
}
2020-07-17 13:12:57 +03:00
}
2020-07-25 19:12:48 +03:00
None = > Ok ( toml ::to_string ( self ) . map_err ( | err | err . to_string ( ) ) ? ) ,
}
}
}
impl DotAddressable for Settings {
fn lookup ( & self , parent_field : & str , path : & [ & str ] ) -> Result < String > {
match path . first ( ) {
Some ( field ) = > {
let tail = & path [ 1 .. ] ;
match * field {
" accounts " = > self . accounts . lookup ( field , tail ) ,
" pager " = > self . pager . lookup ( field , tail ) ,
" listing " = > self . listing . lookup ( field , tail ) ,
2020-09-10 20:57:15 +03:00
" notifications " = > self . notifications . lookup ( field , tail ) ,
2020-07-25 19:12:48 +03:00
" shortcuts " = > self . shortcuts . lookup ( field , tail ) ,
" tags " = > Err ( MeliError ::new ( " unimplemented " ) ) ,
" composing " = > Err ( MeliError ::new ( " unimplemented " ) ) ,
" pgp " = > Err ( MeliError ::new ( " unimplemented " ) ) ,
" terminal " = > self . terminal . lookup ( field , tail ) ,
" log " = > self . log . lookup ( field , tail ) ,
other = > Err ( MeliError ::new ( format! (
" {} has no field named {} " ,
parent_field , other
) ) ) ,
}
}
None = > Ok ( toml ::to_string ( self ) . map_err ( | err | err . to_string ( ) ) ? ) ,
}
}
}
impl DotAddressable for AccountConf {
fn lookup ( & self , parent_field : & str , path : & [ & str ] ) -> Result < String > {
match path . first ( ) {
Some ( field ) = > {
let tail = & path [ 1 .. ] ;
match * field {
" account " = > self . account . lookup ( field , tail ) ,
" conf " = > self . conf . lookup ( field , tail ) ,
" conf_override " = > self . conf_override . lookup ( field , tail ) ,
" mailbox_confs " = > self . mailbox_confs . lookup ( field , tail ) ,
other = > Err ( MeliError ::new ( format! (
" {} has no field named {} " ,
parent_field , other
) ) ) ,
}
}
None = > Ok ( toml ::to_string ( self ) . map_err ( | err | err . to_string ( ) ) ? ) ,
}
}
}
impl DotAddressable for MailUIConf {
fn lookup ( & self , parent_field : & str , path : & [ & str ] ) -> Result < String > {
match path . first ( ) {
Some ( field ) = > {
let _tail = & path [ 1 .. ] ;
match * field {
" pager " = > Err ( MeliError ::new ( " unimplemented " ) ) , //self.pager.lookup(field, tail),
" listing " = > Err ( MeliError ::new ( " unimplemented " ) ) , // self.listing.lookup(field, tail),
" notifications " = > Err ( MeliError ::new ( " unimplemented " ) ) , // self.notifications.lookup(field, tail),
" shortcuts " = > Err ( MeliError ::new ( " unimplemented " ) ) , //self.shortcuts.lookup(field, tail),
" composing " = > Err ( MeliError ::new ( " unimplemented " ) ) , //self.composing.lookup(field, tail),
" identity " = > Err ( MeliError ::new ( " unimplemented " ) ) , //self.identity.lookup(field, tail)<String>,
" tags " = > Err ( MeliError ::new ( " unimplemented " ) ) , //self.tags.lookup(field, tail),
" themes " = > Err ( MeliError ::new ( " unimplemented " ) ) , //self.themes.lookup(field, tail)<Themes>,
" pgp " = > Err ( MeliError ::new ( " unimplemented " ) ) , //self.pgp.lookup(field, tail),
other = > Err ( MeliError ::new ( format! (
" {} has no field named {} " ,
parent_field , other
) ) ) ,
}
}
None = > Ok ( toml ::to_string ( self ) . map_err ( | err | err . to_string ( ) ) ? ) ,
}
}
}
impl DotAddressable for FileMailboxConf {
fn lookup ( & self , parent_field : & str , path : & [ & str ] ) -> Result < String > {
match path . first ( ) {
Some ( field ) = > {
let tail = & path [ 1 .. ] ;
match * field {
" conf_override " = > self . conf_override . lookup ( field , tail ) ,
" mailbox_conf " = > self . mailbox_conf . lookup ( field , tail ) ,
other = > Err ( MeliError ::new ( format! (
" {} has no field named {} " ,
parent_field , other
) ) ) ,
}
}
None = > Ok ( toml ::to_string ( self ) . map_err ( | err | err . to_string ( ) ) ? ) ,
2020-07-17 13:12:57 +03:00
}
}
}
2020-07-25 18:36:01 +03:00
2020-07-25 19:12:48 +03:00
impl DotAddressable for FileAccount {
fn lookup ( & self , parent_field : & str , path : & [ & str ] ) -> Result < String > {
match path . first ( ) {
Some ( field ) = > {
let tail = & path [ 1 .. ] ;
match * field {
" root_mailbox " = > self . root_mailbox . lookup ( field , tail ) ,
" format " = > self . format . lookup ( field , tail ) ,
" identity " = > self . identity . lookup ( field , tail ) ,
" display_name " = > self . display_name . lookup ( field , tail ) ,
" read_only " = > self . read_only . lookup ( field , tail ) ,
" subscribed_mailboxes " = > self . subscribed_mailboxes . lookup ( field , tail ) ,
" mailboxes " = > self . mailboxes . lookup ( field , tail ) ,
" search_backend " = > self . search_backend . lookup ( field , tail ) ,
" manual_refresh " = > self . manual_refresh . lookup ( field , tail ) ,
" refresh_command " = > self . refresh_command . lookup ( field , tail ) ,
" conf_override " = > self . conf_override . lookup ( field , tail ) ,
" extra " = > self . extra . lookup ( field , tail ) ,
other = > Err ( MeliError ::new ( format! (
" {} has no field named {} " ,
parent_field , other
) ) ) ,
}
}
None = > Ok ( toml ::to_string ( self ) . map_err ( | err | err . to_string ( ) ) ? ) ,
}
}
}
impl DotAddressable for melib ::AccountSettings {
fn lookup ( & self , parent_field : & str , path : & [ & str ] ) -> Result < String > {
match path . first ( ) {
Some ( field ) = > {
let tail = & path [ 1 .. ] ;
match * field {
" name " = > self . name . lookup ( field , tail ) ,
" root_mailbox " = > self . root_mailbox . lookup ( field , tail ) ,
" format " = > self . format . lookup ( field , tail ) ,
" identity " = > self . identity . lookup ( field , tail ) ,
" read_only " = > self . read_only . lookup ( field , tail ) ,
" display_name " = > self . display_name . lookup ( field , tail ) ,
" subscribed_mailboxes " = > self . subscribed_mailboxes . lookup ( field , tail ) ,
" mailboxes " = > self . mailboxes . lookup ( field , tail ) ,
" manual_refresh " = > self . manual_refresh . lookup ( field , tail ) ,
" extra " = > self . extra . lookup ( field , tail ) ,
other = > Err ( MeliError ::new ( format! (
" {} has no field named {} " ,
parent_field , other
) ) ) ,
}
}
None = > Ok ( toml ::to_string ( self ) . map_err ( | err | err . to_string ( ) ) ? ) ,
}
}
}
impl DotAddressable for melib ::MailboxConf {
fn lookup ( & self , parent_field : & str , path : & [ & str ] ) -> Result < String > {
match path . first ( ) {
Some ( field ) = > {
let tail = & path [ 1 .. ] ;
match * field {
" alias " = > self . alias . lookup ( field , tail ) ,
" autoload " = > self . autoload . lookup ( field , tail ) ,
" subscribe " = > self . subscribe . lookup ( field , tail ) ,
" ignore " = > self . ignore . lookup ( field , tail ) ,
" usage " = > self . usage . lookup ( field , tail ) ,
" extra " = > self . extra . lookup ( field , tail ) ,
other = > Err ( MeliError ::new ( format! (
" {} has no field named {} " ,
parent_field , other
) ) ) ,
}
2020-07-17 13:12:57 +03:00
}
2020-07-25 19:12:48 +03:00
None = > Ok ( toml ::to_string ( self ) . map_err ( | err | err . to_string ( ) ) ? ) ,
2020-07-17 13:12:57 +03:00
}
}
}
}
2021-09-04 18:50:34 +03:00
#[ test ]
fn test_config_parse ( ) {
use std ::fmt ::Write ;
use std ::fs ;
use std ::io ::prelude ::* ;
use std ::path ::PathBuf ;
struct ConfigFile {
path : PathBuf ,
file : fs ::File ,
}
const TEST_CONFIG : & str = r #"
[ accounts . account - name ]
root_mailbox = " /path/to/root/mailbox "
format = " Maildir "
index_style = " Conversations " # or [ plain , threaded , compact ]
identity = " email@example.com "
display_name = " Name "
subscribed_mailboxes = [ " INBOX " , " INBOX/Sent " , " INBOX/Drafts " , " INBOX/Junk " ]
# Set mailbox - specific settings
[ accounts . account - name . mailboxes ]
" INBOX " = { rename = " Inbox " }
" drafts " = { rename = " Drafts " }
" foobar-devel " = { ignore = true } # don ' t show notifications for this mailbox
# Setting up an mbox account
[ accounts . mbox ]
root_mailbox = " /var/mail/username "
format = " mbox "
index_style = " Compact "
identity = " username@hostname.local "
" #;
impl ConfigFile {
fn new ( ) -> std ::result ::Result < Self , std ::io ::Error > {
let mut f = fs ::File ::open ( " /dev/urandom " ) ? ;
let mut buf = [ 0 u8 ; 16 ] ;
f . read_exact ( & mut buf ) ? ;
let mut filename = String ::with_capacity ( 2 * 16 ) ;
for byte in buf {
write! ( & mut filename , " {:02X} " , byte ) . unwrap ( ) ;
}
let mut path = std ::env ::temp_dir ( ) ;
path . push ( & * filename ) ;
let mut file = OpenOptions ::new ( )
. create_new ( true )
. append ( true )
. open ( & path ) ? ;
file . write_all ( TEST_CONFIG . as_bytes ( ) ) ? ;
Ok ( ConfigFile { path , file } )
}
}
impl Drop for ConfigFile {
fn drop ( & mut self ) {
let _ = fs ::remove_file ( & self . path ) ;
}
}
let mut new_file = ConfigFile ::new ( ) . unwrap ( ) ;
let err = FileSettings ::validate ( new_file . path . clone ( ) , false ) . unwrap_err ( ) ;
assert! ( err . details . as_ref ( ) . starts_with ( " You must set a global `composing` option. If you override `composing` in each account, you can use a dummy global like follows " ) ) ;
new_file
. file
. write_all ( " [composing] \n send_mail = '/bin/false' \n " . as_bytes ( ) )
. unwrap ( ) ;
let err = FileSettings ::validate ( new_file . path . clone ( ) , false ) . unwrap_err ( ) ;
assert_eq! ( err . details . as_ref ( ) , " Configuration error (account-name): root_path `/path/to/root/mailbox` is not a valid directory. " ) ;
}