diff --git a/meli.1 b/meli.1 index ce0b21c26..3527b9c1b 100644 --- a/meli.1 +++ b/meli.1 @@ -430,6 +430,37 @@ attachment according to its mailcap entry. .It Ic v (un)selects mail entries in mail listings .El +.Sh QUERY ABNF SYNTAX +.Bl -bullet +.It +.Li query = \&"(\&" query \&")\&" | from | to | cc | bcc | alladdresses | subject | flags | has_attachments | query \&"or\&" query | query \&"and\&" query | not query +.It +.Li not = \&"not\&" | \&"!\&" +.It +.Li quoted = ALPHA / SP *(ALPHA / DIGIT / SP) +.It +.Li term = ALPHA *(ALPHA / DIGIT) | DQUOTE quoted DQUOTE +.It +.Li tagname = term +.It +.Li flagval = \&"passed\&" | \&"replied\&" | \&"seen\&" | \&"read\&" | \&"junk\&" | \&"trash\&" | \&"trashed\&" | \&"draft\&" | \&"flagged\&" | tagname +.It +.Li flagterm = flagval | flagval \&",\&" flagterm +.It +.Li from = \&"from:\&" term +.It +.Li to = \&"to:\&" term +.It +.Li cc = \&"cc:\&" term +.It +.Li bcc = \&"bcc:\&" term +.It +.Li alladdresses = \&"alladdresses:\&" term +.It +.Li subject = \&"subject:\&" term +.It +.Li flags = \&"flags:\&" flag | \&"tags:\&" flag | \&"is:\&" flag +.El .Sh EXIT STATUS .Nm exits with 0 on a successful run. diff --git a/meli.conf.5 b/meli.conf.5 index f1cc7bd68..542833dac 100644 --- a/meli.conf.5 +++ b/meli.conf.5 @@ -607,6 +607,16 @@ this is usually what you want (optional) Show recent dates as `X {minutes,hours,days} ago`, up to 7 days. .\" default value .Pq Em true +.It Ic filter Ar Query +(optional) Show only envelopes matching this query (for query syntax see +.Xr meli 1 ) +.\" default value +.Pq Em None +.Pp +Example: +.Bd -literal +filter = "not flags:seen" # show only unseen messages +.Ed .El .Sh TAGS .Bl -tag -width 36n diff --git a/melib/src/email.rs b/melib/src/email.rs index 56ad91585..dc309db22 100644 --- a/melib/src/email.rs +++ b/melib/src/email.rs @@ -65,6 +65,20 @@ bitflags! { } } +impl PartialEq<&str> for Flag { + fn eq(&self, other: &&str) -> bool { + (other.eq_ignore_ascii_case("passed") && self.contains(Flag::PASSED)) + || (other.eq_ignore_ascii_case("replied") && self.contains(Flag::REPLIED)) + || (other.eq_ignore_ascii_case("seen") && self.contains(Flag::SEEN)) + || (other.eq_ignore_ascii_case("read") && self.contains(Flag::SEEN)) + || (other.eq_ignore_ascii_case("junk") && self.contains(Flag::TRASHED)) + || (other.eq_ignore_ascii_case("trash") && self.contains(Flag::TRASHED)) + || (other.eq_ignore_ascii_case("trashed") && self.contains(Flag::TRASHED)) + || (other.eq_ignore_ascii_case("draft") && self.contains(Flag::DRAFT)) + || (other.eq_ignore_ascii_case("flagged") && self.contains(Flag::FLAGGED)) + } +} + #[derive(Debug, Clone, Default)] pub struct EnvelopeWrapper { envelope: Envelope, @@ -113,7 +127,7 @@ pub type EnvelopeHash = u64; /// Access to the underlying email object in the account's backend (for example the file or the /// entry in an IMAP server) is given through `operation_token`. For more information see /// `BackendOp`. -#[derive(Clone, Default, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] pub struct Envelope { date: String, from: Vec
, @@ -150,6 +164,12 @@ impl fmt::Debug for Envelope { } } +impl Default for Envelope { + fn default() -> Self { + Envelope::new(0) + } +} + impl Envelope { pub fn new(hash: EnvelopeHash) -> Self { Envelope { @@ -162,7 +182,17 @@ impl Envelope { message_id: MessageID::default(), in_reply_to: None, references: None, - other_headers: FnvHashMap::default(), + other_headers: [ + ("From".to_string(), String::new()), + ("To".to_string(), String::new()), + ("Subject".to_string(), String::new()), + ("Date".to_string(), String::new()), + ("Cc".to_string(), String::new()), + ("Bcc".to_string(), String::new()), + ] + .iter() + .cloned() + .collect(), timestamp: 0, @@ -218,9 +248,17 @@ impl Envelope { let mut in_reply_to = None; for (name, value) in headers { - if value.len() == 1 && value.is_empty() { - continue; - } + self.other_headers.insert( + String::from_utf8(name.to_vec()) + .unwrap_or_else(|err| String::from_utf8_lossy(&err.into_bytes()).into()), + parser::phrase(value, false) + .to_full_result() + .map(|value| { + String::from_utf8(value) + .unwrap_or_else(|err| String::from_utf8_lossy(&err.into_bytes()).into()) + }) + .unwrap_or_else(|_| String::from_utf8_lossy(value).into()), + ); if name.eq_ignore_ascii_case(b"to") { let parse_result = parser::rfc2822address_list(value); if parse_result.is_done() { @@ -298,19 +336,6 @@ impl Envelope { } _ => {} } - } else { - self.other_headers.insert( - String::from_utf8(name.to_vec()) - .unwrap_or_else(|err| String::from_utf8_lossy(&err.into_bytes()).into()), - parser::phrase(value, false) - .to_full_result() - .map(|value| { - String::from_utf8(value).unwrap_or_else(|err| { - String::from_utf8_lossy(&err.into_bytes()).into() - }) - }) - .unwrap_or_else(|_| String::from_utf8_lossy(value).into()), - ); } } /* diff --git a/src/cache.rs b/src/cache.rs index e4de72aa3..078f2ef48 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -35,7 +35,7 @@ use std::sync::{Arc, RwLock}; pub use query_parser::query; use Query::*; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone, Serialize)] pub enum Query { Before(UnixTimestamp), After(UnixTimestamp), @@ -61,6 +61,44 @@ pub enum Query { Not(Box