Add conf_override! macro

conf_override! wraps struct definitions and defines a secondary Override
struct that wraps each field in an Option. The macro mailbox_settings!
is used to select settings from an account & mailbox index. If a user defines an overriding setting, the macro returns the override instead of the immediately next in the hierarchy setting.

The selection is done for a specific field as follows:

  if per-folder override is defined, return per-folder override
    else if per-account override is defined, return per-account override
      else return global setting field value.
memfd
Manos Pitsidianakis 2020-03-18 19:13:07 +02:00
parent a8c1016f37
commit 9ff54f236b
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
17 changed files with 441 additions and 260 deletions

View File

@ -39,7 +39,7 @@ mod status;
pub use self::status::*; pub use self::status::*;
fn get_display_name(context: &Context, idx: usize) -> String { fn get_display_name(context: &Context, idx: usize) -> String {
let settings = context.accounts[idx].runtime_settings.account(); let settings = context.accounts[idx].settings.account();
if let Some(d) = settings.display_name.as_ref() { if let Some(d) = settings.display_name.as_ref() {
format!("{} <{}>", d, settings.identity) format!("{} <{}>", d, settings.identity)
} else { } else {

View File

@ -147,7 +147,9 @@ impl Composer {
id: ComponentId::new_v4(), id: ComponentId::new_v4(),
..Default::default() ..Default::default()
}; };
for (h, v) in context.settings.composing.default_header_values.iter() { for (h, v) in
mailbox_acc_settings!(context[account_cursor].composing.default_header_values).iter()
{
if v.is_empty() { if v.is_empty() {
continue; continue;
} }
@ -298,12 +300,9 @@ impl Composer {
write_string_to_grid( write_string_to_grid(
&format!( &format!(
"☑ sign with {}", "☑ sign with {}",
context mailbox_acc_settings!(context[self.account_cursor].pgp.key)
.settings
.pgp
.key
.as_ref() .as_ref()
.map(String::as_str) .map(|s| s.as_str())
.unwrap_or("default key") .unwrap_or("default key")
), ),
grid, grid,
@ -392,7 +391,9 @@ impl Component for Composer {
if !self.initialized { if !self.initialized {
if self.sign_mail.is_unset() { if self.sign_mail.is_unset() {
self.sign_mail = ToggleFlag::InternalVal(context.settings.pgp.auto_sign); self.sign_mail = ToggleFlag::InternalVal(*mailbox_acc_settings!(
context[self.account_cursor].pgp.auto_sign
));
} }
if !self.draft.headers().contains_key("From") || self.draft.headers()["From"].is_empty() if !self.draft.headers().contains_key("From") || self.draft.headers()["From"].is_empty()
{ {
@ -859,8 +860,10 @@ impl Component for Composer {
&& shortcut!(key == shortcuts[Self::DESCRIPTION]["edit_mail"]) => && shortcut!(key == shortcuts[Self::DESCRIPTION]["edit_mail"]) =>
{ {
/* Edit draft in $EDITOR */ /* Edit draft in $EDITOR */
let settings = &context.settings; let editor = if let Some(editor_cmd) =
let editor = if let Some(editor_cmd) = settings.composing.editor_cmd.as_ref() { mailbox_acc_settings!(context[self.account_cursor].composing.editor_cmd)
.as_ref()
{
editor_cmd.to_string() editor_cmd.to_string()
} else { } else {
match std::env::var("EDITOR") { match std::env::var("EDITOR") {
@ -884,7 +887,7 @@ impl Component for Composer {
true, true,
); );
if settings.composing.embed { if *mailbox_acc_settings!(context[self.account_cursor].composing.embed) {
self.embed = Some(EmbedStatus::Running( self.embed = Some(EmbedStatus::Running(
crate::terminal::embed::create_pty( crate::terminal::embed::create_pty(
width!(self.embed_area), width!(self.embed_area),
@ -1124,7 +1127,8 @@ impl Component for Composer {
Default::default() Default::default()
}; };
let our_map: ShortcutMap = context.settings.shortcuts.composing.key_values(); let our_map: ShortcutMap =
mailbox_acc_settings!(context[self.account_cursor].shortcuts.composing).key_values();
map.insert(Composer::DESCRIPTION, our_map); map.insert(Composer::DESCRIPTION, our_map);
map map
@ -1178,9 +1182,10 @@ pub fn send_draft(
) -> bool { ) -> bool {
use std::io::Write; use std::io::Write;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
let settings = &context.settings; let format_flowed = *mailbox_acc_settings!(context[account_cursor].composing.format_flowed);
let format_flowed = settings.composing.format_flowed; let parts = split_command!(mailbox_acc_settings!(
let parts = split_command!(settings.composing.mailer_cmd); context[account_cursor].composing.mailer_cmd
));
if parts.is_empty() { if parts.is_empty() {
context.replies.push_back(UIEvent::Notification( context.replies.push_back(UIEvent::Notification(
None, None,
@ -1233,8 +1238,12 @@ pub fn send_draft(
} }
let output = crate::components::mail::pgp::sign( let output = crate::components::mail::pgp::sign(
body.into(), body.into(),
context.settings.pgp.gpg_binary.as_ref().map(String::as_str), mailbox_acc_settings!(context[account_cursor].pgp.gpg_binary)
context.settings.pgp.key.as_ref().map(String::as_str), .as_ref()
.map(|s| s.as_str()),
mailbox_acc_settings!(context[account_cursor].pgp.key)
.as_ref()
.map(|s| s.as_str()),
); );
if let Err(e) = &output { if let Err(e) = &output {
debug!("{:?} could not sign draft msg", e); debug!("{:?} could not sign draft msg", e);

View File

@ -576,8 +576,9 @@ impl Component for Listing {
{ {
/* Account might have no mailboxes yet if it's offline */ /* Account might have no mailboxes yet if it's offline */
/* Check if per-mailbox configuration overrides general configuration */ /* Check if per-mailbox configuration overrides general configuration */
let index_style = let index_style = mailbox_settings!(
mailbox_acc_settings!(context[self.cursor_pos.0][mailbox_hash].index_style); context[self.cursor_pos.0][mailbox_hash].listing.index_style
);
self.component.set_style(*index_style); self.component.set_style(*index_style);
} }
context context
@ -1200,7 +1201,7 @@ impl Listing {
/* Check if per-mailbox configuration overrides general configuration */ /* Check if per-mailbox configuration overrides general configuration */
let index_style = let index_style =
mailbox_acc_settings!(context[self.cursor_pos.0][mailbox_hash].index_style); mailbox_settings!(context[self.cursor_pos.0][mailbox_hash].listing.index_style);
self.component.set_style(*index_style); self.component.set_style(*index_style);
} else { } else {
/* Set to dummy */ /* Set to dummy */

View File

@ -650,31 +650,27 @@ impl CompactListing {
hash: ThreadHash, hash: ThreadHash,
) -> EntryStrings { ) -> EntryStrings {
let thread = threads.thread_ref(hash); let thread = threads.thread_ref(hash);
let mailbox = &context.accounts[self.cursor_pos.0][&self.cursor_pos.1].conf;
let mut tags = String::new(); let mut tags = String::new();
let mut colors: SmallVec<[_; 8]> = SmallVec::new(); let mut colors: SmallVec<[_; 8]> = SmallVec::new();
let backend_lck = context.accounts[self.cursor_pos.0].backend.read().unwrap(); let backend_lck = context.accounts[self.cursor_pos.0].backend.read().unwrap();
if let Some(t) = backend_lck.tags() { if let Some(t) = backend_lck.tags() {
let tags_lck = t.read().unwrap(); let tags_lck = t.read().unwrap();
for t in e.labels().iter() { for t in e.labels().iter() {
if mailbox if mailbox_settings!(
.conf_override context[self.cursor_pos.0][&self.cursor_pos.1]
.tags .tags
.as_ref() .ignore_tags
.map(|s| s.ignore_tags.contains(t)) )
.unwrap_or(false) .contains(t)
{ {
continue; continue;
} }
tags.push(' '); tags.push(' ');
tags.push_str(tags_lck.get(t).as_ref().unwrap()); tags.push_str(tags_lck.get(t).as_ref().unwrap());
tags.push(' '); tags.push(' ');
if let Some(&c) = mailbox if let Some(&c) =
.conf_override mailbox_settings!(context[self.cursor_pos.0][&self.cursor_pos.1].tags.colors)
.tags .get(t)
.as_ref()
.map(|s| s.colors.get(t))
.unwrap_or(None)
{ {
colors.push(c); colors.push(c);
} else { } else {
@ -761,10 +757,12 @@ impl CompactListing {
.collection .collection
.get_env(root_env_hash); .get_env(root_env_hash);
use crate::cache::QueryTrait; use crate::cache::QueryTrait;
if let Some(filter_query) = if let Some(filter_query) = mailbox_settings!(
mailbox_settings!(context[self.cursor_pos.0][&self.cursor_pos.1].listing) context[self.cursor_pos.0][&self.cursor_pos.1]
.listing
.filter .filter
.as_ref() )
.as_ref()
{ {
if !root_envelope.is_match(filter_query) { if !root_envelope.is_match(filter_query) {
continue; continue;

View File

@ -593,20 +593,28 @@ impl ConversationsListing {
hash: ThreadHash, hash: ThreadHash,
) -> EntryStrings { ) -> EntryStrings {
let thread = threads.thread_ref(hash); let thread = threads.thread_ref(hash);
let settings = mailbox_settings!(context[self.cursor_pos.0][&self.cursor_pos.1].tags);
let mut tags = String::new(); let mut tags = String::new();
let mut colors = SmallVec::new(); let mut colors = SmallVec::new();
let backend_lck = context.accounts[self.cursor_pos.0].backend.read().unwrap(); let backend_lck = context.accounts[self.cursor_pos.0].backend.read().unwrap();
if let Some(t) = backend_lck.tags() { if let Some(t) = backend_lck.tags() {
let tags_lck = t.read().unwrap(); let tags_lck = t.read().unwrap();
for t in e.labels().iter() { for t in e.labels().iter() {
if settings.ignore_tags.contains(t) { if mailbox_settings!(
context[self.cursor_pos.0][&self.cursor_pos.1]
.tags
.ignore_tags
)
.contains(t)
{
continue; continue;
} }
tags.push(' '); tags.push(' ');
tags.push_str(tags_lck.get(t).as_ref().unwrap()); tags.push_str(tags_lck.get(t).as_ref().unwrap());
tags.push(' '); tags.push(' ');
if let Some(&c) = settings.colors.get(t) { if let Some(&c) =
mailbox_settings!(context[self.cursor_pos.0][&self.cursor_pos.1].tags.colors)
.get(t)
{
colors.push(c); colors.push(c);
} else { } else {
colors.push(Color::Byte(8)); colors.push(Color::Byte(8));
@ -698,10 +706,12 @@ impl ConversationsListing {
.collection .collection
.get_env(root_env_hash); .get_env(root_env_hash);
use crate::cache::QueryTrait; use crate::cache::QueryTrait;
if let Some(filter_query) = if let Some(filter_query) = mailbox_settings!(
mailbox_settings!(context[self.cursor_pos.0][&self.cursor_pos.1].listing) context[self.cursor_pos.0][&self.cursor_pos.1]
.listing
.filter .filter
.as_ref() )
.as_ref()
{ {
if !root_envelope.is_match(filter_query) { if !root_envelope.is_match(filter_query) {
continue; continue;

View File

@ -612,21 +612,30 @@ impl PlainListing {
id: ComponentId::new_v4(), id: ComponentId::new_v4(),
} }
} }
fn make_entry_string(&self, e: EnvelopeRef, context: &Context) -> EntryStrings { fn make_entry_string(&self, e: EnvelopeRef, context: &Context) -> EntryStrings {
let settings = mailbox_settings!(context[self.cursor_pos.0][&self.cursor_pos.1].tags);
let mut tags = String::new(); let mut tags = String::new();
let mut colors = SmallVec::new(); let mut colors = SmallVec::new();
let backend_lck = context.accounts[self.cursor_pos.0].backend.read().unwrap(); let backend_lck = context.accounts[self.cursor_pos.0].backend.read().unwrap();
if let Some(t) = backend_lck.tags() { if let Some(t) = backend_lck.tags() {
let tags_lck = t.read().unwrap(); let tags_lck = t.read().unwrap();
for t in e.labels().iter() { for t in e.labels().iter() {
if settings.ignore_tags.contains(t) { if mailbox_settings!(
context[self.cursor_pos.0][&self.cursor_pos.1]
.tags
.ignore_tags
)
.contains(t)
{
continue; continue;
} }
tags.push(' '); tags.push(' ');
tags.push_str(tags_lck.get(t).as_ref().unwrap()); tags.push_str(tags_lck.get(t).as_ref().unwrap());
tags.push(' '); tags.push(' ');
if let Some(&c) = settings.colors.get(t) { if let Some(&c) =
mailbox_settings!(context[self.cursor_pos.0][&self.cursor_pos.1].tags.colors)
.get(t)
{
colors.push(c); colors.push(c);
} else { } else {
colors.push(Color::Byte(8)); colors.push(Color::Byte(8));
@ -705,10 +714,12 @@ impl PlainListing {
} }
let envelope: EnvelopeRef = context.accounts[self.cursor_pos.0].collection.get_env(i); let envelope: EnvelopeRef = context.accounts[self.cursor_pos.0].collection.get_env(i);
use crate::cache::QueryTrait; use crate::cache::QueryTrait;
if let Some(filter_query) = if let Some(filter_query) = mailbox_settings!(
mailbox_settings!(context[self.cursor_pos.0][&self.cursor_pos.1].listing) context[self.cursor_pos.0][&self.cursor_pos.1]
.listing
.filter .filter
.as_ref() )
.as_ref()
{ {
if !envelope.is_match(filter_query) { if !envelope.is_match(filter_query) {
continue; continue;

View File

@ -290,7 +290,7 @@ impl StatusPanel {
None, None,
); );
write_string_to_grid( write_string_to_grid(
&a.runtime_settings.account().identity, &a.settings.account().identity,
&mut self.content, &mut self.content,
self.theme_default.fg, self.theme_default.fg,
self.theme_default.bg, self.theme_default.bg,

View File

@ -161,10 +161,15 @@ impl MailView {
Some(Box::new(move |a: &'closure Attachment, v: &mut Vec<u8>| { Some(Box::new(move |a: &'closure Attachment, v: &mut Vec<u8>| {
if a.content_type().is_text_html() { if a.content_type().is_text_html() {
use std::io::Write; use std::io::Write;
let settings =
mailbox_settings!(context[self.coordinates.0][&self.coordinates.1].pager);
/* FIXME: duplication with view/html.rs */ /* FIXME: duplication with view/html.rs */
if let Some(filter_invocation) = settings.html_filter.as_ref() { if let Some(filter_invocation) = mailbox_settings!(
context[self.coordinates.0][&self.coordinates.1]
.pager
.html_filter
)
.as_ref()
{
let parts = split_command!(filter_invocation); let parts = split_command!(filter_invocation);
let (cmd, args) = (parts[0], &parts[1..]); let (cmd, args) = (parts[0], &parts[1..]);
let command_obj = Command::new(cmd) let command_obj = Command::new(cmd)
@ -400,10 +405,11 @@ impl Component for MailView {
self.headers_no = 0; self.headers_no = 0;
let mut skip_header_ctr = self.headers_cursor; let mut skip_header_ctr = self.headers_cursor;
let sticky = let sticky = *mailbox_settings!(
mailbox_settings!(context[self.coordinates.0][&self.coordinates.1].pager) context[self.coordinates.0][&self.coordinates.1]
.pager
.headers_sticky .headers_sticky
|| height_p < height; ) || height_p < height;
let (_, mut y) = upper_left; let (_, mut y) = upper_left;
macro_rules! print_header { macro_rules! print_header {
($($string:expr)+) => { ($($string:expr)+) => {
@ -573,9 +579,11 @@ impl Component for MailView {
context context
.dirty_areas .dirty_areas
.push_back((upper_left, set_y(bottom_right, y + 3))); .push_back((upper_left, set_y(bottom_right, y + 3)));
if !mailbox_settings!(context[self.coordinates.0][&self.coordinates.1].pager) if !*mailbox_settings!(
.headers_sticky context[self.coordinates.0][&self.coordinates.1]
{ .pager
.headers_sticky
) {
let height_p = self.pager.size().1; let height_p = self.pager.size().1;
let height = height!(area).saturating_sub(y).saturating_sub(1); let height = height!(area).saturating_sub(y).saturating_sub(1);
@ -644,9 +652,10 @@ impl Component for MailView {
} }
ViewMode::Normal ViewMode::Normal
if mailbox_settings!( if mailbox_settings!(
context[self.coordinates.0][&self.coordinates.1].pager context[self.coordinates.0][&self.coordinates.1]
.pager
.auto_choose_multipart_alternative
) )
.auto_choose_multipart_alternative
.is_true() .is_true()
&& match body.content_type { && match body.content_type {
ContentType::Multipart { ContentType::Multipart {
@ -813,10 +822,11 @@ impl Component for MailView {
_ => match event { _ => match event {
UIEvent::Input(ref key) UIEvent::Input(ref key)
if shortcut!(key == shortcuts[Pager::DESCRIPTION]["scroll_up"]) if shortcut!(key == shortcuts[Pager::DESCRIPTION]["scroll_up"])
&& !mailbox_settings!( && !*mailbox_settings!(
context[self.coordinates.0][&self.coordinates.1].pager context[self.coordinates.0][&self.coordinates.1]
.pager
.headers_sticky
) )
.headers_sticky
&& self.headers_cursor <= self.headers_no => && self.headers_cursor <= self.headers_no =>
{ {
self.force_draw_headers = true; self.force_draw_headers = true;
@ -832,10 +842,11 @@ impl Component for MailView {
} }
UIEvent::Input(ref key) UIEvent::Input(ref key)
if shortcut!(key == shortcuts[Pager::DESCRIPTION]["scroll_down"]) if shortcut!(key == shortcuts[Pager::DESCRIPTION]["scroll_down"])
&& !mailbox_settings!( && !*mailbox_settings!(
context[self.coordinates.0][&self.coordinates.1].pager context[self.coordinates.0][&self.coordinates.1]
.pager
.headers_sticky
) )
.headers_sticky
&& self.headers_cursor < self.headers_no => && self.headers_cursor < self.headers_no =>
{ {
self.force_draw_headers = true; self.force_draw_headers = true;

View File

@ -46,10 +46,10 @@ pub use self::shortcuts::*;
pub use self::tags::*; pub use self::tags::*;
use self::default_vals::*; use self::default_vals::*;
use self::listing::ListingSettings; use self::listing::{ListingSettings, ListingSettingsOverride};
use self::notifications::NotificationsSettings; use self::notifications::{NotificationsSettings, NotificationsSettingsOverride};
use self::terminal::TerminalSettings; use self::terminal::TerminalSettings;
use crate::pager::PagerSettings; use crate::pager::{PagerSettings, PagerSettingsOverride};
use crate::plugins::Plugin; use crate::plugins::Plugin;
use melib::conf::{AccountSettings, MailboxConf, ToggleFlag}; use melib::conf::{AccountSettings, MailboxConf, ToggleFlag};
use melib::error::*; use melib::error::*;
@ -72,38 +72,87 @@ macro_rules! split_command {
#[macro_export] #[macro_export]
macro_rules! mailbox_acc_settings { macro_rules! mailbox_acc_settings {
($context:ident[$account_idx:expr][$mailbox_path:expr].$field:ident) => {{ ($context:ident[$account_idx:expr].$setting:ident.$field:ident) => {{
$context.accounts[$account_idx][$mailbox_path] $context.accounts[$account_idx]
.conf .settings
.conf_override .conf_override
.$setting
.$field .$field
.as_ref() .as_ref()
.unwrap_or(&$context.accounts[$account_idx].settings.conf.$field) .unwrap_or(&$context.settings.$setting.$field)
}}; }};
} }
#[macro_export] #[macro_export]
macro_rules! mailbox_settings { macro_rules! mailbox_settings {
($context:ident[$account_idx:expr][$mailbox_path:expr].$field:ident) => {{ ($context:ident[$account_idx:expr][$mailbox_path:expr].$setting:ident.$field:ident) => {{
$context.accounts[$account_idx][$mailbox_path] $context.accounts[$account_idx][$mailbox_path]
.conf .conf
.conf_override .conf_override
.$setting
.$field .$field
.as_ref() .as_ref()
.unwrap_or(&$context.settings.$field) .or($context.accounts[$account_idx]
.settings
.conf_override
.$setting
.$field
.as_ref())
.unwrap_or(&$context.settings.$setting.$field)
}}; }};
} }
#[macro_export]
macro_rules! override_def {
($override_name:ident,
$(#[$outer:meta])*
pub struct $name:ident { $( $(#[$fouter:meta])* $fname:ident : $ft:ty),*,
}) => {
$(#[$outer])*
pub struct $name {
$(
$(#[$fouter])*
pub $fname : $ft
),*
}
$(#[$outer])*
pub struct $override_name {
$(
$(#[$fouter])*
pub $fname : Option<$ft>
),*
}
impl Default for $override_name {
fn default() -> Self {
$override_name {
$(
$fname : None
),*
}
}
}
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)] #[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct MailUIConf { pub struct MailUIConf {
pub pager: Option<PagerSettings>, #[serde(default)]
pub listing: Option<ListingSettings>, pub pager: PagerSettingsOverride,
pub notifications: Option<NotificationsSettings>, #[serde(default)]
pub shortcuts: Option<Shortcuts>, pub listing: ListingSettingsOverride,
pub composing: Option<ComposingSettings>, #[serde(default)]
pub notifications: NotificationsSettingsOverride,
#[serde(default)]
pub shortcuts: ShortcutsOverride,
#[serde(default)]
pub composing: ComposingSettingsOverride,
#[serde(default)]
pub identity: Option<String>, pub identity: Option<String>,
pub index_style: Option<IndexStyle>, #[serde(default)]
pub tags: Option<TagsSettings>, pub tags: TagsSettingsOverride,
#[serde(default)]
pub theme: Option<Theme>, pub theme: Option<Theme>,
#[serde(default)]
pub pgp: PGPSettingsOverride,
} }
#[serde(default)] #[serde(default)]
@ -133,7 +182,6 @@ pub struct FileAccount {
identity: String, identity: String,
#[serde(default = "none")] #[serde(default = "none")]
display_name: Option<String>, display_name: Option<String>,
pub index_style: IndexStyle,
#[serde(default = "false_val")] #[serde(default = "false_val")]
read_only: bool, read_only: bool,
@ -148,6 +196,8 @@ pub struct FileAccount {
#[serde(default = "none")] #[serde(default = "none")]
pub refresh_command: Option<String>, pub refresh_command: Option<String>,
#[serde(flatten)] #[serde(flatten)]
pub conf_override: MailUIConf,
#[serde(flatten)]
#[serde(deserialize_with = "extra_settings")] #[serde(deserialize_with = "extra_settings")]
pub extra: HashMap<String, String>, /* use custom deserializer to convert any given value (eg bool, number, etc) to string */ pub extra: HashMap<String, String>, /* use custom deserializer to convert any given value (eg bool, number, etc) to string */
} }
@ -180,6 +230,7 @@ impl From<FileAccount> for AccountConf {
let mailbox_confs = x.mailboxes.clone(); let mailbox_confs = x.mailboxes.clone();
AccountConf { AccountConf {
account: acc, account: acc,
conf_override: x.conf_override.clone(),
conf: x, conf: x,
mailbox_confs, mailbox_confs,
} }
@ -195,10 +246,6 @@ impl FileAccount {
&self.root_mailbox &self.root_mailbox
} }
pub fn index_style(&self) -> IndexStyle {
self.index_style
}
pub fn cache_type(&self) -> &CacheType { pub fn cache_type(&self) -> &CacheType {
&self.cache_type &self.cache_type
} }
@ -230,6 +277,7 @@ pub struct FileSettings {
pub struct AccountConf { pub struct AccountConf {
pub(crate) account: AccountSettings, pub(crate) account: AccountSettings,
pub(crate) conf: FileAccount, pub(crate) conf: FileAccount,
pub conf_override: MailUIConf,
pub(crate) mailbox_confs: HashMap<String, FileMailboxConf>, pub(crate) mailbox_confs: HashMap<String, FileMailboxConf>,
} }
@ -237,6 +285,9 @@ impl AccountConf {
pub fn account(&self) -> &AccountSettings { pub fn account(&self) -> &AccountSettings {
&self.account &self.account
} }
pub fn account_mut(&mut self) -> &mut AccountSettings {
&mut self.account
}
pub fn conf(&self) -> &FileAccount { pub fn conf(&self) -> &FileAccount {
&self.conf &self.conf
} }
@ -375,8 +426,8 @@ impl FileSettings {
extra, extra,
manual_refresh, manual_refresh,
refresh_command: _, refresh_command: _,
index_style: _,
cache_type: _, cache_type: _,
conf_override: _,
} = acc.clone(); } = acc.clone();
let lowercase_format = format.to_lowercase(); let lowercase_format = format.to_lowercase();
@ -448,48 +499,48 @@ impl Default for IndexStyle {
*/ */
mod default_vals { mod default_vals {
pub(in crate::conf) fn false_val() -> bool { pub(in crate::conf) fn false_val<T: std::convert::From<bool>>() -> T {
false false.into()
} }
pub(in crate::conf) fn true_val() -> bool { pub(in crate::conf) fn true_val<T: std::convert::From<bool>>() -> T {
true true.into()
} }
pub(in crate::conf) fn zero_val() -> usize { pub(in crate::conf) fn zero_val<T: std::convert::From<usize>>() -> T {
0 0.into()
} }
pub(in crate::conf) fn eighty_percent() -> usize { pub(in crate::conf) fn eighty_val<T: std::convert::From<usize>>() -> T {
80 80.into()
} }
pub(in crate::conf) fn none<T>() -> Option<T> { pub(in crate::conf) fn none<T>() -> Option<T> {
None None
} }
pub(in crate::conf) fn internal_value_false() -> super::ToggleFlag { pub(in crate::conf) fn internal_value_false<T: std::convert::From<super::ToggleFlag>>() -> T {
super::ToggleFlag::InternalVal(false) super::ToggleFlag::InternalVal(false).into()
} }
pub(in crate::conf) fn internal_value_true() -> super::ToggleFlag { pub(in crate::conf) fn internal_value_true<T: std::convert::From<super::ToggleFlag>>() -> T {
super::ToggleFlag::InternalVal(true) super::ToggleFlag::InternalVal(true).into()
} }
} }
mod deserializers { mod deserializers {
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
pub(in crate::conf) fn non_empty_string<'de, D>( pub(in crate::conf) fn non_empty_string<'de, D, T: std::convert::From<Option<String>>>(
deserializer: D, deserializer: D,
) -> std::result::Result<Option<String>, D::Error> ) -> std::result::Result<T, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
let s = <String>::deserialize(deserializer)?; let s = <String>::deserialize(deserializer)?;
if s.is_empty() { if s.is_empty() {
Ok(None) Ok(None.into())
} else { } else {
Ok(Some(s)) Ok(Some(s).into())
} }
} }

View File

@ -122,7 +122,6 @@ pub struct Account {
pub(crate) address_book: AddressBook, pub(crate) address_book: AddressBook,
pub(crate) work_context: WorkContext, pub(crate) work_context: WorkContext,
pub(crate) settings: AccountConf, pub(crate) settings: AccountConf,
pub(crate) runtime_settings: AccountConf,
pub(crate) backend: Arc<RwLock<Box<dyn MailBackend>>>, pub(crate) backend: Arc<RwLock<Box<dyn MailBackend>>>,
sender: Sender<ThreadEvent>, sender: Sender<ThreadEvent>,
@ -242,7 +241,6 @@ impl Account {
sent_mailbox: Default::default(), sent_mailbox: Default::default(),
collection: Default::default(), collection: Default::default(),
work_context, work_context,
runtime_settings: settings.clone(),
settings, settings,
backend: Arc::new(RwLock::new(backend)), backend: Arc::new(RwLock::new(backend)),
notify_fn, notify_fn,

View File

@ -21,29 +21,34 @@
//! Configuration for composing email. //! Configuration for composing email.
use super::default_vals::{false_val, none, true_val}; use super::default_vals::{false_val, none, true_val};
use crate::override_def;
use std::collections::HashMap; use std::collections::HashMap;
/// Settings for writing and sending new e-mail override_def!(
#[derive(Debug, Serialize, Deserialize, Clone)] ComposingSettingsOverride,
pub struct ComposingSettings { /// Settings for writing and sending new e-mail
/// A command to pipe new emails to #[derive(Debug, Serialize, Deserialize, Clone)]
/// Required pub struct ComposingSettings {
pub mailer_cmd: String, /// A command to pipe new emails to
/// Command to launch editor. Can have arguments. Draft filename is given as the last argument. If it's missing, the environment variable $EDITOR is looked up. /// Required
#[serde(default = "none")] #[serde(alias = "mailer-cmd")]
pub editor_cmd: Option<String>, mailer_cmd: String,
/// Embed editor (for terminal interfaces) instead of forking and waiting. /// Command to launch editor. Can have arguments. Draft filename is given as the last argument. If it's missing, the environment variable $EDITOR is looked up.
#[serde(default = "false_val")] #[serde(default = "none", alias = "editor-cmd")]
pub embed: bool, editor_cmd: Option<String>,
/// Set "format=flowed" in plain text attachments. /// Embed editor (for terminal interfaces) instead of forking and waiting.
/// Default: true #[serde(default = "false_val")]
#[serde(default = "true_val")] embed: bool,
pub format_flowed: bool, /// Set "format=flowed" in plain text attachments.
/// Set default header values for new drafts /// Default: true
/// Default: empty #[serde(default = "true_val", alias = "format-flowed")]
#[serde(default)] format_flowed: bool,
pub default_header_values: HashMap<String, String>, /// Set default header values for new drafts
} /// Default: empty
#[serde(default, alias = "default-header-values")]
default_header_values: HashMap<String, String>,
}
);
impl Default for ComposingSettings { impl Default for ComposingSettings {
fn default() -> Self { fn default() -> Self {

View File

@ -19,29 +19,48 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::default_vals::*; use super::{default_vals::*, IndexStyle};
use crate::cache::Query; use crate::cache::Query;
use crate::override_def;
/// Settings for mail listings override_def!(
#[derive(Debug, Deserialize, Clone, Default, Serialize)] ListingSettingsOverride,
pub struct ListingSettings { /// Settings for mail listings
/// Number of context lines when going to next page. #[derive(Debug, Deserialize, Clone, Serialize)]
/// Default: 0 pub struct ListingSettings {
#[serde(default = "zero_val")] /// Number of context lines when going to next page.
pub context_lines: usize, /// Default: 0
#[serde(default = "zero_val", alias = "context-lines")]
context_lines: usize,
/// Datetime formatting passed verbatim to strftime(3). /// Datetime formatting passed verbatim to strftime(3).
/// Default: %Y-%m-%d %T /// Default: %Y-%m-%d %T
#[serde(default = "none")] #[serde(default = "none", alias = "datetime-fmt")]
pub datetime_fmt: Option<String>, datetime_fmt: Option<String>,
/// Show recent dates as `X {minutes,hours,days} ago`, up to 7 days. /// Show recent dates as `X {minutes,hours,days} ago`, up to 7 days.
/// Default: true /// Default: true
#[serde(default = "true_val")] #[serde(default = "true_val", alias = "recent-dates")]
pub recent_dates: bool, recent_dates: bool,
/// Show only envelopes that match this query /// Show only envelopes that match this query
/// Default: None /// Default: None
#[serde(default = "none")] #[serde(default = "none")]
pub filter: Option<Query>, filter: Option<Query>,
#[serde(default, alias = "index-style")]
index_style: IndexStyle,
}
);
impl Default for ListingSettings {
fn default() -> Self {
Self {
context_lines: 0,
datetime_fmt: None,
recent_dates: true,
filter: None,
index_style: IndexStyle::default(),
}
}
} }

View File

@ -19,26 +19,37 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::default_vals::internal_value_false; use super::default_vals::{internal_value_false, none};
use crate::override_def;
fn none() -> Option<String> { override_def!(
None NotificationsSettingsOverride,
} /// Settings for the notifications function.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NotificationsSettings {
/// A command to pipe notifications through
/// Default: None
#[serde(default = "none")]
script: Option<String>,
/// A file location which has its size changed when new mail arrives (max 128 bytes). Can be
/// used to trigger new mail notifications eg with `xbiff(1)`
/// Default: None
#[serde(default = "none", alias = "xbiff-file-path")]
xbiff_file_path: Option<String>,
#[serde(default = "internal_value_false", alias = "play-sound")]
play_sound: super::ToggleFlag,
#[serde(default = "none", alias = "sound-file")]
sound_file: Option<String>,
}
);
/// Settings for the notifications function. impl Default for NotificationsSettings {
#[derive(Debug, Serialize, Deserialize, Clone, Default)] fn default() -> Self {
pub struct NotificationsSettings { Self {
/// A command to pipe notifications through script: None,
/// Default: None xbiff_file_path: None,
#[serde(default = "none")] play_sound: super::ToggleFlag::InternalVal(false),
pub script: Option<String>, sound_file: None,
/// A file location which has its size changed when new mail arrives (max 128 bytes). Can be }
/// used to trigger new mail notifications eg with `xbiff(1)` }
/// Default: None
#[serde(default = "none")]
pub xbiff_file_path: Option<String>,
#[serde(default = "internal_value_false")]
pub play_sound: super::ToggleFlag,
#[serde(default = "none")]
pub sound_file: Option<String>,
} }

View File

@ -23,63 +23,87 @@
use super::default_vals::*; use super::default_vals::*;
use super::deserializers::*; use super::deserializers::*;
use crate::override_def;
use melib::ToggleFlag; use melib::ToggleFlag;
/// Settings for the pager function. override_def!(
#[derive(Debug, Deserialize, Clone, Default, Serialize)] PagerSettingsOverride,
pub struct PagerSettings { /// Settings for the pager function.
/// Number of context lines when going to next page. #[derive(Debug, Deserialize, Clone, Serialize)]
/// Default: 0 pub struct PagerSettings {
#[serde(default = "zero_val")] /// Number of context lines when going to next page.
pub pager_context: usize, /// Default: 0
#[serde(default = "zero_val", alias = "pager-context")]
pager_context: usize,
/// Stop at the end instead of displaying next mail. /// Stop at the end instead of displaying next mail.
/// Default: false /// Default: false
#[serde(default = "false_val")] #[serde(default = "false_val", alias = "pager-stop")]
pub pager_stop: bool, pager_stop: bool,
/// Always show headers when scrolling. /// Always show headers when scrolling.
/// Default: true /// Default: true
#[serde(default = "true_val")] #[serde(default = "true_val", alias = "headers-sticky")]
pub headers_sticky: bool, headers_sticky: bool,
/// The height of the pager in mail view, in percent. /// The height of the pager in mail view, in percent.
/// Default: 80 /// Default: 80
#[serde(default = "eighty_percent")] #[serde(default = "eighty_val", alias = "pager-ratio")]
pub pager_ratio: usize, pager_ratio: usize,
/// A command to pipe mail output through for viewing in pager. /// A command to pipe mail output through for viewing in pager.
/// Default: None /// Default: None
#[serde(default = "none", deserialize_with = "non_empty_string")] #[serde(default = "none", deserialize_with = "non_empty_string")]
pub filter: Option<String>, filter: Option<String>,
/// A command to pipe html output before displaying it in a pager /// A command to pipe html output before displaying it in a pager
/// Default: None /// Default: None
#[serde(default = "none", deserialize_with = "non_empty_string")] #[serde(
pub html_filter: Option<String>, default = "none",
deserialize_with = "non_empty_string",
alias = "html-filter"
)]
html_filter: Option<String>,
/// Respect "format=flowed" /// Respect "format=flowed"
/// Default: true /// Default: true
#[serde(default = "true_val")] #[serde(default = "true_val", alias = "format-flowed")]
pub format_flowed: bool, format_flowed: bool,
/// Split long lines that would overflow on the x axis. /// Split long lines that would overflow on the x axis.
/// Default: true /// Default: true
#[serde(default = "true_val")] #[serde(default = "true_val", alias = "split-long-lines")]
pub split_long_lines: bool, split_long_lines: bool,
/// Minimum text width in columns. /// Minimum text width in columns.
/// Default: 80 /// Default: 80
#[serde(default = "eighty_val")] #[serde(default = "eighty_val", alias = "minimum-width")]
pub minimum_width: usize, minimum_width: usize,
/// Choose `text/html` alternative if `text/plain` is empty in `multipart/alternative` /// Choose `text/html` alternative if `text/plain` is empty in `multipart/alternative`
/// attachments. /// attachments.
/// Default: true /// Default: true
#[serde(default = "internal_value_true")] #[serde(
pub auto_choose_multipart_alternative: ToggleFlag, default = "internal_value_true",
} alias = "auto-choose-multipart-alternative"
)]
fn eighty_val() -> usize { auto_choose_multipart_alternative: ToggleFlag,
80 }
);
impl Default for PagerSettings {
fn default() -> Self {
Self {
pager_context: 0,
pager_stop: false,
headers_sticky: true,
pager_ratio: 80,
filter: None,
html_filter: None,
format_flowed: true,
split_long_lines: true,
minimum_width: 80,
auto_choose_multipart_alternative: ToggleFlag::InternalVal(true),
}
}
} }

View File

@ -20,26 +20,30 @@
*/ */
use super::default_vals::*; use super::default_vals::*;
use crate::override_def;
/// Settings for digital signing and encryption override_def!(
#[derive(Debug, Deserialize, Clone, Serialize)] PGPSettingsOverride,
pub struct PGPSettings { /// Settings for digital signing and encryption
/// auto verify signed e-mail according to RFC3156 #[derive(Debug, Deserialize, Clone, Serialize)]
#[serde(default = "true_val")] pub struct PGPSettings {
pub auto_verify_signatures: bool, /// auto verify signed e-mail according to RFC3156
#[serde(default = "true_val", alias = "auto-verify-signatures")]
auto_verify_signatures: bool,
/// always sign sent messages /// always sign sent messages
#[serde(default = "false_val")] #[serde(default = "false_val", alias = "auto-sign")]
pub auto_sign: bool, auto_sign: bool,
// https://tools.ietf.org/html/rfc4880#section-12.2 // https://tools.ietf.org/html/rfc4880#section-12.2
#[serde(default = "none")] #[serde(default = "none")]
pub key: Option<String>, key: Option<String>,
/// gpg binary name or file location to use /// gpg binary name or file location to use
#[serde(default)] #[serde(default, alias = "gpg-binary")]
pub gpg_binary: Option<String>, gpg_binary: Option<String>,
} }
);
impl Default for PGPSettings { impl Default for PGPSettings {
fn default() -> Self { fn default() -> Self {

View File

@ -19,6 +19,7 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use crate::override_def;
use crate::terminal::Key; use crate::terminal::Key;
use fnv::FnvHashMap; use fnv::FnvHashMap;
@ -32,24 +33,42 @@ macro_rules! shortcut {
}; };
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize)] override_def!(
pub struct Shortcuts { ShortcutsOverride,
#[serde(default)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub general: GeneralShortcuts, pub struct Shortcuts {
#[serde(default)] #[serde(default)]
pub listing: ListingShortcuts, general: GeneralShortcuts,
#[serde(default)] #[serde(default)]
pub composing: ComposingShortcuts, listing: ListingShortcuts,
#[serde(default, alias = "compact-listing")] #[serde(default)]
pub compact_listing: CompactListingShortcuts, composing: ComposingShortcuts,
#[serde(default, alias = "contact-list")] #[serde(default, alias = "compact-listing")]
pub contact_list: ContactListShortcuts, compact_listing: CompactListingShortcuts,
#[serde(default, alias = "envelope-view")] #[serde(default, alias = "contact-list")]
pub envelope_view: EnvelopeViewShortcuts, contact_list: ContactListShortcuts,
#[serde(default, alias = "thread-view")] #[serde(default, alias = "envelope-view")]
pub thread_view: ThreadViewShortcuts, envelope_view: EnvelopeViewShortcuts,
#[serde(default)] #[serde(default, alias = "thread-view")]
pub pager: PagerShortcuts, thread_view: ThreadViewShortcuts,
#[serde(default)]
pager: PagerShortcuts,
}
);
impl Default for Shortcuts {
fn default() -> Self {
Self {
general: GeneralShortcuts::default(),
listing: ListingShortcuts::default(),
composing: ComposingShortcuts::default(),
compact_listing: CompactListingShortcuts::default(),
contact_list: ContactListShortcuts::default(),
envelope_view: EnvelopeViewShortcuts::default(),
thread_view: ThreadViewShortcuts::default(),
pager: PagerShortcuts::default(),
}
}
} }
/// Create a struct holding all of a Component's shortcuts. /// Create a struct holding all of a Component's shortcuts.

View File

@ -21,18 +21,22 @@
//! E-mail tag configuration and {de,}serializing. //! E-mail tag configuration and {de,}serializing.
use crate::override_def;
use crate::terminal::Color; use crate::terminal::Color;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use std::collections::{hash_map::DefaultHasher, HashMap, HashSet}; use std::collections::{hash_map::DefaultHasher, HashMap, HashSet};
use std::hash::Hasher; use std::hash::Hasher;
#[derive(Debug, Deserialize, Clone, Serialize)] override_def!(
pub struct TagsSettings { TagsSettingsOverride,
#[serde(default, deserialize_with = "tag_color_de")] #[derive(Debug, Deserialize, Clone, Serialize)]
pub colors: HashMap<u64, Color>, pub struct TagsSettings {
#[serde(default, deserialize_with = "tag_set_de")] #[serde(default, deserialize_with = "tag_color_de")]
pub ignore_tags: HashSet<u64>, colors: HashMap<u64, Color>,
} #[serde(default, deserialize_with = "tag_set_de", alias = "ignore-tags")]
ignore_tags: HashSet<u64>,
}
);
impl Default for TagsSettings { impl Default for TagsSettings {
fn default() -> Self { fn default() -> Self {
@ -43,7 +47,9 @@ impl Default for TagsSettings {
} }
} }
pub fn tag_set_de<'de, D>(deserializer: D) -> std::result::Result<HashSet<u64>, D::Error> pub fn tag_set_de<'de, D, T: std::convert::From<HashSet<u64>>>(
deserializer: D,
) -> std::result::Result<T, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
@ -54,10 +60,13 @@ where
hasher.write(tag.as_bytes()); hasher.write(tag.as_bytes());
hasher.finish() hasher.finish()
}) })
.collect()) .collect::<HashSet<u64>>()
.into())
} }
pub fn tag_color_de<'de, D>(deserializer: D) -> std::result::Result<HashMap<u64, Color>, D::Error> pub fn tag_color_de<'de, D, T: std::convert::From<HashMap<u64, Color>>>(
deserializer: D,
) -> std::result::Result<T, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
@ -81,5 +90,6 @@ where
}, },
) )
}) })
.collect()) .collect::<HashMap<u64, Color>>()
.into())
} }