mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-25 12:30:22 +00:00
extract account and config from cli to lib (#340)
This commit is contained in:
parent
0e98def513
commit
3f5feed0ff
27 changed files with 219 additions and 103 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -441,6 +441,7 @@ dependencies = [
|
||||||
"convert_case",
|
"convert_case",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"erased-serde",
|
"erased-serde",
|
||||||
|
"himalaya-lib",
|
||||||
"html-escape",
|
"html-escape",
|
||||||
"imap",
|
"imap",
|
||||||
"imap-proto",
|
"imap-proto",
|
||||||
|
@ -468,6 +469,20 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "himalaya-lib"
|
name = "himalaya-lib"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"imap",
|
||||||
|
"imap-proto",
|
||||||
|
"lettre",
|
||||||
|
"log",
|
||||||
|
"maildir",
|
||||||
|
"mailparse",
|
||||||
|
"md5",
|
||||||
|
"notmuch",
|
||||||
|
"serde",
|
||||||
|
"shellexpand",
|
||||||
|
"thiserror",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hostname"
|
name = "hostname"
|
||||||
|
@ -1365,6 +1380,26 @@ dependencies = [
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
|
|
|
@ -31,6 +31,7 @@ clap = { version = "2.33.3", default-features = false, features = ["suggestions"
|
||||||
convert_case = "0.5.0"
|
convert_case = "0.5.0"
|
||||||
env_logger = "0.8.3"
|
env_logger = "0.8.3"
|
||||||
erased-serde = "0.3.18"
|
erased-serde = "0.3.18"
|
||||||
|
himalaya-lib = { path = "../lib" }
|
||||||
html-escape = "0.2.9"
|
html-escape = "0.2.9"
|
||||||
lettre = { version = "0.10.0-rc.1", features = ["serde"] }
|
lettre = { version = "0.10.0-rc.1", features = ["serde"] }
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
//! This module contains the definition of the IMAP backend.
|
//! This module contains the definition of the IMAP backend.
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use himalaya_lib::account::{AccountConfig, ImapBackendConfig};
|
||||||
use log::{debug, log_enabled, trace, Level};
|
use log::{debug, log_enabled, trace, Level};
|
||||||
use native_tls::{TlsConnector, TlsStream};
|
use native_tls::{TlsConnector, TlsStream};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -16,7 +17,6 @@ use crate::{
|
||||||
backends::{
|
backends::{
|
||||||
imap::msg_sort_criterion::SortCriteria, Backend, ImapEnvelope, ImapEnvelopes, ImapMboxes,
|
imap::msg_sort_criterion::SortCriteria, Backend, ImapEnvelope, ImapEnvelopes, ImapMboxes,
|
||||||
},
|
},
|
||||||
config::{AccountConfig, ImapBackendConfig},
|
|
||||||
mbox::Mboxes,
|
mbox::Mboxes,
|
||||||
msg::{Envelopes, Msg},
|
msg::{Envelopes, Msg},
|
||||||
output::run_cmd,
|
output::run_cmd,
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
//! traits implementation.
|
//! traits implementation.
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use himalaya_lib::account::{AccountConfig, MaildirBackendConfig};
|
||||||
use log::{debug, info, trace};
|
use log::{debug, info, trace};
|
||||||
use std::{convert::TryInto, env, fs, path::PathBuf};
|
use std::{convert::TryInto, env, fs, path::PathBuf};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backends::{Backend, IdMapper, MaildirEnvelopes, MaildirFlags, MaildirMboxes},
|
backends::{Backend, IdMapper, MaildirEnvelopes, MaildirFlags, MaildirMboxes},
|
||||||
config::{AccountConfig, MaildirBackendConfig},
|
|
||||||
mbox::Mboxes,
|
mbox::Mboxes,
|
||||||
msg::{Envelopes, Msg},
|
msg::{Envelopes, Msg},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use std::{convert::TryInto, fs};
|
use std::{convert::TryInto, fs};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use himalaya_lib::account::{AccountConfig, NotmuchBackendConfig};
|
||||||
use log::{debug, info, trace};
|
use log::{debug, info, trace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backends::{Backend, IdMapper, MaildirBackend, NotmuchEnvelopes, NotmuchMbox, NotmuchMboxes},
|
backends::{Backend, IdMapper, MaildirBackend, NotmuchEnvelopes, NotmuchMbox, NotmuchMboxes},
|
||||||
config::{AccountConfig, NotmuchBackendConfig},
|
|
||||||
mbox::Mboxes,
|
mbox::Mboxes,
|
||||||
msg::{Envelopes, Msg},
|
msg::{Envelopes, Msg},
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,8 +12,9 @@ use std::{
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use himalaya_lib::account::DeserializedAccountConfig;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::DeserializedAccountConfig,
|
|
||||||
output::{PrintTable, PrintTableOpts, WriteColor},
|
output::{PrintTable, PrintTableOpts, WriteColor},
|
||||||
ui::{Cell, Row, Table},
|
ui::{Cell, Row, Table},
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,10 +3,11 @@
|
||||||
//! This module gathers all account actions triggered by the CLI.
|
//! This module gathers all account actions triggered by the CLI.
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use himalaya_lib::account::{AccountConfig, DeserializedConfig};
|
||||||
use log::{info, trace};
|
use log::{info, trace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{AccountConfig, Accounts, DeserializedConfig},
|
config::Accounts,
|
||||||
output::{PrintTableOpts, PrinterService},
|
output::{PrintTableOpts, PrinterService},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -36,13 +37,13 @@ pub fn list<'a, P: PrinterService>(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use himalaya_lib::account::{
|
||||||
|
AccountConfig, DeserializedAccountConfig, DeserializedConfig, DeserializedImapAccountConfig,
|
||||||
|
};
|
||||||
use std::{collections::HashMap, fmt::Debug, io, iter::FromIterator};
|
use std::{collections::HashMap, fmt::Debug, io, iter::FromIterator};
|
||||||
use termcolor::ColorSpec;
|
use termcolor::ColorSpec;
|
||||||
|
|
||||||
use crate::{
|
use crate::output::{Print, PrintTable, WriteColor};
|
||||||
config::{DeserializedAccountConfig, DeserializedImapAccountConfig},
|
|
||||||
output::{Print, PrintTable, WriteColor},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
|
@ -107,12 +107,6 @@ pub mod smtp {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod config {
|
pub mod config {
|
||||||
pub mod deserialized_config;
|
|
||||||
pub use deserialized_config::*;
|
|
||||||
|
|
||||||
pub mod deserialized_account_config;
|
|
||||||
pub use deserialized_account_config::*;
|
|
||||||
|
|
||||||
pub mod config_args;
|
pub mod config_args;
|
||||||
|
|
||||||
pub mod account_args;
|
pub mod account_args;
|
||||||
|
@ -120,15 +114,6 @@ pub mod config {
|
||||||
|
|
||||||
pub mod account;
|
pub mod account;
|
||||||
pub use account::*;
|
pub use account::*;
|
||||||
|
|
||||||
pub mod account_config;
|
|
||||||
pub use account_config::*;
|
|
||||||
|
|
||||||
pub mod format;
|
|
||||||
pub use format::*;
|
|
||||||
|
|
||||||
pub mod hooks;
|
|
||||||
pub use hooks::*;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod compl;
|
pub mod compl;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use himalaya_lib::account::{
|
||||||
|
AccountConfig, BackendConfig, DeserializedConfig, DEFAULT_INBOX_FOLDER,
|
||||||
|
};
|
||||||
use std::{convert::TryFrom, env};
|
use std::{convert::TryFrom, env};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use himalaya::{
|
use himalaya::{
|
||||||
backends::Backend,
|
backends::Backend,
|
||||||
compl::{compl_args, compl_handlers},
|
compl::{compl_args, compl_handlers},
|
||||||
config::{
|
config::{account_args, account_handlers, config_args},
|
||||||
account_args, account_handlers, config_args, AccountConfig, BackendConfig,
|
|
||||||
DeserializedConfig, DEFAULT_INBOX_FOLDER,
|
|
||||||
},
|
|
||||||
mbox::{mbox_args, mbox_handlers},
|
mbox::{mbox_args, mbox_handlers},
|
||||||
msg::{flag_args, flag_handlers, msg_args, msg_handlers, tpl_args, tpl_handlers},
|
msg::{flag_args, flag_handlers, msg_args, msg_handlers, tpl_args, tpl_handlers},
|
||||||
output::{output_args, OutputFmt, StdoutPrinter},
|
output::{output_args, OutputFmt, StdoutPrinter},
|
||||||
|
@ -22,7 +22,9 @@ use himalaya::backends::{imap_args, imap_handlers, ImapBackend};
|
||||||
use himalaya::backends::MaildirBackend;
|
use himalaya::backends::MaildirBackend;
|
||||||
|
|
||||||
#[cfg(feature = "notmuch-backend")]
|
#[cfg(feature = "notmuch-backend")]
|
||||||
use himalaya::{backends::NotmuchBackend, config::MaildirBackendConfig};
|
use himalaya::backends::NotmuchBackend;
|
||||||
|
#[cfg(feature = "notmuch-backend")]
|
||||||
|
use himalaya_lib::account::MaildirBackendConfig;
|
||||||
|
|
||||||
fn create_app<'a>() -> clap::App<'a, 'a> {
|
fn create_app<'a>() -> clap::App<'a, 'a> {
|
||||||
let app = clap::App::new(env!("CARGO_PKG_NAME"))
|
let app = clap::App::new(env!("CARGO_PKG_NAME"))
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
//! This module gathers all mailbox actions triggered by the CLI.
|
//! This module gathers all mailbox actions triggered by the CLI.
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use himalaya_lib::account::AccountConfig;
|
||||||
use log::{info, trace};
|
use log::{info, trace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backends::Backend,
|
backends::Backend,
|
||||||
config::AccountConfig,
|
|
||||||
output::{PrintTableOpts, PrinterService},
|
output::{PrintTableOpts, PrinterService},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,9 @@ use ammonia;
|
||||||
use anyhow::{anyhow, Context, Error, Result};
|
use anyhow::{anyhow, Context, Error, Result};
|
||||||
use chrono::{DateTime, Local, TimeZone, Utc};
|
use chrono::{DateTime, Local, TimeZone, Utc};
|
||||||
use convert_case::{Case, Casing};
|
use convert_case::{Case, Casing};
|
||||||
|
use himalaya_lib::account::{
|
||||||
|
AccountConfig, DEFAULT_DRAFT_FOLDER, DEFAULT_SENT_FOLDER, DEFAULT_SIG_DELIM,
|
||||||
|
};
|
||||||
use html_escape;
|
use html_escape;
|
||||||
use lettre::message::{header::ContentType, Attachment, MultiPart, SinglePart};
|
use lettre::message::{header::ContentType, Attachment, MultiPart, SinglePart};
|
||||||
use log::{info, trace, warn};
|
use log::{info, trace, warn};
|
||||||
|
@ -18,7 +21,6 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backends::Backend,
|
backends::Backend,
|
||||||
config::{AccountConfig, DEFAULT_DRAFT_FOLDER, DEFAULT_SENT_FOLDER, DEFAULT_SIG_DELIM},
|
|
||||||
msg::{
|
msg::{
|
||||||
from_addrs_to_sendable_addrs, from_addrs_to_sendable_mbox, from_slice_to_addrs, msg_utils,
|
from_addrs_to_sendable_addrs, from_addrs_to_sendable_mbox, from_slice_to_addrs, msg_utils,
|
||||||
Addr, Addrs, BinaryPart, Part, Parts, TextPlainPart, TplOverride,
|
Addr, Addrs, BinaryPart, Part, Parts, TextPlainPart, TplOverride,
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use atty::Stream;
|
use atty::Stream;
|
||||||
|
use himalaya_lib::account::{AccountConfig, DEFAULT_SENT_FOLDER};
|
||||||
use log::{debug, info, trace};
|
use log::{debug, info, trace};
|
||||||
use mailparse::addrparse;
|
use mailparse::addrparse;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -15,7 +16,6 @@ use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backends::Backend,
|
backends::Backend,
|
||||||
config::{AccountConfig, DEFAULT_SENT_FOLDER},
|
|
||||||
msg::{Msg, Part, Parts, TextPlainPart},
|
msg::{Msg, Part, Parts, TextPlainPart},
|
||||||
output::{PrintTableOpts, PrinterService},
|
output::{PrintTableOpts, PrinterService},
|
||||||
smtp::SmtpService,
|
smtp::SmtpService,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use himalaya_lib::account::AccountConfig;
|
||||||
use mailparse::MailHeaderMap;
|
use mailparse::MailHeaderMap;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -7,8 +8,6 @@ use std::{
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::config::AccountConfig;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize)]
|
#[derive(Debug, Clone, Default, Serialize)]
|
||||||
pub struct TextPlainPart {
|
pub struct TextPlainPart {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use atty::Stream;
|
use atty::Stream;
|
||||||
|
use himalaya_lib::account::AccountConfig;
|
||||||
use std::io::{self, BufRead};
|
use std::io::{self, BufRead};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backends::Backend,
|
backends::Backend,
|
||||||
config::AccountConfig,
|
|
||||||
msg::{Msg, TplOverride},
|
msg::{Msg, TplOverride},
|
||||||
output::PrinterService,
|
output::PrinterService,
|
||||||
smtp::SmtpService,
|
smtp::SmtpService,
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use himalaya_lib::account::Format;
|
||||||
use std::io;
|
use std::io;
|
||||||
use termcolor::{self, StandardStream};
|
use termcolor::{self, StandardStream};
|
||||||
|
|
||||||
use crate::config::Format;
|
|
||||||
|
|
||||||
pub trait WriteColor: io::Write + termcolor::WriteColor {}
|
pub trait WriteColor: io::Write + termcolor::WriteColor {}
|
||||||
|
|
||||||
impl WriteColor for StandardStream {}
|
impl WriteColor for StandardStream {}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use himalaya_lib::account::AccountConfig;
|
||||||
use lettre::{
|
use lettre::{
|
||||||
self,
|
self,
|
||||||
transport::smtp::{
|
transport::smtp::{
|
||||||
|
@ -9,7 +10,7 @@ use lettre::{
|
||||||
};
|
};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use crate::{config::AccountConfig, msg::Msg, output::pipe_cmd};
|
use crate::{msg::Msg, output::pipe_cmd};
|
||||||
|
|
||||||
pub trait SmtpService {
|
pub trait SmtpService {
|
||||||
fn send(&mut self, account: &AccountConfig, msg: &Msg) -> Result<Vec<u8>>;
|
fn send(&mut self, account: &AccountConfig, msg: &Msg) -> Result<Vec<u8>>;
|
||||||
|
@ -62,7 +63,7 @@ impl SmtpService for LettreService<'_> {
|
||||||
if let Some(cmd) = account.hooks.pre_send.as_deref() {
|
if let Some(cmd) = account.hooks.pre_send.as_deref() {
|
||||||
for cmd in cmd.split('|') {
|
for cmd in cmd.split('|') {
|
||||||
raw_msg = pipe_cmd(cmd.trim(), &raw_msg)
|
raw_msg = pipe_cmd(cmd.trim(), &raw_msg)
|
||||||
.with_context(|| format!("cannot execute pre-send hook {:?}", cmd))?
|
.with_context(|| format!("cannot execute pre-send hook {:?}", cmd))?;
|
||||||
}
|
}
|
||||||
let parsed_mail = mailparse::parse_mail(&raw_msg)?;
|
let parsed_mail = mailparse::parse_mail(&raw_msg)?;
|
||||||
Msg::from_parsed_mail(parsed_mail, account)?.try_into()
|
Msg::from_parsed_mail(parsed_mail, account)?.try_into()
|
||||||
|
|
|
@ -5,15 +5,13 @@
|
||||||
//! [builder design pattern]: https://refactoring.guru/design-patterns/builder
|
//! [builder design pattern]: https://refactoring.guru/design-patterns/builder
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use himalaya_lib::account::Format;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use termcolor::{Color, ColorSpec};
|
use termcolor::{Color, ColorSpec};
|
||||||
use terminal_size;
|
use terminal_size;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
use crate::{
|
use crate::output::{Print, PrintTableOpts, WriteColor};
|
||||||
config::Format,
|
|
||||||
output::{Print, PrintTableOpts, WriteColor},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Defines the default terminal size.
|
/// Defines the default terminal size.
|
||||||
/// This is used when the size cannot be determined by the `terminal_size` crate.
|
/// This is used when the size cannot be determined by the `terminal_size` crate.
|
||||||
|
|
|
@ -3,4 +3,24 @@ name = "himalaya-lib"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
imap-backend = ["imap", "imap-proto"]
|
||||||
|
maildir-backend = ["maildir", "md5"]
|
||||||
|
notmuch-backend = ["notmuch", "maildir-backend"]
|
||||||
|
default = ["imap-backend", "maildir-backend"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
lettre = { version = "0.10.0-rc.1", features = ["serde"] }
|
||||||
|
log = "0.4.14"
|
||||||
|
mailparse = "0.13.6"
|
||||||
|
serde = { version = "1.0.118", features = ["derive"] }
|
||||||
|
shellexpand = "2.1.0"
|
||||||
|
thiserror = "1.0.31"
|
||||||
|
toml = "0.5.8"
|
||||||
|
|
||||||
|
# [optional]
|
||||||
|
imap = { version = "=3.0.0-alpha.4", optional = true }
|
||||||
|
imap-proto = { version = "0.14.3", optional = true }
|
||||||
|
maildir = { version = "0.6.1", optional = true }
|
||||||
|
md5 = { version = "0.7.0", optional = true }
|
||||||
|
notmuch = { version = "0.7.1", optional = true }
|
||||||
|
|
|
@ -1,10 +1,35 @@
|
||||||
use anyhow::{anyhow, Context, Result};
|
|
||||||
use lettre::transport::smtp::authentication::Credentials as SmtpCredentials;
|
use lettre::transport::smtp::authentication::Credentials as SmtpCredentials;
|
||||||
use log::{debug, info, trace};
|
use log::{debug, info, trace};
|
||||||
use mailparse::MailAddr;
|
use mailparse::MailAddr;
|
||||||
use std::{collections::HashMap, env, ffi::OsStr, fs, path::PathBuf};
|
use shellexpand;
|
||||||
|
use std::{collections::HashMap, env, ffi::OsStr, fs, path::PathBuf, result};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{config::*, output::run_cmd};
|
use crate::process::{run_cmd, ProcessError};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum AccountError {
|
||||||
|
#[error("cannot find default account")]
|
||||||
|
FindDefaultAccountError,
|
||||||
|
#[error("cannot find account \"{0}\"")]
|
||||||
|
FindAccountError(String),
|
||||||
|
#[error("cannot shell expand")]
|
||||||
|
ShellExpandError(#[from] shellexpand::LookupError<env::VarError>),
|
||||||
|
#[error("cannot parse account address")]
|
||||||
|
ParseAccountAddressError(#[from] mailparse::MailParseError),
|
||||||
|
#[error("cannot find account address from \"{0}\"")]
|
||||||
|
FindAccountAddressError(String),
|
||||||
|
#[error("cannot parse download file name from \"{0}\"")]
|
||||||
|
ParseDownloadFileNameError(PathBuf),
|
||||||
|
#[error("cannot find password")]
|
||||||
|
FindPasswordError,
|
||||||
|
#[error(transparent)]
|
||||||
|
RunCmdError(#[from] ProcessError),
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result<T> = result::Result<T, AccountError>;
|
||||||
|
|
||||||
/// Represents the user account.
|
/// Represents the user account.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
|
@ -62,7 +87,8 @@ pub struct AccountConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AccountConfig {
|
impl<'a> AccountConfig {
|
||||||
/// tries to create an account from a config and an optional account name.
|
/// Tries to create an account from a config and an optional
|
||||||
|
/// account name.
|
||||||
pub fn from_config_and_opt_account_name(
|
pub fn from_config_and_opt_account_name(
|
||||||
config: &'a DeserializedConfig,
|
config: &'a DeserializedConfig,
|
||||||
account_name: Option<&str>,
|
account_name: Option<&str>,
|
||||||
|
@ -87,12 +113,12 @@ impl<'a> AccountConfig {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|(name, account)| (name.to_owned(), account))
|
.map(|(name, account)| (name.to_owned(), account))
|
||||||
.ok_or_else(|| anyhow!("cannot find default account")),
|
.ok_or_else(|| AccountError::FindDefaultAccountError),
|
||||||
Some(name) => config
|
Some(name) => config
|
||||||
.accounts
|
.accounts
|
||||||
.get(name)
|
.get(name)
|
||||||
.map(|account| (name.to_owned(), account))
|
.map(|account| (name.to_owned(), account))
|
||||||
.ok_or_else(|| anyhow!(r#"cannot find account "{}""#, name)),
|
.ok_or_else(|| AccountError::FindAccountError(name.to_owned())),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
let base_account = account.to_base();
|
let base_account = account.to_base();
|
||||||
|
@ -225,20 +251,19 @@ impl<'a> AccountConfig {
|
||||||
format!("{} <{}>", self.display_name, self.email)
|
format!("{} <{}>", self.display_name, self.email)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(mailparse::addrparse(&addr)
|
Ok(mailparse::addrparse(&addr)?
|
||||||
.context(format!(
|
|
||||||
"cannot parse account address {:?}",
|
|
||||||
self.display_name
|
|
||||||
))?
|
|
||||||
.first()
|
.first()
|
||||||
.ok_or_else(|| anyhow!("cannot parse account address {:?}", self.display_name))?
|
.ok_or_else(|| AccountError::FindAccountAddressError(addr.into()))?
|
||||||
.clone())
|
.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds the user account SMTP credentials.
|
/// Builds the user account SMTP credentials.
|
||||||
pub fn smtp_creds(&self) -> Result<SmtpCredentials> {
|
pub fn smtp_creds(&self) -> Result<SmtpCredentials> {
|
||||||
let passwd = run_cmd(&self.smtp_passwd_cmd).context("cannot run SMTP passwd cmd")?;
|
let passwd = run_cmd(&self.smtp_passwd_cmd)?;
|
||||||
let passwd = passwd.lines().next().context("cannot find password")?;
|
let passwd = passwd
|
||||||
|
.lines()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| AccountError::FindPasswordError)?;
|
||||||
|
|
||||||
Ok(SmtpCredentials::new(
|
Ok(SmtpCredentials::new(
|
||||||
self.smtp_login.to_owned(),
|
self.smtp_login.to_owned(),
|
||||||
|
@ -250,10 +275,7 @@ impl<'a> AccountConfig {
|
||||||
pub fn pgp_encrypt_file(&self, addr: &str, path: PathBuf) -> Result<Option<String>> {
|
pub fn pgp_encrypt_file(&self, addr: &str, path: PathBuf) -> Result<Option<String>> {
|
||||||
if let Some(cmd) = self.pgp_encrypt_cmd.as_ref() {
|
if let Some(cmd) = self.pgp_encrypt_cmd.as_ref() {
|
||||||
let encrypt_file_cmd = format!("{} {} {:?}", cmd, addr, path);
|
let encrypt_file_cmd = format!("{} {} {:?}", cmd, addr, path);
|
||||||
run_cmd(&encrypt_file_cmd).map(Some).context(format!(
|
Ok(run_cmd(&encrypt_file_cmd).map(Some)?)
|
||||||
"cannot run pgp encrypt command {:?}",
|
|
||||||
encrypt_file_cmd
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
@ -263,10 +285,7 @@ impl<'a> AccountConfig {
|
||||||
pub fn pgp_decrypt_file(&self, path: PathBuf) -> Result<Option<String>> {
|
pub fn pgp_decrypt_file(&self, path: PathBuf) -> Result<Option<String>> {
|
||||||
if let Some(cmd) = self.pgp_decrypt_cmd.as_ref() {
|
if let Some(cmd) = self.pgp_decrypt_cmd.as_ref() {
|
||||||
let decrypt_file_cmd = format!("{} {:?}", cmd, path);
|
let decrypt_file_cmd = format!("{} {:?}", cmd, path);
|
||||||
run_cmd(&decrypt_file_cmd).map(Some).context(format!(
|
Ok(run_cmd(&decrypt_file_cmd).map(Some)?)
|
||||||
"cannot run pgp decrypt command {:?}",
|
|
||||||
decrypt_file_cmd
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
@ -276,13 +295,10 @@ impl<'a> AccountConfig {
|
||||||
pub fn get_download_file_path<S: AsRef<str>>(&self, file_name: S) -> Result<PathBuf> {
|
pub fn get_download_file_path<S: AsRef<str>>(&self, file_name: S) -> Result<PathBuf> {
|
||||||
let file_path = self.downloads_dir.join(file_name.as_ref());
|
let file_path = self.downloads_dir.join(file_name.as_ref());
|
||||||
self.get_unique_download_file_path(&file_path, |path, _count| path.is_file())
|
self.get_unique_download_file_path(&file_path, |path, _count| path.is_file())
|
||||||
.context(format!(
|
|
||||||
"cannot get download file path of {:?}",
|
|
||||||
file_name.as_ref()
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the unique download path from a file name by adding suffixes in case of name conflicts.
|
/// Gets the unique download path from a file name by adding
|
||||||
|
/// suffixes in case of name conflicts.
|
||||||
pub fn get_unique_download_file_path(
|
pub fn get_unique_download_file_path(
|
||||||
&self,
|
&self,
|
||||||
original_file_path: &PathBuf,
|
original_file_path: &PathBuf,
|
||||||
|
@ -303,7 +319,9 @@ impl<'a> AccountConfig {
|
||||||
.file_stem()
|
.file_stem()
|
||||||
.and_then(OsStr::to_str)
|
.and_then(OsStr::to_str)
|
||||||
.map(|fstem| format!("{}_{}{}", fstem, count, file_ext))
|
.map(|fstem| format!("{}_{}{}", fstem, count, file_ext))
|
||||||
.ok_or_else(|| anyhow!("cannot get stem from file {:?}", original_file_path))?,
|
.ok_or_else(|| {
|
||||||
|
AccountError::ParseDownloadFileNameError(file_path.to_owned())
|
||||||
|
})?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,7 +341,7 @@ impl<'a> AccountConfig {
|
||||||
.unwrap_or(default_cmd);
|
.unwrap_or(default_cmd);
|
||||||
|
|
||||||
debug!("run command: {}", cmd);
|
debug!("run command: {}", cmd);
|
||||||
run_cmd(&cmd).context("cannot run notify cmd")?;
|
run_cmd(&cmd)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,9 +353,8 @@ impl<'a> AccountConfig {
|
||||||
.get(&mbox.trim().to_lowercase())
|
.get(&mbox.trim().to_lowercase())
|
||||||
.map(|s| s.as_str())
|
.map(|s| s.as_str())
|
||||||
.unwrap_or(mbox);
|
.unwrap_or(mbox);
|
||||||
shellexpand::full(mbox)
|
let mbox = shellexpand::full(mbox).map(String::from)?;
|
||||||
.map(String::from)
|
Ok(mbox)
|
||||||
.with_context(|| format!("cannot expand mailbox path {:?}", mbox))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,8 +391,11 @@ pub struct ImapBackendConfig {
|
||||||
impl ImapBackendConfig {
|
impl ImapBackendConfig {
|
||||||
/// Gets the IMAP password of the user account.
|
/// Gets the IMAP password of the user account.
|
||||||
pub fn imap_passwd(&self) -> Result<String> {
|
pub fn imap_passwd(&self) -> Result<String> {
|
||||||
let passwd = run_cmd(&self.imap_passwd_cmd).context("cannot run IMAP passwd cmd")?;
|
let passwd = run_cmd(&self.imap_passwd_cmd)?;
|
||||||
let passwd = passwd.lines().next().context("cannot find password")?;
|
let passwd = passwd
|
||||||
|
.lines()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| AccountError::FindPasswordError)?;
|
||||||
Ok(passwd.to_string())
|
Ok(passwd.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use crate::config::{Format, Hooks};
|
use crate::account::{Format, Hooks};
|
||||||
|
|
||||||
pub trait ToDeserializedBaseAccountConfig {
|
pub trait ToDeserializedBaseAccountConfig {
|
||||||
fn to_base(&self) -> DeserializedBaseAccountConfig;
|
fn to_base(&self) -> DeserializedBaseAccountConfig;
|
|
@ -1,10 +1,10 @@
|
||||||
use anyhow::{Context, Result};
|
use log::{debug, trace};
|
||||||
use log::{debug, info, trace};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{collections::HashMap, env, fs, path::PathBuf};
|
use std::{collections::HashMap, env, fs, io, path::PathBuf, result};
|
||||||
|
use thiserror::Error;
|
||||||
use toml;
|
use toml;
|
||||||
|
|
||||||
use crate::config::DeserializedAccountConfig;
|
use crate::account::DeserializedAccountConfig;
|
||||||
|
|
||||||
pub const DEFAULT_PAGE_SIZE: usize = 10;
|
pub const DEFAULT_PAGE_SIZE: usize = 10;
|
||||||
pub const DEFAULT_SIG_DELIM: &str = "-- \n";
|
pub const DEFAULT_SIG_DELIM: &str = "-- \n";
|
||||||
|
@ -13,6 +13,18 @@ pub const DEFAULT_INBOX_FOLDER: &str = "INBOX";
|
||||||
pub const DEFAULT_SENT_FOLDER: &str = "Sent";
|
pub const DEFAULT_SENT_FOLDER: &str = "Sent";
|
||||||
pub const DEFAULT_DRAFT_FOLDER: &str = "Drafts";
|
pub const DEFAULT_DRAFT_FOLDER: &str = "Drafts";
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum DeserializeConfigError {
|
||||||
|
#[error("cannot read config file")]
|
||||||
|
ReadConfigFile(#[from] io::Error),
|
||||||
|
#[error("cannot parse config file")]
|
||||||
|
ParseConfigFile(#[from] toml::de::Error),
|
||||||
|
#[error("cannot read environment variable")]
|
||||||
|
ReadEnvVar(#[from] env::VarError),
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result<T> = result::Result<T, DeserializeConfigError>;
|
||||||
|
|
||||||
/// Represents the user config file.
|
/// Represents the user config file.
|
||||||
#[derive(Debug, Default, Clone, Deserialize)]
|
#[derive(Debug, Default, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
@ -42,32 +54,35 @@ pub struct DeserializedConfig {
|
||||||
impl DeserializedConfig {
|
impl DeserializedConfig {
|
||||||
/// Tries to create a config from an optional path.
|
/// Tries to create a config from an optional path.
|
||||||
pub fn from_opt_path(path: Option<&str>) -> Result<Self> {
|
pub fn from_opt_path(path: Option<&str>) -> Result<Self> {
|
||||||
info!("begin: try to parse config from path");
|
trace!(">> parse config from path");
|
||||||
debug!("path: {:?}", path);
|
debug!("path: {:?}", path);
|
||||||
|
|
||||||
let path = path.map(|s| s.into()).unwrap_or(Self::path()?);
|
let path = path.map(|s| s.into()).unwrap_or(Self::path()?);
|
||||||
let content = fs::read_to_string(path).context("cannot read config file")?;
|
let content = fs::read_to_string(path)?;
|
||||||
let config = toml::from_str(&content).context("cannot parse config file")?;
|
let config = toml::from_str(&content)?;
|
||||||
info!("end: try to parse config from path");
|
|
||||||
trace!("config: {:?}", config);
|
trace!("config: {:?}", config);
|
||||||
|
trace!("<< parse config from path");
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to get the XDG config file path from XDG_CONFIG_HOME environment variable.
|
/// Tries to get the XDG config file path from XDG_CONFIG_HOME
|
||||||
|
/// environment variable.
|
||||||
fn path_from_xdg() -> Result<PathBuf> {
|
fn path_from_xdg() -> Result<PathBuf> {
|
||||||
let path =
|
let path = env::var("XDG_CONFIG_HOME")?;
|
||||||
env::var("XDG_CONFIG_HOME").context("cannot find \"XDG_CONFIG_HOME\" env var")?;
|
|
||||||
let path = PathBuf::from(path).join("himalaya").join("config.toml");
|
let path = PathBuf::from(path).join("himalaya").join("config.toml");
|
||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to get the XDG config file path from HOME environment variable.
|
/// Tries to get the XDG config file path from HOME environment
|
||||||
|
/// variable.
|
||||||
fn path_from_xdg_alt() -> Result<PathBuf> {
|
fn path_from_xdg_alt() -> Result<PathBuf> {
|
||||||
let home_var = if cfg!(target_family = "windows") {
|
let home_var = if cfg!(target_family = "windows") {
|
||||||
"USERPROFILE"
|
"USERPROFILE"
|
||||||
} else {
|
} else {
|
||||||
"HOME"
|
"HOME"
|
||||||
};
|
};
|
||||||
let path = env::var(home_var).context(format!("cannot find {:?} env var", home_var))?;
|
let path = env::var(home_var)?;
|
||||||
let path = PathBuf::from(path)
|
let path = PathBuf::from(path)
|
||||||
.join(".config")
|
.join(".config")
|
||||||
.join("himalaya")
|
.join("himalaya")
|
||||||
|
@ -75,14 +90,15 @@ impl DeserializedConfig {
|
||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to get the .himalayarc config file path from HOME environment variable.
|
/// Tries to get the .himalayarc config file path from HOME
|
||||||
|
/// environment variable.
|
||||||
fn path_from_home() -> Result<PathBuf> {
|
fn path_from_home() -> Result<PathBuf> {
|
||||||
let home_var = if cfg!(target_family = "windows") {
|
let home_var = if cfg!(target_family = "windows") {
|
||||||
"USERPROFILE"
|
"USERPROFILE"
|
||||||
} else {
|
} else {
|
||||||
"HOME"
|
"HOME"
|
||||||
};
|
};
|
||||||
let path = env::var(home_var).context(format!("cannot find {:?} env var", home_var))?;
|
let path = env::var(home_var)?;
|
||||||
let path = PathBuf::from(path).join(".himalayarc");
|
let path = PathBuf::from(path).join(".himalayarc");
|
||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
@ -92,6 +108,5 @@ impl DeserializedConfig {
|
||||||
Self::path_from_xdg()
|
Self::path_from_xdg()
|
||||||
.or_else(|_| Self::path_from_xdg_alt())
|
.or_else(|_| Self::path_from_xdg_alt())
|
||||||
.or_else(|_| Self::path_from_home())
|
.or_else(|_| Self::path_from_home())
|
||||||
.context("cannot find config path")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
16
lib/src/account/mod.rs
Normal file
16
lib/src/account/mod.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
pub mod deserialized_config;
|
||||||
|
pub use deserialized_config::*;
|
||||||
|
|
||||||
|
pub mod deserialized_account_config;
|
||||||
|
pub use deserialized_account_config::*;
|
||||||
|
|
||||||
|
// pub mod account_handlers;
|
||||||
|
|
||||||
|
pub mod account_config;
|
||||||
|
pub use account_config::*;
|
||||||
|
|
||||||
|
pub mod format;
|
||||||
|
pub use format::*;
|
||||||
|
|
||||||
|
pub mod hooks;
|
||||||
|
pub use hooks::*;
|
|
@ -1,8 +1,2 @@
|
||||||
#[cfg(test)]
|
pub mod account;
|
||||||
mod tests {
|
mod process;
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
let result = 2 + 2;
|
|
||||||
assert_eq!(result, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
25
lib/src/process.rs
Normal file
25
lib/src/process.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use log::debug;
|
||||||
|
use std::{io, process::Command, result, string};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ProcessError {
|
||||||
|
#[error("cannot run command")]
|
||||||
|
RunCmdError(#[from] io::Error),
|
||||||
|
#[error("cannot parse command output")]
|
||||||
|
ParseCmdOutputError(#[from] string::FromUtf8Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result<T> = result::Result<T, ProcessError>;
|
||||||
|
|
||||||
|
pub fn run_cmd(cmd: &str) -> Result<String> {
|
||||||
|
debug!("running command: {}", cmd);
|
||||||
|
|
||||||
|
let output = if cfg!(target_os = "windows") {
|
||||||
|
Command::new("cmd").args(&["/C", cmd]).output()
|
||||||
|
} else {
|
||||||
|
Command::new("sh").arg("-c").arg(cmd).output()
|
||||||
|
}?;
|
||||||
|
|
||||||
|
Ok(String::from_utf8(output.stdout)?)
|
||||||
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
#[cfg(feature = "notmuch-backend")]
|
#[cfg(feature = "notmuch-backend")]
|
||||||
use std::{collections::HashMap, env, fs, iter::FromIterator};
|
use std::{collections::HashMap, env, fs, iter::FromIterator};
|
||||||
|
|
||||||
|
|
||||||
#[cfg(feature = "notmuch-backend")]
|
#[cfg(feature = "notmuch-backend")]
|
||||||
use himalaya::{
|
use himalaya::{
|
||||||
backends::{Backend, MaildirBackend, NotmuchBackend, NotmuchEnvelopes},
|
backends::{Backend, MaildirBackend, NotmuchBackend, NotmuchEnvelopes},
|
||||||
config::{AccountConfig, MaildirBackendConfig, NotmuchBackendConfig},
|
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "notmuch-backend")]
|
||||||
|
use himalaya_lib::account::{AccountConfig, MaildirBackendConfig, NotmuchBackendConfig}
|
||||||
|
|
||||||
#[cfg(feature = "notmuch-backend")]
|
#[cfg(feature = "notmuch-backend")]
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in a new issue