melib: add utils::{futures, random}
parent
02e86d1fad
commit
5699baecfb
|
@ -28,7 +28,7 @@ use std::{collections::HashMap, ops::Deref};
|
|||
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
use crate::utils::{
|
||||
datetime::{self, UnixTimestamp},
|
||||
parsec::Parser,
|
||||
};
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
use std::collections::VecDeque;
|
||||
|
||||
use super::*;
|
||||
use crate::parsec::{is_not, map_res, match_literal_anycase, prefix, Parser};
|
||||
use crate::utils::parsec::{is_not, map_res, match_literal_anycase, prefix, Parser};
|
||||
|
||||
//alias <nickname> [ <long name> ] <address>
|
||||
// From mutt doc:
|
||||
|
|
|
@ -32,7 +32,7 @@ use std::{collections::HashMap, convert::TryInto};
|
|||
use super::*;
|
||||
use crate::{
|
||||
error::{Error, Result},
|
||||
parsec::{match_literal_anycase, one_or_more, peek, prefix, take_until, Parser},
|
||||
utils::parsec::{match_literal_anycase, one_or_more, peek, prefix, take_until, Parser},
|
||||
};
|
||||
|
||||
/* Supported vcard versions */
|
||||
|
@ -222,8 +222,9 @@ impl<V: VCardVersion> TryInto<Card> for VCard<V> {
|
|||
T102200Z
|
||||
T102200-0800
|
||||
*/
|
||||
card.birthday = crate::datetime::timestamp_from_string(val.value.as_str(), "%Y%m%d\0")
|
||||
.unwrap_or_default();
|
||||
card.birthday =
|
||||
crate::utils::datetime::timestamp_from_string(val.value.as_str(), "%Y%m%d\0")
|
||||
.unwrap_or_default();
|
||||
}
|
||||
if let Some(val) = self.0.remove("EMAIL") {
|
||||
card.set_email(val.value);
|
||||
|
|
|
@ -65,9 +65,9 @@ use crate::{
|
|||
},
|
||||
collection::Collection,
|
||||
conf::AccountSettings,
|
||||
connections::timeout,
|
||||
email::{parser::BytesExt, *},
|
||||
error::{Error, Result, ResultIntoError},
|
||||
utils::futures::timeout,
|
||||
};
|
||||
|
||||
pub type ImapNum = usize;
|
||||
|
|
|
@ -106,17 +106,17 @@ pub use sqlite3_m::*;
|
|||
#[cfg(feature = "sqlite3")]
|
||||
mod sqlite3_m {
|
||||
use super::*;
|
||||
use crate::sqlite3::{
|
||||
use crate::utils::sqlite3::{
|
||||
self,
|
||||
rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput},
|
||||
DatabaseDescription,
|
||||
Connection, DatabaseDescription,
|
||||
};
|
||||
|
||||
type Sqlite3UID = i32;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Sqlite3Cache {
|
||||
connection: crate::sqlite3::Connection,
|
||||
connection: Connection,
|
||||
loaded_mailboxes: BTreeSet<MailboxHash>,
|
||||
uid_store: Arc<UIDStore>,
|
||||
}
|
||||
|
|
|
@ -21,10 +21,13 @@
|
|||
|
||||
use super::protocol_parser::{ImapLineSplit, ImapResponse, RequiredResponses, SelectResponse};
|
||||
use crate::{
|
||||
backends::{MailboxHash, RefreshEvent},
|
||||
connections::{lookup_ipv4, timeout, Connection},
|
||||
backends::{BackendEvent, MailboxHash, RefreshEvent},
|
||||
email::parser::BytesExt,
|
||||
error::*,
|
||||
utils::{
|
||||
connections::{lookup_ipv4, Connection},
|
||||
futures::timeout,
|
||||
},
|
||||
LogLevel,
|
||||
};
|
||||
extern crate native_tls;
|
||||
|
@ -162,7 +165,7 @@ impl ImapStream {
|
|||
let stream = if server_conf.use_tls {
|
||||
(uid_store.event_consumer)(
|
||||
uid_store.account_hash,
|
||||
crate::backends::BackendEvent::AccountStateChange {
|
||||
BackendEvent::AccountStateChange {
|
||||
message: "Establishing TLS connection.".into(),
|
||||
},
|
||||
);
|
||||
|
@ -172,9 +175,7 @@ impl ImapStream {
|
|||
}
|
||||
let connector = connector
|
||||
.build()
|
||||
.chain_err_kind(crate::error::ErrorKind::Network(
|
||||
crate::error::NetworkErrorKind::InvalidTLSConnection,
|
||||
))?;
|
||||
.chain_err_kind(ErrorKind::Network(NetworkErrorKind::InvalidTLSConnection))?;
|
||||
|
||||
let addr = lookup_ipv4(path, server_conf.server_port)?;
|
||||
|
||||
|
@ -263,8 +264,8 @@ impl ImapStream {
|
|||
midhandshake_stream = Some(stream);
|
||||
}
|
||||
p => {
|
||||
p.chain_err_kind(crate::error::ErrorKind::Network(
|
||||
crate::error::NetworkErrorKind::InvalidTLSConnection,
|
||||
p.chain_err_kind(ErrorKind::Network(
|
||||
NetworkErrorKind::InvalidTLSConnection,
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
@ -276,11 +277,7 @@ impl ImapStream {
|
|||
.chain_err_summary(|| format!("Could not initiate TLS negotiation to {}.", path))?
|
||||
}
|
||||
} else {
|
||||
let addr = if let Ok(a) = lookup_ipv4(path, server_conf.server_port) {
|
||||
a
|
||||
} else {
|
||||
return Err(Error::new(format!("Could not lookup address {}", &path)));
|
||||
};
|
||||
let addr = lookup_ipv4(path, server_conf.server_port)?;
|
||||
AsyncWrapper::new(Connection::Tcp(
|
||||
if let Some(timeout) = server_conf.timeout {
|
||||
TcpStream::connect_timeout(&addr, timeout)?
|
||||
|
@ -322,7 +319,7 @@ impl ImapStream {
|
|||
|
||||
(uid_store.event_consumer)(
|
||||
uid_store.account_hash,
|
||||
crate::backends::BackendEvent::AccountStateChange {
|
||||
BackendEvent::AccountStateChange {
|
||||
message: "Negotiating server capabilities.".into(),
|
||||
},
|
||||
);
|
||||
|
@ -344,7 +341,7 @@ impl ImapStream {
|
|||
&server_conf.server_hostname,
|
||||
String::from_utf8_lossy(&res)
|
||||
))
|
||||
.set_kind(ErrorKind::Bug));
|
||||
.set_kind(ErrorKind::ProtocolError));
|
||||
}
|
||||
|
||||
let capabilities = capabilities.unwrap();
|
||||
|
@ -355,7 +352,8 @@ impl ImapStream {
|
|||
return Err(Error::new(format!(
|
||||
"Could not connect to {}: server is not IMAP4rev1 compliant",
|
||||
&server_conf.server_hostname
|
||||
)));
|
||||
))
|
||||
.set_kind(ErrorKind::ProtocolNotSupported));
|
||||
} else if capabilities
|
||||
.iter()
|
||||
.any(|cap| cap.eq_ignore_ascii_case(b"LOGINDISABLED"))
|
||||
|
@ -364,12 +362,12 @@ impl ImapStream {
|
|||
"Could not connect to {}: server does not accept logins [LOGINDISABLED]",
|
||||
&server_conf.server_hostname
|
||||
))
|
||||
.set_err_kind(crate::error::ErrorKind::Authentication));
|
||||
.set_err_kind(ErrorKind::Authentication));
|
||||
}
|
||||
|
||||
(uid_store.event_consumer)(
|
||||
uid_store.account_hash,
|
||||
crate::backends::BackendEvent::AccountStateChange {
|
||||
BackendEvent::AccountStateChange {
|
||||
message: "Attempting authentication.".into(),
|
||||
},
|
||||
);
|
||||
|
@ -390,10 +388,14 @@ impl ImapStream {
|
|||
.map(|capability| String::from_utf8_lossy(capability).to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
)));
|
||||
))
|
||||
.set_err_kind(ErrorKind::Authentication));
|
||||
}
|
||||
let xoauth2 = base64::decode(&server_conf.server_password)
|
||||
.map_err(|_| Error::new("Bad XOAUTH2 in config"))?;
|
||||
.chain_err_summary(|| {
|
||||
"Could not decode `server_password` from base64. Is the value correct?"
|
||||
})
|
||||
.chain_err_kind(ErrorKind::Configuration)?;
|
||||
// TODO(#222): Improve this as soon as imap-codec supports XOAUTH2.
|
||||
ret.send_command(CommandBody::authenticate(
|
||||
AuthMechanism::Other(
|
||||
|
@ -404,8 +406,10 @@ impl ImapStream {
|
|||
.await?;
|
||||
}
|
||||
_ => {
|
||||
let username = AString::try_from(server_conf.server_username.as_str())?;
|
||||
let password = AString::try_from(server_conf.server_password.as_str())?;
|
||||
let username = AString::try_from(server_conf.server_username.as_str())
|
||||
.chain_err_kind(ErrorKind::Bug)?;
|
||||
let password = AString::try_from(server_conf.server_password.as_str())
|
||||
.chain_err_kind(ErrorKind::Bug)?;
|
||||
|
||||
ret.send_command(CommandBody::Login {
|
||||
username,
|
||||
|
@ -435,7 +439,7 @@ impl ImapStream {
|
|||
"Could not connect. Server replied with '{}'",
|
||||
String::from_utf8_lossy(l[tag_start.len()..].trim())
|
||||
))
|
||||
.set_err_kind(crate::error::ErrorKind::Authentication));
|
||||
.set_err_kind(ErrorKind::Authentication));
|
||||
}
|
||||
should_break = true;
|
||||
}
|
||||
|
@ -448,7 +452,6 @@ impl ImapStream {
|
|||
if capabilities.is_none() {
|
||||
/* sending CAPABILITY after LOGIN automatically is an RFC recommendation, so
|
||||
* check for lazy servers */
|
||||
drop(capabilities);
|
||||
ret.send_command(CommandBody::Capability).await?;
|
||||
ret.read_response(&mut res).await.unwrap();
|
||||
let capabilities = protocol_parser::capabilities(&res)?.1;
|
||||
|
@ -827,7 +830,7 @@ impl ImapConnection {
|
|||
);
|
||||
(self.uid_store.event_consumer)(
|
||||
self.uid_store.account_hash,
|
||||
crate::backends::BackendEvent::Notice {
|
||||
BackendEvent::Notice {
|
||||
description: response_code.to_string(),
|
||||
content: None,
|
||||
level: LogLevel::ERROR,
|
||||
|
@ -845,7 +848,7 @@ impl ImapConnection {
|
|||
);
|
||||
(self.uid_store.event_consumer)(
|
||||
self.uid_store.account_hash,
|
||||
crate::backends::BackendEvent::Notice {
|
||||
BackendEvent::Notice {
|
||||
description: response_code.to_string(),
|
||||
content: None,
|
||||
level: LogLevel::ERROR,
|
||||
|
@ -974,7 +977,7 @@ impl ImapConnection {
|
|||
"Trying to select a \\NoSelect mailbox: {}",
|
||||
&imap_path
|
||||
))
|
||||
.set_kind(crate::error::ErrorKind::Bug));
|
||||
.set_kind(ErrorKind::Bug));
|
||||
}
|
||||
self.send_command(CommandBody::select(imap_path.as_str())?)
|
||||
.await?;
|
||||
|
@ -1004,7 +1007,7 @@ impl ImapConnection {
|
|||
}) {
|
||||
(self.uid_store.event_consumer)(
|
||||
self.uid_store.account_hash,
|
||||
crate::backends::BackendEvent::from(err),
|
||||
BackendEvent::from(err),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1061,7 +1064,7 @@ impl ImapConnection {
|
|||
"Trying to examine a \\NoSelect mailbox: {}",
|
||||
&imap_path
|
||||
))
|
||||
.set_kind(crate::error::ErrorKind::Bug));
|
||||
.set_kind(ErrorKind::Bug));
|
||||
}
|
||||
self.send_command(CommandBody::examine(imap_path.as_str())?)
|
||||
.await?;
|
||||
|
@ -1126,10 +1129,7 @@ impl ImapConnection {
|
|||
}
|
||||
|
||||
pub fn add_refresh_event(&mut self, ev: RefreshEvent) {
|
||||
(self.uid_store.event_consumer)(
|
||||
self.uid_store.account_hash,
|
||||
crate::backends::BackendEvent::Refresh(ev),
|
||||
);
|
||||
(self.uid_store.event_consumer)(self.uid_store.account_hash, BackendEvent::Refresh(ev));
|
||||
}
|
||||
|
||||
async fn create_uid_msn_cache(
|
||||
|
|
|
@ -24,8 +24,8 @@
|
|||
use std::collections::VecDeque;
|
||||
|
||||
use crate::{
|
||||
datetime::{formats::IMAP_DATE, timestamp_to_string},
|
||||
search::*,
|
||||
utils::datetime::{formats::IMAP_DATE, timestamp_to_string},
|
||||
};
|
||||
|
||||
mod private {
|
||||
|
@ -256,7 +256,7 @@ impl ToImapSearch for Query {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::parsec::Parser;
|
||||
use crate::utils::parsec::Parser;
|
||||
|
||||
#[test]
|
||||
fn test_imap_query_search() {
|
||||
|
|
|
@ -34,9 +34,9 @@ use serde_json::Value;
|
|||
use crate::{
|
||||
backends::*,
|
||||
conf::AccountSettings,
|
||||
connections::timeout,
|
||||
email::*,
|
||||
error::{Error, Result},
|
||||
utils::futures::{sleep, timeout},
|
||||
Collection,
|
||||
};
|
||||
|
||||
|
@ -344,7 +344,7 @@ impl MailBackend for JmapType {
|
|||
conn.email_changes(mailbox_hash).await?;
|
||||
}
|
||||
}
|
||||
crate::connections::sleep(Duration::from_secs(60)).await;
|
||||
sleep(Duration::from_secs(60)).await;
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ use super::*;
|
|||
use crate::{
|
||||
backends::jmap::rfc8620::bool_false,
|
||||
email::address::{Address, MailboxAddress},
|
||||
utils::datetime,
|
||||
};
|
||||
|
||||
mod import;
|
||||
|
@ -265,8 +266,7 @@ impl std::convert::From<EmailObject> for crate::Envelope {
|
|||
env.set_datetime(d);
|
||||
}
|
||||
if let Some(ref mut sent_at) = t.sent_at {
|
||||
let unix =
|
||||
crate::datetime::rfc3339_to_timestamp(sent_at.as_bytes().to_vec()).unwrap_or(0);
|
||||
let unix = datetime::rfc3339_to_timestamp(sent_at.as_bytes().to_vec()).unwrap_or(0);
|
||||
env.set_datetime(unix);
|
||||
env.set_date(std::mem::replace(sent_at, String::new()).as_bytes());
|
||||
}
|
||||
|
@ -588,10 +588,10 @@ impl From<crate::search::Query> for Filter<EmailFilterCondition, EmailObject> {
|
|||
fn from(val: crate::search::Query) -> Self {
|
||||
let mut ret = Filter::Condition(EmailFilterCondition::new().into());
|
||||
fn rec(q: &crate::search::Query, f: &mut Filter<EmailFilterCondition, EmailObject>) {
|
||||
use crate::{
|
||||
datetime::{formats::RFC3339_DATE, timestamp_to_string},
|
||||
search::Query::*,
|
||||
};
|
||||
use datetime::{formats::RFC3339_DATE, timestamp_to_string};
|
||||
|
||||
use crate::search::Query::*;
|
||||
|
||||
match q {
|
||||
Subject(t) => {
|
||||
*f = Filter::Condition(EmailFilterCondition::new().subject(t.clone()).into());
|
||||
|
|
|
@ -40,7 +40,7 @@ use crate::{
|
|||
backends::*,
|
||||
email::Flag,
|
||||
error::{Error, Result},
|
||||
shellexpand::ShellExpandTrait,
|
||||
utils::shellexpand::ShellExpandTrait,
|
||||
};
|
||||
|
||||
/// `BackendOp` implementor for Maildir
|
||||
|
|
|
@ -32,7 +32,7 @@ use crate::{
|
|||
conf::AccountSettings,
|
||||
email::{Envelope, EnvelopeHash, Flag},
|
||||
error::{Error, ErrorKind, Result},
|
||||
shellexpand::ShellExpandTrait,
|
||||
utils::shellexpand::ShellExpandTrait,
|
||||
Collection,
|
||||
};
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@ use crate::{
|
|||
email::{parser::BytesExt, *},
|
||||
error::{Error, ErrorKind, Result},
|
||||
get_path_hash,
|
||||
shellexpand::ShellExpandTrait,
|
||||
utils::shellexpand::ShellExpandTrait,
|
||||
};
|
||||
|
||||
extern crate notify;
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
*/
|
||||
|
||||
use super::*;
|
||||
use crate::datetime;
|
||||
use crate::utils::datetime;
|
||||
|
||||
impl MboxFormat {
|
||||
pub fn append(
|
||||
|
|
|
@ -52,9 +52,9 @@ use futures::{lock::Mutex as FutureMutex, stream::Stream};
|
|||
use crate::{
|
||||
backends::*,
|
||||
conf::AccountSettings,
|
||||
connections::timeout,
|
||||
email::*,
|
||||
error::{Error, Result, ResultIntoError},
|
||||
utils::futures::timeout,
|
||||
Collection,
|
||||
};
|
||||
pub type UID = usize;
|
||||
|
@ -286,8 +286,11 @@ impl MailBackend for NntpType {
|
|||
let mut conn = timeout(Some(Duration::from_secs(60 * 16)), connection.lock()).await?;
|
||||
if let Some(mut latest_article) = latest_article {
|
||||
let timestamp = latest_article - 10 * 60;
|
||||
let datetime_str =
|
||||
crate::datetime::timestamp_to_string(timestamp, Some("%Y%m%d %H%M%S"), true);
|
||||
let datetime_str = crate::utils::datetime::timestamp_to_string(
|
||||
timestamp,
|
||||
Some("%Y%m%d %H%M%S"),
|
||||
true,
|
||||
);
|
||||
|
||||
if newnews_support {
|
||||
conn.send_command(
|
||||
|
|
|
@ -21,10 +21,10 @@
|
|||
|
||||
use crate::{
|
||||
backends::{BackendMailbox, MailboxHash},
|
||||
connections::{lookup_ipv4, Connection},
|
||||
email::parser::BytesExt,
|
||||
error::*,
|
||||
log,
|
||||
utils::connections::{lookup_ipv4, Connection},
|
||||
};
|
||||
extern crate native_tls;
|
||||
use std::{collections::HashSet, future::Future, pin::Pin, sync::Arc, time::Instant};
|
||||
|
|
|
@ -35,7 +35,7 @@ use crate::{
|
|||
conf::AccountSettings,
|
||||
email::{Envelope, EnvelopeHash, Flag},
|
||||
error::{Error, Result},
|
||||
shellexpand::ShellExpandTrait,
|
||||
utils::shellexpand::ShellExpandTrait,
|
||||
Collection,
|
||||
};
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ impl<'m> Message<'m> {
|
|||
unsafe { CStr::from_ptr(msg_id) }
|
||||
}
|
||||
|
||||
pub fn date(&self) -> crate::datetime::UnixTimestamp {
|
||||
pub fn date(&self) -> crate::UnixTimestamp {
|
||||
(unsafe { call!(self.lib, notmuch_message_get_date)(self.message) }) as u64
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ impl<'q> Thread<'q> {
|
|||
ThreadHash::from(c_str.to_bytes())
|
||||
}
|
||||
|
||||
pub fn date(&self) -> crate::datetime::UnixTimestamp {
|
||||
pub fn date(&self) -> crate::UnixTimestamp {
|
||||
(unsafe { call!(self.lib, notmuch_thread_get_newest_date)(self.ptr) }) as u64
|
||||
}
|
||||
|
||||
|
|
|
@ -122,11 +122,10 @@ use imap_codec::{
|
|||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
datetime::UnixTimestamp,
|
||||
error::{Error, Result},
|
||||
parser::BytesExt,
|
||||
thread::ThreadNodeHash,
|
||||
TagHash,
|
||||
TagHash, UnixTimestamp,
|
||||
};
|
||||
|
||||
#[cfg(feature = "imap_backend")]
|
||||
|
|
|
@ -33,12 +33,11 @@ use xdg_utils::query_mime_info;
|
|||
|
||||
use super::*;
|
||||
use crate::{
|
||||
datetime,
|
||||
email::{
|
||||
attachment_types::{Charset, ContentTransferEncoding, ContentType, MultipartType},
|
||||
attachments::AttachmentBuilder,
|
||||
},
|
||||
shellexpand::ShellExpandTrait,
|
||||
utils::{datetime, shellexpand::ShellExpandTrait},
|
||||
};
|
||||
|
||||
pub mod mime;
|
||||
|
|
|
@ -19,31 +19,7 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{char, fs::File, io::prelude::*, time::SystemTime};
|
||||
|
||||
fn random_u64() -> u64 {
|
||||
let mut f = File::open("/dev/urandom").unwrap();
|
||||
let mut buffer = [0; 8];
|
||||
|
||||
// read exactly 10 bytes
|
||||
f.read_exact(&mut buffer).unwrap();
|
||||
|
||||
u64::from(buffer[0])
|
||||
| (u64::from(buffer[1]) << 8)
|
||||
| (u64::from(buffer[2]) << 16)
|
||||
| (u64::from(buffer[3]) << 24)
|
||||
| (u64::from(buffer[4]) << 32)
|
||||
| (u64::from(buffer[5]) << 40)
|
||||
| (u64::from(buffer[6]) << 48)
|
||||
| (u64::from(buffer[7]) << 56)
|
||||
}
|
||||
|
||||
fn clock() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
}
|
||||
use crate::utils::random::{clock, random_u64};
|
||||
|
||||
fn base36(mut m: u64) -> String {
|
||||
let mut stack = Vec::with_capacity(32);
|
||||
|
|
|
@ -29,7 +29,7 @@ use std::convert::TryFrom;
|
|||
use super::*;
|
||||
use crate::{
|
||||
email::headers::HeaderMap,
|
||||
percent_encoding::{AsciiSet, CONTROLS},
|
||||
utils::percent_encoding::{AsciiSet, CONTROLS},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
|
|
|
@ -42,8 +42,7 @@ use crate::{
|
|||
mailto::Mailto,
|
||||
},
|
||||
error::{Error, Result, ResultIntoError},
|
||||
html_escape::HtmlEntity,
|
||||
percent_encoding::percent_decode,
|
||||
utils::{html_escape::HtmlEntity, percent_encoding::percent_decode},
|
||||
};
|
||||
|
||||
macro_rules! to_str {
|
||||
|
@ -330,7 +329,7 @@ pub fn mail(input: &[u8]) -> Result<(Vec<(&[u8], &[u8])>, &[u8])> {
|
|||
pub mod dates {
|
||||
/*! Date values in headers */
|
||||
use super::{generic::*, *};
|
||||
use crate::datetime::UnixTimestamp;
|
||||
use crate::utils::datetime::UnixTimestamp;
|
||||
|
||||
fn take_n_digits(n: usize) -> impl Fn(&[u8]) -> IResult<&[u8], &[u8]> {
|
||||
move |input: &[u8]| {
|
||||
|
@ -451,7 +450,7 @@ pub mod dates {
|
|||
accum.extend_from_slice(b" ");
|
||||
accum.extend_from_slice(sign);
|
||||
accum.extend_from_slice(zone);
|
||||
match crate::datetime::rfc822_to_timestamp(accum.to_vec()) {
|
||||
match crate::utils::datetime::rfc822_to_timestamp(accum.to_vec()) {
|
||||
Ok(t) => Ok((input, t)),
|
||||
Err(_err) => Err(nom::Err::Error(
|
||||
(
|
||||
|
@ -513,7 +512,7 @@ pub mod dates {
|
|||
accum.extend_from_slice(sign);
|
||||
accum.extend_from_slice(zone);
|
||||
}
|
||||
match crate::datetime::rfc822_to_timestamp(accum.to_vec()) {
|
||||
match crate::utils::datetime::rfc822_to_timestamp(accum.to_vec()) {
|
||||
Ok(t) => Ok((input, t)),
|
||||
Err(_err) => Err(nom::Err::Error(
|
||||
(
|
||||
|
@ -582,7 +581,7 @@ pub mod dates {
|
|||
Ok((input, ret))
|
||||
}
|
||||
|
||||
pub fn rfc5322_date(input: &[u8]) -> Result<crate::datetime::UnixTimestamp> {
|
||||
pub fn rfc5322_date(input: &[u8]) -> Result<crate::UnixTimestamp> {
|
||||
date_time(input)
|
||||
.or_else(|_| {
|
||||
//let (_, mut parsed_result) = encodings::phrase(&eat_comments(input), false)?;
|
||||
|
@ -616,7 +615,7 @@ pub mod dates {
|
|||
parsed_result[pos] = b'+';
|
||||
}
|
||||
|
||||
crate::datetime::rfc822_to_timestamp(parsed_result.trim())
|
||||
crate::utils::datetime::rfc822_to_timestamp(parsed_result.trim())
|
||||
*/
|
||||
}
|
||||
|
||||
|
|
|
@ -183,7 +183,7 @@ pub enum NetworkErrorKind {
|
|||
}
|
||||
|
||||
impl NetworkErrorKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
pub const fn as_str(&self) -> &'static str {
|
||||
use NetworkErrorKind::*;
|
||||
match self {
|
||||
None => "Network",
|
||||
|
@ -242,6 +242,19 @@ impl NetworkErrorKind {
|
|||
NetworkAuthenticationRequired => "Network Authentication Required",
|
||||
}
|
||||
}
|
||||
|
||||
/// Error kind means network is certainly down.
|
||||
pub const fn is_network_down(&self) -> bool {
|
||||
use NetworkErrorKind::*;
|
||||
matches!(
|
||||
self,
|
||||
BadGateway
|
||||
| ServiceUnavailable
|
||||
| GatewayTimeout
|
||||
| NetworkAuthenticationRequired
|
||||
| ConnectionFailed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NetworkErrorKind {
|
||||
|
@ -312,6 +325,13 @@ pub enum ErrorKind {
|
|||
External,
|
||||
Authentication,
|
||||
Configuration,
|
||||
/// Protocol error.
|
||||
///
|
||||
/// `EPROTO 71 Protocol error`
|
||||
ProtocolError,
|
||||
/// Protocol is not supported.
|
||||
/// It could be the wrong type or version.
|
||||
ProtocolNotSupported,
|
||||
Bug,
|
||||
Network(NetworkErrorKind),
|
||||
Timeout,
|
||||
|
@ -332,6 +352,9 @@ impl fmt::Display for ErrorKind {
|
|||
Self::Authentication => "Authentication",
|
||||
Self::Bug => "Bug, please report this!",
|
||||
Self::Network(ref inner) => inner.as_str(),
|
||||
Self::ProtocolError => "Protocol error",
|
||||
Self::ProtocolNotSupported =>
|
||||
"Protocol is not supported. It could be the wrong type or version.",
|
||||
Self::Timeout => "Timeout",
|
||||
Self::OSError => "OS Error",
|
||||
Self::Configuration => "Configuration",
|
||||
|
@ -343,18 +366,29 @@ impl fmt::Display for ErrorKind {
|
|||
}
|
||||
}
|
||||
|
||||
macro_rules! is_variant {
|
||||
($n:ident, $($var:tt)+) => {
|
||||
#[inline]
|
||||
pub fn $n(&self) -> bool {
|
||||
matches!(self, Self::$($var)*)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl ErrorKind {
|
||||
pub fn is_network(&self) -> bool {
|
||||
matches!(self, Self::Network(_))
|
||||
}
|
||||
|
||||
pub fn is_timeout(&self) -> bool {
|
||||
matches!(self, Self::Timeout)
|
||||
}
|
||||
|
||||
pub fn is_authentication(&self) -> bool {
|
||||
matches!(self, Self::Authentication)
|
||||
}
|
||||
is_variant! { is_authentication, Authentication }
|
||||
is_variant! { is_bug, Bug }
|
||||
is_variant! { is_configuration, Configuration }
|
||||
is_variant! { is_external, External }
|
||||
is_variant! { is_network, Network(_) }
|
||||
is_variant! { is_network_down, Network(ref k) if k.is_network_down() }
|
||||
is_variant! { is_not_implemented, NotImplemented }
|
||||
is_variant! { is_not_supported, NotSupported }
|
||||
is_variant! { is_oserror, OSError }
|
||||
is_variant! { is_protocol_error, ProtocolError }
|
||||
is_variant! { is_protocol_not_supported, ProtocolNotSupported }
|
||||
is_variant! { is_timeout, Timeout }
|
||||
is_variant! { is_value_error, ValueError }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -709,3 +743,12 @@ impl<'a> From<&'a Error> for Error {
|
|||
kind.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<base64::DecodeError> for Error {
|
||||
#[inline]
|
||||
fn from(kind: base64::DecodeError) -> Error {
|
||||
Error::new("base64 decoding failed")
|
||||
.set_source(Some(Arc::new(kind)))
|
||||
.set_kind(ErrorKind::ValueError)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,9 +82,10 @@ pub mod dbg {
|
|||
#[cfg(feature = "unicode_algorithms")]
|
||||
pub mod text_processing;
|
||||
|
||||
pub use utils::{datetime::UnixTimestamp, *};
|
||||
|
||||
pub use self::logging::{LogLevel, StderrLogger};
|
||||
pub use utils::{
|
||||
datetime::UnixTimestamp,
|
||||
logging::{LogLevel, StderrLogger},
|
||||
};
|
||||
|
||||
pub mod addressbook;
|
||||
pub use addressbook::*;
|
||||
|
@ -98,13 +99,13 @@ pub use conf::*;
|
|||
pub mod email;
|
||||
pub use email::*;
|
||||
pub mod error;
|
||||
pub use crate::error::*;
|
||||
pub use error::*;
|
||||
pub mod thread;
|
||||
pub use thread::*;
|
||||
pub mod search;
|
||||
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
pub mod utils;
|
||||
|
||||
#[cfg(feature = "gpgme")]
|
||||
pub mod gpgme;
|
||||
|
@ -158,4 +159,4 @@ impl core::fmt::Display for Bytes {
|
|||
}
|
||||
}
|
||||
|
||||
pub use shellexpand::ShellExpandTrait;
|
||||
pub use utils::shellexpand::ShellExpandTrait;
|
||||
|
|
|
@ -24,7 +24,7 @@ use std::{borrow::Cow, convert::TryFrom};
|
|||
pub use query_parser::query;
|
||||
use Query::*;
|
||||
|
||||
use crate::{
|
||||
use crate::utils::{
|
||||
datetime::{formats, UnixTimestamp},
|
||||
parsec::*,
|
||||
};
|
||||
|
@ -152,9 +152,10 @@ pub mod query_parser {
|
|||
fn date<'a>() -> impl Parser<'a, UnixTimestamp> {
|
||||
move |input| {
|
||||
literal().parse(input).and_then(|(next_input, result)| {
|
||||
if let Ok((_, t)) =
|
||||
crate::datetime::parse_timestamp_from_string(result, formats::RFC3339_DATE)
|
||||
{
|
||||
if let Ok((_, t)) = crate::utils::datetime::parse_timestamp_from_string(
|
||||
result,
|
||||
formats::RFC3339_DATE,
|
||||
) {
|
||||
Ok((next_input, t))
|
||||
} else {
|
||||
Err(next_input)
|
||||
|
@ -368,8 +369,8 @@ pub mod query_parser {
|
|||
/// # Invocation
|
||||
/// ```
|
||||
/// use melib::{
|
||||
/// parsec::Parser,
|
||||
/// search::{query, Query},
|
||||
/// utils::parsec::Parser,
|
||||
/// };
|
||||
///
|
||||
/// let input = "test";
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use crate::parsec::*;
|
||||
use crate::utils::parsec::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RuleBlock(pub Vec<Rule>);
|
||||
|
@ -683,7 +683,7 @@ mod test {
|
|||
parser::*, ActionCommand::*, AddressOperator::*, CharacterOperator::*, ConditionRule::*,
|
||||
ControlCommand::*, IntegerOperator::*, MatchOperator::*, Rule::*, RuleBlock,
|
||||
};
|
||||
use crate::parsec::Parser;
|
||||
use crate::utils::parsec::Parser;
|
||||
|
||||
#[test]
|
||||
fn test_sieve_parse_strings() {
|
||||
|
|
|
@ -80,9 +80,9 @@ use smallvec::SmallVec;
|
|||
use smol::{unblock, Async as AsyncWrapper};
|
||||
|
||||
use crate::{
|
||||
connections::{lookup_ipv4, Connection},
|
||||
email::{parser::BytesExt, Address, Envelope},
|
||||
error::{Error, Result, ResultIntoError},
|
||||
utils::connections::{lookup_ipv4, Connection},
|
||||
};
|
||||
|
||||
/// Kind of server security (StartTLS/TLS/None) the client should attempt
|
||||
|
|
|
@ -34,8 +34,8 @@
|
|||
*/
|
||||
|
||||
use crate::{
|
||||
datetime::UnixTimestamp,
|
||||
email::{address::StrBuild, parser::BytesExt, *},
|
||||
UnixTimestamp,
|
||||
};
|
||||
|
||||
mod iterators;
|
||||
|
@ -1674,7 +1674,7 @@ fn save_graph(
|
|||
|
||||
let mut file = File::create(format!(
|
||||
"/tmp/meli/threads/threads_{}.json",
|
||||
crate::datetime::now()
|
||||
crate::utils::datetime::now()
|
||||
))
|
||||
.unwrap();
|
||||
file.write_all(s.as_bytes()).unwrap();
|
||||
|
|
|
@ -275,24 +275,3 @@ pub fn lookup_ipv4(host: &str, port: u16) -> crate::Result<std::net::SocketAddr>
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
use futures::future::{self, Either, Future};
|
||||
|
||||
pub async fn timeout<O>(dur: Option<Duration>, f: impl Future<Output = O>) -> crate::Result<O> {
|
||||
futures::pin_mut!(f);
|
||||
if let Some(dur) = dur {
|
||||
match future::select(f, smol::Timer::after(dur)).await {
|
||||
Either::Left((out, _)) => Ok(out),
|
||||
Either::Right(_) => {
|
||||
Err(crate::error::Error::new("Timed out.")
|
||||
.set_kind(crate::error::ErrorKind::Timeout))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(f.await)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn sleep(dur: Duration) {
|
||||
smol::Timer::after(dur).await;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* meli - melib library
|
||||
*
|
||||
* Copyright 2020 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/>.
|
||||
*/
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::future::{self, Either, Future};
|
||||
|
||||
pub async fn timeout<O>(dur: Option<Duration>, f: impl Future<Output = O>) -> crate::Result<O> {
|
||||
futures::pin_mut!(f);
|
||||
if let Some(dur) = dur {
|
||||
match future::select(f, smol::Timer::after(dur)).await {
|
||||
Either::Left((out, _)) => Ok(out),
|
||||
Either::Right(_) => {
|
||||
Err(crate::error::Error::new("Timed out.")
|
||||
.set_kind(crate::error::ErrorKind::Timeout))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(f.await)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn sleep(dur: Duration) {
|
||||
smol::Timer::after(dur).await;
|
||||
}
|
|
@ -220,7 +220,7 @@ impl StderrLogger {
|
|||
|
||||
#[cfg(not(test))]
|
||||
pub fn change_log_dest(&mut self, path: PathBuf) {
|
||||
use crate::shellexpand::ShellExpandTrait;
|
||||
use crate::utils::shellexpand::ShellExpandTrait;
|
||||
|
||||
let path = path.expand(); // expand shell stuff
|
||||
let mut dest = self.dest.lock().unwrap();
|
||||
|
@ -254,7 +254,7 @@ impl Log for StderrLogger {
|
|||
) -> Option<()> {
|
||||
writer
|
||||
.write_all(
|
||||
crate::datetime::timestamp_to_string(crate::datetime::now(), None, false)
|
||||
super::datetime::timestamp_to_string(super::datetime::now(), None, false)
|
||||
.as_bytes(),
|
||||
)
|
||||
.ok()?;
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
|
||||
pub mod connections;
|
||||
pub mod datetime;
|
||||
pub mod futures;
|
||||
pub mod random;
|
||||
#[macro_use]
|
||||
pub mod logging;
|
||||
pub mod parsec;
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::datetime::{parse_timestamp_from_string, UnixTimestamp};
|
||||
use crate::utils::datetime::{parse_timestamp_from_string, UnixTimestamp};
|
||||
|
||||
pub type Result<'a, Output> = std::result::Result<(&'a str, Output), &'a str>;
|
||||
|
||||
|
@ -445,8 +445,8 @@ pub fn is_not<'a>(slice: &'static [u8]) -> impl Parser<'a, &'a str> {
|
|||
/// Try alternative parsers in order until one succeeds.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use melib::parsec::{Parser, quoted_slice, match_literal, alt, delimited, prefix};
|
||||
///
|
||||
/// # use melib::utils::parsec::{Parser, quoted_slice, match_literal, alt, delimited, prefix};
|
||||
/// #
|
||||
/// let parser = |input| {
|
||||
/// alt([
|
||||
/// delimited(match_literal("{"), quoted_slice(), match_literal("}")),
|
||||
|
@ -456,9 +456,8 @@ pub fn is_not<'a>(slice: &'static [u8]) -> impl Parser<'a, &'a str> {
|
|||
/// };
|
||||
///
|
||||
/// let input1: &str = "{\"quoted\"}";
|
||||
/// let input2: &str = "[\"quoted\"]";
|
||||
/// assert_eq!(Ok(("", "quoted")), parser.parse(input1));
|
||||
///
|
||||
/// let input2: &str = "[\"quoted\"]";
|
||||
/// assert_eq!(Ok(("", "quoted")), parser.parse(input2));
|
||||
/// ```
|
||||
pub fn alt<'a, P, A, const N: usize>(parsers: [P; N]) -> impl Parser<'a, A>
|
||||
|
@ -584,7 +583,7 @@ pub fn take<'a>(count: usize) -> impl Parser<'a, &'a str> {
|
|||
///
|
||||
///```rust
|
||||
/// # use std::str::FromStr;
|
||||
/// # use melib::parsec::{Parser, delimited, match_literal, map_res, is_a, take_literal};
|
||||
/// # use melib::utils::parsec::{Parser, delimited, match_literal, map_res, is_a, take_literal};
|
||||
/// let lit: &str = "{31}\r\nThere is no script by that name\r\n";
|
||||
/// assert_eq!(
|
||||
/// take_literal(delimited(
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* meli - melib crate.
|
||||
*
|
||||
* Copyright 2017-2020 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/>.
|
||||
*/
|
||||
|
||||
use std::{fs::File, io::prelude::*, time::SystemTime};
|
||||
|
||||
const EXPECT: &str = "Could not open/read /dev/urandom";
|
||||
|
||||
pub fn random_u64() -> u64 {
|
||||
let mut f = File::open("/dev/urandom").expect(EXPECT);
|
||||
let mut buffer = [0; 8];
|
||||
|
||||
// read exactly 8 bytes
|
||||
f.read_exact(&mut buffer).expect(EXPECT);
|
||||
|
||||
u64::from(buffer[0])
|
||||
| (u64::from(buffer[1]) << 8)
|
||||
| (u64::from(buffer[2]) << 16)
|
||||
| (u64::from(buffer[3]) << 24)
|
||||
| (u64::from(buffer[4]) << 32)
|
||||
| (u64::from(buffer[5]) << 40)
|
||||
| (u64::from(buffer[6]) << 48)
|
||||
| (u64::from(buffer[7]) << 56)
|
||||
}
|
||||
|
||||
pub fn random_u32() -> u32 {
|
||||
let mut f = File::open("/dev/urandom").expect(EXPECT);
|
||||
let mut buffer = [0; 4];
|
||||
|
||||
// read exactly 4 bytes
|
||||
f.read_exact(&mut buffer).expect(EXPECT);
|
||||
|
||||
u32::from(buffer[0])
|
||||
| (u32::from(buffer[1]) << 8)
|
||||
| (u32::from(buffer[2]) << 16)
|
||||
| (u32::from(buffer[3]) << 24)
|
||||
}
|
||||
|
||||
pub fn random_u8() -> u8 {
|
||||
let mut f = File::open("/dev/urandom").expect(EXPECT);
|
||||
let mut buffer = [0; 1];
|
||||
|
||||
// read exactly 1 byte
|
||||
f.read_exact(&mut buffer).expect(EXPECT);
|
||||
|
||||
buffer[0]
|
||||
}
|
||||
|
||||
pub fn clock() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
}
|
208
src/command.rs
208
src/command.rs
|
@ -1005,107 +1005,6 @@ pub fn parse_command(input: &[u8]) -> Result<Action, Error> {
|
|||
.map_err(|err| err.into())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
let mut input = "sort".to_string();
|
||||
macro_rules! match_input {
|
||||
($input:expr) => {{
|
||||
let mut sugg = Default::default();
|
||||
let mut vec = vec![];
|
||||
//print!("{}", $input);
|
||||
for (_tags, _desc, tokens) in COMMAND_COMPLETION.iter() {
|
||||
//println!("{:?}, {:?}, {:?}", _tags, _desc, tokens);
|
||||
let m = tokens.matches(&mut $input.as_str(), &mut sugg);
|
||||
if !m.is_empty() {
|
||||
vec.push(tokens);
|
||||
//print!("{:?} ", desc);
|
||||
//println!(" result = {:#?}\n\n", m);
|
||||
}
|
||||
}
|
||||
//println!("suggestions = {:#?}", sugg);
|
||||
sugg.into_iter()
|
||||
.map(|s| format!("{}{}", $input.as_str(), s.as_str()))
|
||||
.collect::<HashSet<String>>()
|
||||
}};
|
||||
}
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter(["sort date".to_string(), "sort subject".to_string()]).collect(),
|
||||
);
|
||||
input = "so".to_string();
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter(["sort".to_string()]).collect(),
|
||||
);
|
||||
input = "so ".to_string();
|
||||
assert_eq!(&match_input!(input), &HashSet::default(),);
|
||||
input = "to".to_string();
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter(["toggle".to_string()]).collect(),
|
||||
);
|
||||
input = "toggle ".to_string();
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter([
|
||||
"toggle mouse".to_string(),
|
||||
"toggle sign".to_string(),
|
||||
"toggle encrypt".to_string(),
|
||||
"toggle thread_snooze".to_string()
|
||||
])
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_parser_interactive() {
|
||||
use std::io;
|
||||
let mut input = String::new();
|
||||
loop {
|
||||
input.clear();
|
||||
print!("> ");
|
||||
match io::stdin().read_line(&mut input) {
|
||||
Ok(_n) => {
|
||||
println!("Input is {:?}", input.as_str().trim());
|
||||
let mut sugg = Default::default();
|
||||
let mut vec = vec![];
|
||||
//print!("{}", input);
|
||||
for (_tags, _desc, tokens) in COMMAND_COMPLETION.iter() {
|
||||
//println!("{:?}, {:?}, {:?}", _tags, _desc, tokens);
|
||||
let m = tokens.matches(&mut input.as_str().trim(), &mut sugg);
|
||||
if !m.is_empty() {
|
||||
vec.push(tokens);
|
||||
//print!("{:?} ", desc);
|
||||
//println!(" result = {:#?}\n\n", m);
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"suggestions = {:#?}",
|
||||
sugg.into_iter()
|
||||
.zip(vec.into_iter())
|
||||
.map(|(s, v)| format!(
|
||||
"{}{} {:?}",
|
||||
input.as_str().trim(),
|
||||
if input.trim().is_empty() {
|
||||
s.trim()
|
||||
} else {
|
||||
s.as_str()
|
||||
},
|
||||
v
|
||||
))
|
||||
.collect::<Vec<String>>()
|
||||
);
|
||||
if input.trim() == "quit" {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(error) => println!("error: {}", error),
|
||||
}
|
||||
}
|
||||
println!("alright");
|
||||
}
|
||||
|
||||
/// Get command suggestions for input
|
||||
pub fn command_completion_suggestions(input: &str) -> Vec<String> {
|
||||
use crate::melib::ShellExpandTrait;
|
||||
|
@ -1124,3 +1023,110 @@ pub fn command_completion_suggestions(input: &str) -> Vec<String> {
|
|||
.map(|s| format!("{}{}", input, s.as_str()))
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_command_parser() {
|
||||
let mut input = "sort".to_string();
|
||||
macro_rules! match_input {
|
||||
($input:expr) => {{
|
||||
let mut sugg = Default::default();
|
||||
let mut vec = vec![];
|
||||
//print!("{}", $input);
|
||||
for (_tags, _desc, tokens) in COMMAND_COMPLETION.iter() {
|
||||
//println!("{:?}, {:?}, {:?}", _tags, _desc, tokens);
|
||||
let m = tokens.matches(&mut $input.as_str(), &mut sugg);
|
||||
if !m.is_empty() {
|
||||
vec.push(tokens);
|
||||
//print!("{:?} ", desc);
|
||||
//println!(" result = {:#?}\n\n", m);
|
||||
}
|
||||
}
|
||||
//println!("suggestions = {:#?}", sugg);
|
||||
sugg.into_iter()
|
||||
.map(|s| format!("{}{}", $input.as_str(), s.as_str()))
|
||||
.collect::<HashSet<String>>()
|
||||
}};
|
||||
}
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter(["sort date".to_string(), "sort subject".to_string()])
|
||||
.collect(),
|
||||
);
|
||||
input = "so".to_string();
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter(["sort".to_string()]).collect(),
|
||||
);
|
||||
input = "so ".to_string();
|
||||
assert_eq!(&match_input!(input), &HashSet::default(),);
|
||||
input = "to".to_string();
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter(["toggle".to_string()]).collect(),
|
||||
);
|
||||
input = "toggle ".to_string();
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter([
|
||||
"toggle mouse".to_string(),
|
||||
"toggle sign".to_string(),
|
||||
"toggle encrypt".to_string(),
|
||||
"toggle thread_snooze".to_string()
|
||||
])
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_parser_interactive() {
|
||||
use std::io;
|
||||
let mut input = String::new();
|
||||
loop {
|
||||
input.clear();
|
||||
print!("> ");
|
||||
match io::stdin().read_line(&mut input) {
|
||||
Ok(_n) => {
|
||||
println!("Input is {:?}", input.as_str().trim());
|
||||
let mut sugg = Default::default();
|
||||
let mut vec = vec![];
|
||||
//print!("{}", input);
|
||||
for (_tags, _desc, tokens) in COMMAND_COMPLETION.iter() {
|
||||
//println!("{:?}, {:?}, {:?}", _tags, _desc, tokens);
|
||||
let m = tokens.matches(&mut input.as_str().trim(), &mut sugg);
|
||||
if !m.is_empty() {
|
||||
vec.push(tokens);
|
||||
//print!("{:?} ", desc);
|
||||
//println!(" result = {:#?}\n\n", m);
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"suggestions = {:#?}",
|
||||
sugg.into_iter()
|
||||
.zip(vec.into_iter())
|
||||
.map(|(s, v)| format!(
|
||||
"{}{} {:?}",
|
||||
input.as_str().trim(),
|
||||
if input.trim().is_empty() {
|
||||
s.trim()
|
||||
} else {
|
||||
s.as_str()
|
||||
},
|
||||
v
|
||||
))
|
||||
.collect::<Vec<String>>()
|
||||
);
|
||||
if input.trim() == "quit" {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(error) => println!("error: {}", error),
|
||||
}
|
||||
}
|
||||
println!("alright");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2448,7 +2448,7 @@ fn attribution_string(
|
|||
.map(|addr| addr.get_email())
|
||||
.unwrap_or_else(|| "\"\"".to_string()),
|
||||
);
|
||||
melib::datetime::timestamp_to_string(date, Some(fmt.as_str()), posix)
|
||||
melib::utils::datetime::timestamp_to_string(date, Some(fmt.as_str()), posix)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
//! Pre-submission hooks for draft validation and/or transformations.
|
||||
pub use std::borrow::Cow;
|
||||
|
||||
use melib::email::headers::HeaderName;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub enum HookFn {
|
||||
|
@ -153,10 +155,10 @@ impl std::ops::DerefMut for Hook {
|
|||
}
|
||||
|
||||
fn past_date_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
|
||||
use melib::datetime::*;
|
||||
use melib::utils::datetime::*;
|
||||
if let Some(v) = draft
|
||||
.headers
|
||||
.get("Date")
|
||||
.get(HeaderName::DATE)
|
||||
.map(rfc822_to_timestamp)
|
||||
.and_then(Result::ok)
|
||||
{
|
||||
|
@ -181,8 +183,8 @@ pub const PASTDATEWARN: Hook = Hook {
|
|||
};
|
||||
|
||||
fn important_header_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
|
||||
for hdr in ["From", "To"] {
|
||||
match draft.headers.get(hdr).map(melib::Address::list_try_from) {
|
||||
for hdr in [HeaderName::FROM, HeaderName::TO] {
|
||||
match draft.headers.get(&hdr).map(melib::Address::list_try_from) {
|
||||
Some(Ok(_)) => {}
|
||||
Some(Err(err)) => return Err(format!("{hdr} header value is invalid ({err}).").into()),
|
||||
None => return Err(format!("{hdr} header is missing and should be present.").into()),
|
||||
|
@ -192,8 +194,8 @@ fn important_header_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
|
|||
{
|
||||
match draft
|
||||
.headers
|
||||
.get("Date")
|
||||
.map(melib::datetime::rfc822_to_timestamp)
|
||||
.get(HeaderName::DATE)
|
||||
.map(melib::utils::datetime::rfc822_to_timestamp)
|
||||
{
|
||||
Some(Err(err)) => return Err(format!("Date header value is invalid ({err}).").into()),
|
||||
Some(Ok(0)) => return Err("Date header value is invalid.".into()),
|
||||
|
@ -201,10 +203,10 @@ fn important_header_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
for hdr in ["Cc", "Bcc"] {
|
||||
for hdr in [HeaderName::CC, HeaderName::BCC] {
|
||||
if let Some(Err(err)) = draft
|
||||
.headers
|
||||
.get(hdr)
|
||||
.get(&hdr)
|
||||
.filter(|v| !v.trim().is_empty())
|
||||
.map(melib::Address::list_try_from)
|
||||
{
|
||||
|
@ -223,7 +225,7 @@ pub const HEADERWARN: Hook = Hook {
|
|||
fn missing_attachment_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
|
||||
if draft
|
||||
.headers
|
||||
.get("Subject")
|
||||
.get(HeaderName::SUBJECT)
|
||||
.map(|s| s.to_lowercase().contains("attach"))
|
||||
.unwrap_or(false)
|
||||
&& draft.attachments.is_empty()
|
||||
|
@ -247,7 +249,7 @@ pub const MISSINGATTACHMENTWARN: Hook = Hook {
|
|||
fn empty_draft_warn(_ctx: &mut Context, draft: &mut Draft) -> Result<()> {
|
||||
if draft
|
||||
.headers
|
||||
.get("Subject")
|
||||
.get(HeaderName::SUBJECT)
|
||||
.filter(|v| !v.trim().is_empty())
|
||||
.is_none()
|
||||
&& draft.body.trim().is_empty()
|
||||
|
|
|
@ -764,7 +764,7 @@ impl ConversationsListing {
|
|||
n / (24 * 60 * 60),
|
||||
if n / (24 * 60 * 60) == 1 { "" } else { "s" }
|
||||
),
|
||||
_ => melib::datetime::timestamp_to_string(
|
||||
_ => melib::utils::datetime::timestamp_to_string(
|
||||
epoch,
|
||||
context
|
||||
.settings
|
||||
|
|
|
@ -992,7 +992,7 @@ impl PlainListing {
|
|||
n if n < 4 * 24 * 60 * 60 => {
|
||||
format!("{} days ago{}", n / (24 * 60 * 60), " ".repeat(9))
|
||||
}
|
||||
_ => melib::datetime::timestamp_to_string(envelope.datetime(), None, false),
|
||||
_ => melib::utils::datetime::timestamp_to_string(envelope.datetime(), None, false),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,8 +28,8 @@ use std::{
|
|||
};
|
||||
|
||||
use melib::{
|
||||
datetime, email::attachment_types::ContentType, list_management, mailto::Mailto,
|
||||
parser::BytesExt, Card, Draft, HeaderName, SpecialUsageMailbox,
|
||||
email::attachment_types::ContentType, list_management, mailto::Mailto, parser::BytesExt,
|
||||
utils::datetime, Card, Draft, HeaderName, SpecialUsageMailbox,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
|
|
|
@ -401,8 +401,8 @@ impl Component for SVGScreenshotFilter {
|
|||
}
|
||||
res.push(b);
|
||||
}
|
||||
let mut filename = melib::datetime::timestamp_to_string(
|
||||
melib::datetime::now(),
|
||||
let mut filename = melib::utils::datetime::timestamp_to_string(
|
||||
melib::utils::datetime::now(),
|
||||
Some("meli Screenshot - %e %h %Y %H:%M:%S.svg"),
|
||||
true,
|
||||
);
|
||||
|
|
|
@ -906,7 +906,7 @@ mod pp {
|
|||
|
||||
use melib::{
|
||||
error::{Error, Result},
|
||||
parsec::*,
|
||||
utils::parsec::*,
|
||||
ShellExpandTrait,
|
||||
};
|
||||
|
||||
|
|
|
@ -77,6 +77,15 @@ macro_rules! try_recv_timeout {
|
|||
}};
|
||||
}
|
||||
|
||||
macro_rules! is_variant {
|
||||
($n:ident, $($var:tt)+) => {
|
||||
#[inline]
|
||||
pub fn $n(&self) -> bool {
|
||||
matches!(self, Self::$($var)*)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub enum MailboxStatus {
|
||||
Available,
|
||||
|
@ -88,13 +97,8 @@ pub enum MailboxStatus {
|
|||
}
|
||||
|
||||
impl MailboxStatus {
|
||||
pub fn is_available(&self) -> bool {
|
||||
matches!(self, MailboxStatus::Available)
|
||||
}
|
||||
|
||||
pub fn is_parsing(&self) -> bool {
|
||||
matches!(self, MailboxStatus::Parsing(_, _))
|
||||
}
|
||||
is_variant! { is_available, Available }
|
||||
is_variant! { is_parsing, Parsing(_, _) }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -34,8 +34,8 @@ use melib::{
|
|||
escape_double_quote,
|
||||
Query::{self, *},
|
||||
},
|
||||
sqlite3::{self as melib_sqlite3, rusqlite::params, DatabaseDescription},
|
||||
thread::{SortField, SortOrder},
|
||||
utils::sqlite3::{self as melib_sqlite3, rusqlite::params, DatabaseDescription},
|
||||
Error, Result,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
@ -497,7 +497,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_query_to_sql() {
|
||||
use melib::{parsec::Parser, search::query};
|
||||
use melib::{search::query, utils::parsec::Parser};
|
||||
assert_eq!(
|
||||
"(subject LIKE \"%test%\" ) AND (body_text LIKE \"%i%\" ) ",
|
||||
&query_to_sql(&query().parse_complete("subject:test and i").unwrap().1)
|
||||
|
|
19
src/state.rs
19
src/state.rs
|
@ -40,6 +40,7 @@ use crossbeam::channel::{unbounded, Receiver, Sender};
|
|||
use indexmap::{IndexMap, IndexSet};
|
||||
use melib::{
|
||||
backends::{AccountHash, BackendEvent, BackendEventConsumer, Backends, RefreshEvent},
|
||||
utils::datetime,
|
||||
UnixTimestamp,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
@ -542,7 +543,7 @@ impl State {
|
|||
let mut areas: smallvec::SmallVec<[Area; 8]> =
|
||||
self.context.dirty_areas.drain(0..).collect();
|
||||
if self.display_messages_active {
|
||||
let now = melib::datetime::now();
|
||||
let now = datetime::now();
|
||||
if self
|
||||
.display_messages_expiration_start
|
||||
.map(|t| t + 5 < now)
|
||||
|
@ -687,9 +688,11 @@ impl State {
|
|||
}
|
||||
}
|
||||
let ((x, mut y), box_displ_area_bottom_right) = box_displ_area;
|
||||
for line in msg_lines.into_iter().chain(Some(String::new())).chain(Some(
|
||||
melib::datetime::timestamp_to_string(*timestamp, None, false),
|
||||
)) {
|
||||
for line in msg_lines
|
||||
.into_iter()
|
||||
.chain(Some(String::new()))
|
||||
.chain(Some(datetime::timestamp_to_string(*timestamp, None, false)))
|
||||
{
|
||||
write_string_to_grid(
|
||||
&line,
|
||||
&mut self.screen.overlay_grid,
|
||||
|
@ -1016,7 +1019,7 @@ impl State {
|
|||
pub fn rcv_event(&mut self, mut event: UIEvent) {
|
||||
if let UIEvent::Input(_) = event {
|
||||
if self.display_messages_expiration_start.is_none() {
|
||||
self.display_messages_expiration_start = Some(melib::datetime::now());
|
||||
self.display_messages_expiration_start = Some(datetime::now());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1171,7 +1174,7 @@ impl State {
|
|||
.general
|
||||
.info_message_previous =>
|
||||
{
|
||||
self.display_messages_expiration_start = Some(melib::datetime::now());
|
||||
self.display_messages_expiration_start = Some(datetime::now());
|
||||
self.display_messages_active = true;
|
||||
self.display_messages_initialised = false;
|
||||
self.display_messages_dirty = true;
|
||||
|
@ -1181,7 +1184,7 @@ impl State {
|
|||
UIEvent::Input(ref key)
|
||||
if *key == self.context.settings.shortcuts.general.info_message_next =>
|
||||
{
|
||||
self.display_messages_expiration_start = Some(melib::datetime::now());
|
||||
self.display_messages_expiration_start = Some(datetime::now());
|
||||
self.display_messages_active = true;
|
||||
self.display_messages_initialised = false;
|
||||
self.display_messages_dirty = true;
|
||||
|
@ -1193,7 +1196,7 @@ impl State {
|
|||
}
|
||||
UIEvent::StatusEvent(StatusEvent::DisplayMessage(ref msg)) => {
|
||||
self.display_messages.push(DisplayMessage {
|
||||
timestamp: melib::datetime::now(),
|
||||
timestamp: datetime::now(),
|
||||
msg: msg.clone(),
|
||||
});
|
||||
self.display_messages_active = true;
|
||||
|
|
Loading…
Reference in New Issue