melib/addressbook: add parser for mutt alias file
parent
40c6647db8
commit
2878bbb8c8
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
#[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,
|
||||
);
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
#[cfg(feature = "vcard")]
|
||||
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
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
pub fn set_title(&mut self, new: String) {
|
||||
self.title = new;
|
||||
}
|
||||
pub fn set_name(&mut self, new: String) {
|
||||
self.name = new;
|
||||
}
|
||||
pub fn set_additionalname(&mut self, new: String) {
|
||||
self.additionalname = new;
|
||||
}
|
||||
pub fn set_name_prefix(&mut self, new: String) {
|
||||
self.name_prefix = new;
|
||||
}
|
||||
pub fn set_name_suffix(&mut self, new: String) {
|
||||
self.name_suffix = new;
|
||||
}
|
||||
pub fn set_email(&mut self, new: String) {
|
||||
self.email = new;
|
||||
}
|
||||
pub fn set_url(&mut self, new: String) {
|
||||
self.url = new;
|
||||
}
|
||||
pub fn set_key(&mut self, new: String) {
|
||||
self.key = new;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_extra_property(&mut self, key: &str, value: String) {
|
||||
pub fn set_title(&mut self, new: String) -> &mut Self {
|
||||
self.title = new;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_name(&mut self, new: String) -> &mut Self {
|
||||
self.name = new;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_additionalname(&mut self, new: String) -> &mut Self {
|
||||
self.additionalname = new;
|
||||
self
|
||||
}
|
||||
|
||||
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) -> &mut Self {
|
||||
self.name_suffix = new;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_email(&mut self, new: String) -> &mut Self {
|
||||
self.email = new;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_url(&mut self, new: String) -> &mut Self {
|
||||
self.url = new;
|
||||
self
|
||||
}
|
||||
|
||||
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) -> &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 {
|
||||
|
|
|
@ -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");
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue