summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/meli.17
-rw-r--r--docs/meli.conf.56
-rw-r--r--melib/src/addressbook.rs100
-rw-r--r--melib/src/addressbook/mutt.rs106
-rw-r--r--melib/src/parsec.rs17
5 files changed, 206 insertions, 30 deletions
diff --git a/docs/meli.1 b/docs/meli.1
index 97e5298c..e248538a 100644
--- a/docs/meli.1
+++ b/docs/meli.1
@@ -351,7 +351,7 @@ To open a draft for further editing, select your draft in the mail listing and p
\&.
.Sh CONTACTS
.Nm
-supports two kinds of contact backends:
+supports three kinds of contact backends:
.sp
.Bl -enum -compact -offset indent
.It
@@ -366,6 +366,11 @@ The path defined as
.Ic vcard_folder
can hold multiple vCards per file.
They are loaded read only.
+.It
+a
+.Xr mutt 1
+compatible alias file in the option
+.Ic mutt_alias_file
.El
.sp
See
diff --git a/docs/meli.conf.5 b/docs/meli.conf.5
index 40d67192..09046128 100644
--- a/docs/meli.conf.5
+++ b/docs/meli.conf.5
@@ -176,6 +176,12 @@ Available options are 'none' and 'sqlite3'
.Pq Em optional
Folder that contains .vcf files.
They are parsed and imported read-only.
+.It Ic mutt_alias_file Ar String
+.Pq Em optional
+Path of
+.Xr mutt 1
+compatible alias file in the option
+They are parsed and imported read-only.
.It Ic mailboxes Ar mailbox
.Pq Em optional
Configuration for each mailbox.
diff --git a/melib/src/addressbook.rs b/melib/src/addressbook.rs
index 276c0ae5..42867d08 100644
--- a/melib/src/addressbook.rs
+++ b/melib/src/addressbook.rs
@@ -22,7 +22,10 @@
#[cfg(feature = "vcard")]
pub mod vcard;
+pub mod mutt;
+
use crate::datetime::{self, UnixTimestamp};
+use crate::parsec::Parser;
use std::collections::HashMap;
use uuid::Uuid;
@@ -97,30 +100,50 @@ impl AddressBook {
}
pub fn with_account(s: &crate::conf::AccountSettings) -> AddressBook {
- #[cfg(not(feature = "vcard"))]
- {
- AddressBook::new(s.name.clone())
+ let mut ret = AddressBook::new(s.name.clone());
+ if let Some(mutt_alias_file) = s.extra.get("mutt_alias_file").map(String::as_str) {
+ match std::fs::read_to_string(std::path::Path::new(mutt_alias_file))
+ .map_err(|err| err.to_string())
+ .and_then(|contents| {
+ contents
+ .lines()
+ .map(|line| mutt::parse_mutt_contact().parse(line).map(|(_, c)| c))
+ .collect::<Result<Vec<Card>, &str>>()
+ .map_err(|err| err.to_string())
+ }) {
+ Ok(cards) => {
+ for c in cards {
+ ret.add_card(c);
+ }
+ }
+ Err(err) => {
+ crate::log(
+ format!(
+ "Could not load mutt alias file {:?}: {}",
+ mutt_alias_file, err
+ ),
+ crate::WARN,
+ );
+ }
+ }
}
#[cfg(feature = "vcard")]
- {
- let mut ret = AddressBook::new(s.name.clone());
- if let Some(vcard_path) = s.vcard_folder() {
- match vcard::load_cards(std::path::Path::new(vcard_path)) {
- Ok(cards) => {
- for c in cards {
- ret.add_card(c);
- }
- }
- Err(err) => {
- crate::log(
- format!("Could not load vcards from {:?}: {}", vcard_path, err),
- crate::WARN,
- );
+ if let Some(vcard_path) = s.vcard_folder() {
+ match vcard::load_cards(std::path::Path::new(vcard_path)) {
+ Ok(cards) => {
+ for c in cards {
+ ret.add_card(c);
}
}
+ Err(err) => {
+ crate::log(
+ format!("Could not load vcards from {:?}: {}", vcard_path, err),
+ crate::WARN,
+ );
+ }
}
- ret
}
+ ret
}
pub fn add_card(&mut self, card: Card) {
@@ -203,36 +226,54 @@ impl Card {
datetime::timestamp_to_string(self.last_edited, None, false)
}
- pub fn set_id(&mut self, new_val: CardId) {
+ pub fn set_id(&mut self, new_val: CardId) -> &mut Self {
self.id = new_val;
+ self
}
- pub fn set_title(&mut self, new: String) {
+
+ pub fn set_title(&mut self, new: String) -> &mut Self {
self.title = new;
+ self
}
- pub fn set_name(&mut self, new: String) {
+
+ pub fn set_name(&mut self, new: String) -> &mut Self {
self.name = new;
+ self
}
- pub fn set_additionalname(&mut self, new: String) {
+
+ pub fn set_additionalname(&mut self, new: String) -> &mut Self {
self.additionalname = new;
+ self
}
- pub fn set_name_prefix(&mut self, new: String) {
+
+ pub fn set_name_prefix(&mut self, new: String) -> &mut Self {
self.name_prefix = new;
+ self
}
- pub fn set_name_suffix(&mut self, new: String) {
+
+ pub fn set_name_suffix(&mut self, new: String) -> &mut Self {
self.name_suffix = new;
+ self
}
- pub fn set_email(&mut self, new: String) {
+
+ pub fn set_email(&mut self, new: String) -> &mut Self {
self.email = new;
+ self
}
- pub fn set_url(&mut self, new: String) {
+
+ pub fn set_url(&mut self, new: String) -> &mut Self {
self.url = new;
+ self
}
- pub fn set_key(&mut self, new: String) {
+
+ pub fn set_key(&mut self, new: String) -> &mut Self {
self.key = new;
+ self
}
- pub fn set_extra_property(&mut self, key: &str, value: String) {
+ pub fn set_extra_property(&mut self, key: &str, value: String) -> &mut Self {
self.extra_properties.insert(key.to_string(), value);
+ self
}
pub fn extra_property(&self, key: &str) -> Option<&str> {
@@ -243,8 +284,9 @@ impl Card {
&self.extra_properties
}
- pub fn set_external_resource(&mut self, new_val: bool) {
+ pub fn set_external_resource(&mut self, new_val: bool) -> &mut Self {
self.external_resource = new_val;
+ self
}
pub fn external_resource(&self) -> bool {
diff --git a/melib/src/addressbook/mutt.rs b/melib/src/addressbook/mutt.rs
new file mode 100644
index 00000000..805c3546
--- /dev/null
+++ b/melib/src/addressbook/mutt.rs
@@ -0,0 +1,106 @@
+/*
+ * meli - addressbook module
+ *
+ * Copyright 2019 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/>.
+ */
+
+//! # Mutt contact formats
+//!
+
+use super::*;
+use crate::parsec::{is_not, map_res, match_literal_anycase, prefix, Parser};
+use std::collections::VecDeque;
+
+//alias <nickname> [ <long name> ] <address>
+// From mutt doc:
+//
+// ```text
+// Since the name can consist of several whitespace-separated words, the
+// last word is considered the address, and it can be optionally enclosed
+// between angle brackets.
+// For example: alias mumon My dear pupil Mumon foobar@example.com
+// will be parsed in this way:
+//
+// alias mumon My dear pupil Mumon foobar@example.com
+// ^ ^ ^
+// nickname long name email address
+// The nickname (or alias) will be used to select a corresponding long name
+// and email address when specifying the To field of an outgoing message,
+// e.g. when using the function in the browser or index context.
+// The long name is optional, so you can specify an alias command in this
+// way:
+//
+// alias mumon foobar@example.com
+// ^ ^
+// nickname email address
+// ```
+pub fn parse_mutt_contact<'a>() -> impl Parser<'a, Card> {
+ move |input| {
+ map_res(
+ prefix(match_literal_anycase("alias "), is_not(b"\r\n")),
+ |l| {
+ let mut tokens = l.split_whitespace().collect::<VecDeque<&str>>();
+
+ let mut ret = Card::new();
+ let title = tokens.pop_front().ok_or(l)?.to_string();
+ let mut email = tokens.pop_back().ok_or(l)?.to_string();
+ if email.starts_with('<') && email.ends_with('>') {
+ email.pop();
+ email.remove(0);
+ }
+ let mut name = tokens.into_iter().fold(String::new(), |mut acc, el| {
+ acc.push_str(el);
+ acc.push(' ');
+ acc
+ });
+ name.pop();
+ if name.trim().is_empty() {
+ name = title.clone();
+ }
+ ret.set_title(title).set_email(email).set_name(name);
+ Ok::<Card, &'a str>(ret)
+ },
+ )
+ .parse(input)
+ }
+}
+
+#[test]
+fn test_mutt_contacts() {
+ let a = "alias mumon My dear pupil Mumon foobar@example.com";
+ let b = "alias mumon foobar@example.com";
+ let c = "alias <nickname> <long name> <address>";
+
+ let (other, a_card) = parse_mutt_contact().parse(a).unwrap();
+ assert!(other.is_empty());
+ assert_eq!(a_card.name(), "My dear pupil Mumon");
+ assert_eq!(a_card.title(), "mumon");
+ assert_eq!(a_card.email(), "foobar@example.com");
+
+ let (other, b_card) = parse_mutt_contact().parse(b).unwrap();
+ assert!(other.is_empty());
+ assert_eq!(b_card.name(), "mumon");
+ assert_eq!(b_card.title(), "mumon");
+ assert_eq!(b_card.email(), "foobar@example.com");
+
+ let (other, c_card) = parse_mutt_contact().parse(c).unwrap();
+ assert!(other.is_empty());
+ assert_eq!(c_card.name(), "<long name>");
+ assert_eq!(c_card.title(), "<nickname>");
+ assert_eq!(c_card.email(), "address");
+}
diff --git a/melib/src/parsec.rs b/melib/src/parsec.rs
index a502ca74..8eebf608 100644
--- a/melib/src/parsec.rs
+++ b/melib/src/parsec.rs
@@ -343,6 +343,23 @@ pub fn is_a<'a>(slice: &'static [u8]) -> impl Parser<'a, &'a str> {
}
}
+pub fn is_not<'a>(slice: &'static [u8]) -> impl Parser<'a, &'a str> {
+ move |input: &'a str| {
+ let mut i = 0;
+ for byte in input.as_bytes().iter() {
+ if slice.contains(byte) {
+ break;
+ }
+ i += 1;
+ }
+ if i == 0 {
+ return Err("");
+ }
+ let (b, a) = input.split_at(i);
+ Ok((a, b))
+ }
+}
+
/// Try alternative parsers in order until one succeeds.
///
/// ```rust