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.
async
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::*;
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() {
format!("{} <{}>", d, settings.identity)
} else {

View File

@ -147,7 +147,9 @@ impl Composer {
id: ComponentId::new_v4(),
..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() {
continue;
}
@ -298,12 +300,9 @@ impl Composer {
write_string_to_grid(
&format!(
"☑ sign with {}",
context
.settings
.pgp
.key
mailbox_acc_settings!(context[self.account_cursor].pgp.key)
.as_ref()
.map(String::as_str)
.map(|s| s.as_str())
.unwrap_or("default key")
),
grid,
@ -392,7 +391,9 @@ impl Component for Composer {
if !self.initialized {
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()
{
@ -859,8 +860,10 @@ impl Component for Composer {
&& shortcut!(key == shortcuts[Self::DESCRIPTION]["edit_mail"]) =>
{
/* Edit draft in $EDITOR */
let settings = &context.settings;
let editor = if let Some(editor_cmd) = settings.composing.editor_cmd.as_ref() {
let editor = if let Some(editor_cmd) =
mailbox_acc_settings!(context[self.account_cursor].composing.editor_cmd)
.as_ref()
{
editor_cmd.to_string()
} else {
match std::env::var("EDITOR") {
@ -884,7 +887,7 @@ impl Component for Composer {
true,
);
if settings.composing.embed {
if *mailbox_acc_settings!(context[self.account_cursor].composing.embed) {
self.embed = Some(EmbedStatus::Running(
crate::terminal::embed::create_pty(
width!(self.embed_area),
@ -1124,7 +1127,8 @@ impl Component for Composer {
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
@ -1178,9 +1182,10 @@ pub fn send_draft(
) -> bool {
use std::io::Write;
use std::process::{Command, Stdio};
let settings = &context.settings;
let format_flowed = settings.composing.format_flowed;
let parts = split_command!(settings.composing.mailer_cmd);
let format_flowed = *mailbox_acc_settings!(context[account_cursor].composing.format_flowed);
let parts = split_command!(mailbox_acc_settings!(
context[account_cursor].composing.mailer_cmd
));
if parts.is_empty() {
context.replies.push_back(UIEvent::Notification(
None,
@ -1233,8 +1238,12 @@ pub fn send_draft(
}
let output = crate::components::mail::pgp::sign(
body.into(),
context.settings.pgp.gpg_binary.as_ref().map(String::as_str),
context.settings.pgp.key.as_ref().map(String::as_str),
mailbox_acc_settings!(context[account_cursor].pgp.gpg_binary)
.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 {
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 */
/* Check if per-mailbox configuration overrides general configuration */
let index_style =
mailbox_acc_settings!(context[self.cursor_pos.0][mailbox_hash].index_style);
let index_style = mailbox_settings!(
context[self.cursor_pos.0][mailbox_hash].listing.index_style
);
self.component.set_style(*index_style);
}
context
@ -1200,7 +1201,7 @@ impl Listing {
/* Check if per-mailbox configuration overrides general configuration */
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);
} else {
/* Set to dummy */

View File

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

View File

@ -593,20 +593,28 @@ impl ConversationsListing {
hash: ThreadHash,
) -> EntryStrings {
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 colors = SmallVec::new();
let backend_lck = context.accounts[self.cursor_pos.0].backend.read().unwrap();
if let Some(t) = backend_lck.tags() {
let tags_lck = t.read().unwrap();
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;
}
tags.push(' ');
tags.push_str(tags_lck.get(t).as_ref().unwrap());
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);
} else {
colors.push(Color::Byte(8));
@ -698,10 +706,12 @@ impl ConversationsListing {
.collection
.get_env(root_env_hash);
use crate::cache::QueryTrait;
if let Some(filter_query) =
mailbox_settings!(context[self.cursor_pos.0][&self.cursor_pos.1].listing)
if let Some(filter_query) = mailbox_settings!(
context[self.cursor_pos.0][&self.cursor_pos.1]
.listing
.filter
.as_ref()
)
.as_ref()
{
if !root_envelope.is_match(filter_query) {
continue;

View File

@ -612,21 +612,30 @@ impl PlainListing {
id: ComponentId::new_v4(),
}
}
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 colors = SmallVec::new();
let backend_lck = context.accounts[self.cursor_pos.0].backend.read().unwrap();
if let Some(t) = backend_lck.tags() {
let tags_lck = t.read().unwrap();
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;
}
tags.push(' ');
tags.push_str(tags_lck.get(t).as_ref().unwrap());
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);
} else {
colors.push(Color::Byte(8));
@ -705,10 +714,12 @@ impl PlainListing {
}
let envelope: EnvelopeRef = context.accounts[self.cursor_pos.0].collection.get_env(i);
use crate::cache::QueryTrait;
if let Some(filter_query) =
mailbox_settings!(context[self.cursor_pos.0][&self.cursor_pos.1].listing)
if let Some(filter_query) = mailbox_settings!(
context[self.cursor_pos.0][&self.cursor_pos.1]
.listing
.filter
.as_ref()
)
.as_ref()
{
if !envelope.is_match(filter_query) {
continue;

View File

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

View File

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

View File

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

View File

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

View File

@ -21,29 +21,34 @@
//! Configuration for composing email.
use super::default_vals::{false_val, none, true_val};
use crate::override_def;
use std::collections::HashMap;
/// Settings for writing and sending new e-mail
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ComposingSettings {
/// A command to pipe new emails to
/// Required
pub mailer_cmd: String,
/// 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 = "none")]
pub editor_cmd: Option<String>,
/// Embed editor (for terminal interfaces) instead of forking and waiting.
#[serde(default = "false_val")]
pub embed: bool,
/// Set "format=flowed" in plain text attachments.
/// Default: true
#[serde(default = "true_val")]
pub format_flowed: bool,
/// Set default header values for new drafts
/// Default: empty
#[serde(default)]
pub default_header_values: HashMap<String, String>,
}
override_def!(
ComposingSettingsOverride,
/// Settings for writing and sending new e-mail
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ComposingSettings {
/// A command to pipe new emails to
/// Required
#[serde(alias = "mailer-cmd")]
mailer_cmd: String,
/// 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 = "none", alias = "editor-cmd")]
editor_cmd: Option<String>,
/// Embed editor (for terminal interfaces) instead of forking and waiting.
#[serde(default = "false_val")]
embed: bool,
/// Set "format=flowed" in plain text attachments.
/// Default: true
#[serde(default = "true_val", alias = "format-flowed")]
format_flowed: bool,
/// 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 {
fn default() -> Self {

View File

@ -19,29 +19,48 @@
* 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::override_def;
/// Settings for mail listings
#[derive(Debug, Deserialize, Clone, Default, Serialize)]
pub struct ListingSettings {
/// Number of context lines when going to next page.
/// Default: 0
#[serde(default = "zero_val")]
pub context_lines: usize,
override_def!(
ListingSettingsOverride,
/// Settings for mail listings
#[derive(Debug, Deserialize, Clone, Serialize)]
pub struct ListingSettings {
/// Number of context lines when going to next page.
/// Default: 0
#[serde(default = "zero_val", alias = "context-lines")]
context_lines: usize,
/// Datetime formatting passed verbatim to strftime(3).
/// Default: %Y-%m-%d %T
#[serde(default = "none")]
pub datetime_fmt: Option<String>,
/// Datetime formatting passed verbatim to strftime(3).
/// Default: %Y-%m-%d %T
#[serde(default = "none", alias = "datetime-fmt")]
datetime_fmt: Option<String>,
/// Show recent dates as `X {minutes,hours,days} ago`, up to 7 days.
/// Default: true
#[serde(default = "true_val")]
pub recent_dates: bool,
/// Show recent dates as `X {minutes,hours,days} ago`, up to 7 days.
/// Default: true
#[serde(default = "true_val", alias = "recent-dates")]
recent_dates: bool,
/// Show only envelopes that match this query
/// Default: None
#[serde(default = "none")]
pub filter: Option<Query>,
/// Show only envelopes that match this query
/// Default: None
#[serde(default = "none")]
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/>.
*/
use super::default_vals::internal_value_false;
use super::default_vals::{internal_value_false, none};
use crate::override_def;
fn none() -> Option<String> {
None
}
override_def!(
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.
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct NotificationsSettings {
/// A command to pipe notifications through
/// Default: None
#[serde(default = "none")]
pub 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")]
pub xbiff_file_path: Option<String>,
#[serde(default = "internal_value_false")]
pub play_sound: super::ToggleFlag,
#[serde(default = "none")]
pub sound_file: Option<String>,
impl Default for NotificationsSettings {
fn default() -> Self {
Self {
script: None,
xbiff_file_path: None,
play_sound: super::ToggleFlag::InternalVal(false),
sound_file: None,
}
}
}

View File

@ -23,63 +23,87 @@
use super::default_vals::*;
use super::deserializers::*;
use crate::override_def;
use melib::ToggleFlag;
/// Settings for the pager function.
#[derive(Debug, Deserialize, Clone, Default, Serialize)]
pub struct PagerSettings {
/// Number of context lines when going to next page.
/// Default: 0
#[serde(default = "zero_val")]
pub pager_context: usize,
override_def!(
PagerSettingsOverride,
/// Settings for the pager function.
#[derive(Debug, Deserialize, Clone, Serialize)]
pub struct PagerSettings {
/// Number of context lines when going to next page.
/// Default: 0
#[serde(default = "zero_val", alias = "pager-context")]
pager_context: usize,
/// Stop at the end instead of displaying next mail.
/// Default: false
#[serde(default = "false_val")]
pub pager_stop: bool,
/// Stop at the end instead of displaying next mail.
/// Default: false
#[serde(default = "false_val", alias = "pager-stop")]
pager_stop: bool,
/// Always show headers when scrolling.
/// Default: true
#[serde(default = "true_val")]
pub headers_sticky: bool,
/// Always show headers when scrolling.
/// Default: true
#[serde(default = "true_val", alias = "headers-sticky")]
headers_sticky: bool,
/// The height of the pager in mail view, in percent.
/// Default: 80
#[serde(default = "eighty_percent")]
pub pager_ratio: usize,
/// The height of the pager in mail view, in percent.
/// Default: 80
#[serde(default = "eighty_val", alias = "pager-ratio")]
pager_ratio: usize,
/// A command to pipe mail output through for viewing in pager.
/// Default: None
#[serde(default = "none", deserialize_with = "non_empty_string")]
pub filter: Option<String>,
/// A command to pipe mail output through for viewing in pager.
/// Default: None
#[serde(default = "none", deserialize_with = "non_empty_string")]
filter: Option<String>,
/// A command to pipe html output before displaying it in a pager
/// Default: None
#[serde(default = "none", deserialize_with = "non_empty_string")]
pub html_filter: Option<String>,
/// A command to pipe html output before displaying it in a pager
/// Default: None
#[serde(
default = "none",
deserialize_with = "non_empty_string",
alias = "html-filter"
)]
html_filter: Option<String>,
/// Respect "format=flowed"
/// Default: true
#[serde(default = "true_val")]
pub format_flowed: bool,
/// Respect "format=flowed"
/// Default: true
#[serde(default = "true_val", alias = "format-flowed")]
format_flowed: bool,
/// Split long lines that would overflow on the x axis.
/// Default: true
#[serde(default = "true_val")]
pub split_long_lines: bool,
/// Split long lines that would overflow on the x axis.
/// Default: true
#[serde(default = "true_val", alias = "split-long-lines")]
split_long_lines: bool,
/// Minimum text width in columns.
/// Default: 80
#[serde(default = "eighty_val")]
pub minimum_width: usize,
/// Minimum text width in columns.
/// Default: 80
#[serde(default = "eighty_val", alias = "minimum-width")]
minimum_width: usize,
/// Choose `text/html` alternative if `text/plain` is empty in `multipart/alternative`
/// attachments.
/// Default: true
#[serde(default = "internal_value_true")]
pub auto_choose_multipart_alternative: ToggleFlag,
}
fn eighty_val() -> usize {
80
/// Choose `text/html` alternative if `text/plain` is empty in `multipart/alternative`
/// attachments.
/// Default: true
#[serde(
default = "internal_value_true",
alias = "auto-choose-multipart-alternative"
)]
auto_choose_multipart_alternative: ToggleFlag,
}
);
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 crate::override_def;
/// Settings for digital signing and encryption
#[derive(Debug, Deserialize, Clone, Serialize)]
pub struct PGPSettings {
/// auto verify signed e-mail according to RFC3156
#[serde(default = "true_val")]
pub auto_verify_signatures: bool,
override_def!(
PGPSettingsOverride,
/// Settings for digital signing and encryption
#[derive(Debug, Deserialize, Clone, Serialize)]
pub struct PGPSettings {
/// auto verify signed e-mail according to RFC3156
#[serde(default = "true_val", alias = "auto-verify-signatures")]
auto_verify_signatures: bool,
/// always sign sent messages
#[serde(default = "false_val")]
pub auto_sign: bool,
/// always sign sent messages
#[serde(default = "false_val", alias = "auto-sign")]
auto_sign: bool,
// https://tools.ietf.org/html/rfc4880#section-12.2
#[serde(default = "none")]
pub key: Option<String>,
// https://tools.ietf.org/html/rfc4880#section-12.2
#[serde(default = "none")]
key: Option<String>,
/// gpg binary name or file location to use
#[serde(default)]
pub gpg_binary: Option<String>,
}
/// gpg binary name or file location to use
#[serde(default, alias = "gpg-binary")]
gpg_binary: Option<String>,
}
);
impl Default for PGPSettings {
fn default() -> Self {

View File

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

View File

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