From 3f5feed0ff1abdc6163800f9082d90d2b85ca431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Sat, 28 May 2022 16:59:12 +0200 Subject: [PATCH] extract account and config from cli to lib (#340) --- Cargo.lock | 35 ++++++++ cli/Cargo.toml | 1 + cli/src/backends/imap/imap_backend.rs | 2 +- cli/src/backends/maildir/maildir_backend.rs | 2 +- cli/src/backends/notmuch/notmuch_backend.rs | 2 +- cli/src/config/account.rs | 3 +- cli/src/config/account_handlers.rs | 11 +-- cli/src/lib.rs | 15 ---- cli/src/main.rs | 12 +-- cli/src/mbox/mbox_handlers.rs | 2 +- cli/src/msg/msg_entity.rs | 4 +- cli/src/msg/msg_handlers.rs | 2 +- cli/src/msg/parts_entity.rs | 3 +- cli/src/msg/tpl_handlers.rs | 2 +- cli/src/output/print_table.rs | 3 +- cli/src/smtp/smtp_service.rs | 5 +- cli/src/ui/table.rs | 6 +- lib/Cargo.toml | 20 +++++ .../src/account}/account_config.rs | 88 ++++++++++++------- .../account}/deserialized_account_config.rs | 2 +- .../src/account}/deserialized_config.rs | 47 ++++++---- {cli/src/config => lib/src/account}/format.rs | 0 {cli/src/config => lib/src/account}/hooks.rs | 0 lib/src/account/mod.rs | 16 ++++ lib/src/lib.rs | 10 +-- lib/src/process.rs | 25 ++++++ tests/test_notmuch_backend.rs | 4 +- 27 files changed, 219 insertions(+), 103 deletions(-) rename {cli/src/config => lib/src/account}/account_config.rs (87%) rename {cli/src/config => lib/src/account}/deserialized_account_config.rs (99%) rename {cli/src/config => lib/src/account}/deserialized_config.rs (72%) rename {cli/src/config => lib/src/account}/format.rs (100%) rename {cli/src/config => lib/src/account}/hooks.rs (100%) create mode 100644 lib/src/account/mod.rs create mode 100644 lib/src/process.rs diff --git a/Cargo.lock b/Cargo.lock index ad142eb..cc51987 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -441,6 +441,7 @@ dependencies = [ "convert_case", "env_logger", "erased-serde", + "himalaya-lib", "html-escape", "imap", "imap-proto", @@ -468,6 +469,20 @@ dependencies = [ [[package]] name = "himalaya-lib" version = "0.1.0" +dependencies = [ + "imap", + "imap-proto", + "lettre", + "log", + "maildir", + "mailparse", + "md5", + "notmuch", + "serde", + "shellexpand", + "thiserror", + "toml", +] [[package]] name = "hostname" @@ -1365,6 +1380,26 @@ dependencies = [ "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]] name = "time" version = "0.1.44" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 045f366..50fea80 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -31,6 +31,7 @@ clap = { version = "2.33.3", default-features = false, features = ["suggestions" convert_case = "0.5.0" env_logger = "0.8.3" erased-serde = "0.3.18" +himalaya-lib = { path = "../lib" } html-escape = "0.2.9" lettre = { version = "0.10.0-rc.1", features = ["serde"] } log = "0.4.14" diff --git a/cli/src/backends/imap/imap_backend.rs b/cli/src/backends/imap/imap_backend.rs index f6319e5..9f9bc5c 100644 --- a/cli/src/backends/imap/imap_backend.rs +++ b/cli/src/backends/imap/imap_backend.rs @@ -3,6 +3,7 @@ //! This module contains the definition of the IMAP backend. use anyhow::{anyhow, Context, Result}; +use himalaya_lib::account::{AccountConfig, ImapBackendConfig}; use log::{debug, log_enabled, trace, Level}; use native_tls::{TlsConnector, TlsStream}; use std::{ @@ -16,7 +17,6 @@ use crate::{ backends::{ imap::msg_sort_criterion::SortCriteria, Backend, ImapEnvelope, ImapEnvelopes, ImapMboxes, }, - config::{AccountConfig, ImapBackendConfig}, mbox::Mboxes, msg::{Envelopes, Msg}, output::run_cmd, diff --git a/cli/src/backends/maildir/maildir_backend.rs b/cli/src/backends/maildir/maildir_backend.rs index a2e85a5..a83423d 100644 --- a/cli/src/backends/maildir/maildir_backend.rs +++ b/cli/src/backends/maildir/maildir_backend.rs @@ -4,12 +4,12 @@ //! traits implementation. use anyhow::{anyhow, Context, Result}; +use himalaya_lib::account::{AccountConfig, MaildirBackendConfig}; use log::{debug, info, trace}; use std::{convert::TryInto, env, fs, path::PathBuf}; use crate::{ backends::{Backend, IdMapper, MaildirEnvelopes, MaildirFlags, MaildirMboxes}, - config::{AccountConfig, MaildirBackendConfig}, mbox::Mboxes, msg::{Envelopes, Msg}, }; diff --git a/cli/src/backends/notmuch/notmuch_backend.rs b/cli/src/backends/notmuch/notmuch_backend.rs index 37e559a..66a2c14 100644 --- a/cli/src/backends/notmuch/notmuch_backend.rs +++ b/cli/src/backends/notmuch/notmuch_backend.rs @@ -1,11 +1,11 @@ use std::{convert::TryInto, fs}; use anyhow::{anyhow, Context, Result}; +use himalaya_lib::account::{AccountConfig, NotmuchBackendConfig}; use log::{debug, info, trace}; use crate::{ backends::{Backend, IdMapper, MaildirBackend, NotmuchEnvelopes, NotmuchMbox, NotmuchMboxes}, - config::{AccountConfig, NotmuchBackendConfig}, mbox::Mboxes, msg::{Envelopes, Msg}, }; diff --git a/cli/src/config/account.rs b/cli/src/config/account.rs index d593b8f..3a11deb 100644 --- a/cli/src/config/account.rs +++ b/cli/src/config/account.rs @@ -12,8 +12,9 @@ use std::{ ops::Deref, }; +use himalaya_lib::account::DeserializedAccountConfig; + use crate::{ - config::DeserializedAccountConfig, output::{PrintTable, PrintTableOpts, WriteColor}, ui::{Cell, Row, Table}, }; diff --git a/cli/src/config/account_handlers.rs b/cli/src/config/account_handlers.rs index 4ee2c57..80059d9 100644 --- a/cli/src/config/account_handlers.rs +++ b/cli/src/config/account_handlers.rs @@ -3,10 +3,11 @@ //! This module gathers all account actions triggered by the CLI. use anyhow::Result; +use himalaya_lib::account::{AccountConfig, DeserializedConfig}; use log::{info, trace}; use crate::{ - config::{AccountConfig, Accounts, DeserializedConfig}, + config::Accounts, output::{PrintTableOpts, PrinterService}, }; @@ -36,13 +37,13 @@ pub fn list<'a, P: PrinterService>( #[cfg(test)] mod tests { + use himalaya_lib::account::{ + AccountConfig, DeserializedAccountConfig, DeserializedConfig, DeserializedImapAccountConfig, + }; use std::{collections::HashMap, fmt::Debug, io, iter::FromIterator}; use termcolor::ColorSpec; - use crate::{ - config::{DeserializedAccountConfig, DeserializedImapAccountConfig}, - output::{Print, PrintTable, WriteColor}, - }; + use crate::output::{Print, PrintTable, WriteColor}; use super::*; diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 32384d5..2e0a581 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -107,12 +107,6 @@ pub mod smtp { } 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 account_args; @@ -120,15 +114,6 @@ pub mod config { pub mod 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; diff --git a/cli/src/main.rs b/cli/src/main.rs index ab2a944..10c7382 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,14 +1,14 @@ use anyhow::Result; +use himalaya_lib::account::{ + AccountConfig, BackendConfig, DeserializedConfig, DEFAULT_INBOX_FOLDER, +}; use std::{convert::TryFrom, env}; use url::Url; use himalaya::{ backends::Backend, compl::{compl_args, compl_handlers}, - config::{ - account_args, account_handlers, config_args, AccountConfig, BackendConfig, - DeserializedConfig, DEFAULT_INBOX_FOLDER, - }, + config::{account_args, account_handlers, config_args}, mbox::{mbox_args, mbox_handlers}, msg::{flag_args, flag_handlers, msg_args, msg_handlers, tpl_args, tpl_handlers}, output::{output_args, OutputFmt, StdoutPrinter}, @@ -22,7 +22,9 @@ use himalaya::backends::{imap_args, imap_handlers, ImapBackend}; use himalaya::backends::MaildirBackend; #[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> { let app = clap::App::new(env!("CARGO_PKG_NAME")) diff --git a/cli/src/mbox/mbox_handlers.rs b/cli/src/mbox/mbox_handlers.rs index 4a110e9..332db46 100644 --- a/cli/src/mbox/mbox_handlers.rs +++ b/cli/src/mbox/mbox_handlers.rs @@ -3,11 +3,11 @@ //! This module gathers all mailbox actions triggered by the CLI. use anyhow::Result; +use himalaya_lib::account::AccountConfig; use log::{info, trace}; use crate::{ backends::Backend, - config::AccountConfig, output::{PrintTableOpts, PrinterService}, }; diff --git a/cli/src/msg/msg_entity.rs b/cli/src/msg/msg_entity.rs index 9978d46..0ee49ce 100644 --- a/cli/src/msg/msg_entity.rs +++ b/cli/src/msg/msg_entity.rs @@ -2,6 +2,9 @@ use ammonia; use anyhow::{anyhow, Context, Error, Result}; use chrono::{DateTime, Local, TimeZone, Utc}; use convert_case::{Case, Casing}; +use himalaya_lib::account::{ + AccountConfig, DEFAULT_DRAFT_FOLDER, DEFAULT_SENT_FOLDER, DEFAULT_SIG_DELIM, +}; use html_escape; use lettre::message::{header::ContentType, Attachment, MultiPart, SinglePart}; use log::{info, trace, warn}; @@ -18,7 +21,6 @@ use uuid::Uuid; use crate::{ backends::Backend, - config::{AccountConfig, DEFAULT_DRAFT_FOLDER, DEFAULT_SENT_FOLDER, DEFAULT_SIG_DELIM}, msg::{ from_addrs_to_sendable_addrs, from_addrs_to_sendable_mbox, from_slice_to_addrs, msg_utils, Addr, Addrs, BinaryPart, Part, Parts, TextPlainPart, TplOverride, diff --git a/cli/src/msg/msg_handlers.rs b/cli/src/msg/msg_handlers.rs index 57ce255..0c2e0c0 100644 --- a/cli/src/msg/msg_handlers.rs +++ b/cli/src/msg/msg_handlers.rs @@ -4,6 +4,7 @@ use anyhow::{Context, Result}; use atty::Stream; +use himalaya_lib::account::{AccountConfig, DEFAULT_SENT_FOLDER}; use log::{debug, info, trace}; use mailparse::addrparse; use std::{ @@ -15,7 +16,6 @@ use url::Url; use crate::{ backends::Backend, - config::{AccountConfig, DEFAULT_SENT_FOLDER}, msg::{Msg, Part, Parts, TextPlainPart}, output::{PrintTableOpts, PrinterService}, smtp::SmtpService, diff --git a/cli/src/msg/parts_entity.rs b/cli/src/msg/parts_entity.rs index 9b371a0..36eac50 100644 --- a/cli/src/msg/parts_entity.rs +++ b/cli/src/msg/parts_entity.rs @@ -1,4 +1,5 @@ use anyhow::{anyhow, Context, Result}; +use himalaya_lib::account::AccountConfig; use mailparse::MailHeaderMap; use serde::Serialize; use std::{ @@ -7,8 +8,6 @@ use std::{ }; use uuid::Uuid; -use crate::config::AccountConfig; - #[derive(Debug, Clone, Default, Serialize)] pub struct TextPlainPart { pub content: String, diff --git a/cli/src/msg/tpl_handlers.rs b/cli/src/msg/tpl_handlers.rs index b2db225..16953b1 100644 --- a/cli/src/msg/tpl_handlers.rs +++ b/cli/src/msg/tpl_handlers.rs @@ -4,11 +4,11 @@ use anyhow::Result; use atty::Stream; +use himalaya_lib::account::AccountConfig; use std::io::{self, BufRead}; use crate::{ backends::Backend, - config::AccountConfig, msg::{Msg, TplOverride}, output::PrinterService, smtp::SmtpService, diff --git a/cli/src/output/print_table.rs b/cli/src/output/print_table.rs index 45557b9..007c2da 100644 --- a/cli/src/output/print_table.rs +++ b/cli/src/output/print_table.rs @@ -1,9 +1,8 @@ use anyhow::Result; +use himalaya_lib::account::Format; use std::io; use termcolor::{self, StandardStream}; -use crate::config::Format; - pub trait WriteColor: io::Write + termcolor::WriteColor {} impl WriteColor for StandardStream {} diff --git a/cli/src/smtp/smtp_service.rs b/cli/src/smtp/smtp_service.rs index 13ea6ce..e417afb 100644 --- a/cli/src/smtp/smtp_service.rs +++ b/cli/src/smtp/smtp_service.rs @@ -1,4 +1,5 @@ use anyhow::{Context, Result}; +use himalaya_lib::account::AccountConfig; use lettre::{ self, transport::smtp::{ @@ -9,7 +10,7 @@ use lettre::{ }; use std::convert::TryInto; -use crate::{config::AccountConfig, msg::Msg, output::pipe_cmd}; +use crate::{msg::Msg, output::pipe_cmd}; pub trait SmtpService { fn send(&mut self, account: &AccountConfig, msg: &Msg) -> Result>; @@ -62,7 +63,7 @@ impl SmtpService for LettreService<'_> { if let Some(cmd) = account.hooks.pre_send.as_deref() { for cmd in cmd.split('|') { 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)?; Msg::from_parsed_mail(parsed_mail, account)?.try_into() diff --git a/cli/src/ui/table.rs b/cli/src/ui/table.rs index 5342699..2e84209 100644 --- a/cli/src/ui/table.rs +++ b/cli/src/ui/table.rs @@ -5,15 +5,13 @@ //! [builder design pattern]: https://refactoring.guru/design-patterns/builder use anyhow::{Context, Result}; +use himalaya_lib::account::Format; use log::trace; use termcolor::{Color, ColorSpec}; use terminal_size; use unicode_width::UnicodeWidthStr; -use crate::{ - config::Format, - output::{Print, PrintTableOpts, WriteColor}, -}; +use crate::output::{Print, PrintTableOpts, WriteColor}; /// Defines the default terminal size. /// This is used when the size cannot be determined by the `terminal_size` crate. diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 8316d53..4bfae01 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -3,4 +3,24 @@ name = "himalaya-lib" version = "0.1.0" edition = "2021" +[features] +imap-backend = ["imap", "imap-proto"] +maildir-backend = ["maildir", "md5"] +notmuch-backend = ["notmuch", "maildir-backend"] +default = ["imap-backend", "maildir-backend"] + [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 } diff --git a/cli/src/config/account_config.rs b/lib/src/account/account_config.rs similarity index 87% rename from cli/src/config/account_config.rs rename to lib/src/account/account_config.rs index 29cb3a0..5e7b951 100644 --- a/cli/src/config/account_config.rs +++ b/lib/src/account/account_config.rs @@ -1,10 +1,35 @@ -use anyhow::{anyhow, Context, Result}; use lettre::transport::smtp::authentication::Credentials as SmtpCredentials; use log::{debug, info, trace}; 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), + #[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 = result::Result; /// Represents the user account. #[derive(Debug, Default, Clone)] @@ -62,7 +87,8 @@ pub struct 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( config: &'a DeserializedConfig, account_name: Option<&str>, @@ -87,12 +113,12 @@ impl<'a> AccountConfig { } }) .map(|(name, account)| (name.to_owned(), account)) - .ok_or_else(|| anyhow!("cannot find default account")), + .ok_or_else(|| AccountError::FindDefaultAccountError), Some(name) => config .accounts .get(name) .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(); @@ -225,20 +251,19 @@ impl<'a> AccountConfig { format!("{} <{}>", self.display_name, self.email) }; - Ok(mailparse::addrparse(&addr) - .context(format!( - "cannot parse account address {:?}", - self.display_name - ))? + Ok(mailparse::addrparse(&addr)? .first() - .ok_or_else(|| anyhow!("cannot parse account address {:?}", self.display_name))? + .ok_or_else(|| AccountError::FindAccountAddressError(addr.into()))? .clone()) } /// Builds the user account SMTP credentials. pub fn smtp_creds(&self) -> Result { - let passwd = run_cmd(&self.smtp_passwd_cmd).context("cannot run SMTP passwd cmd")?; - let passwd = passwd.lines().next().context("cannot find password")?; + let passwd = run_cmd(&self.smtp_passwd_cmd)?; + let passwd = passwd + .lines() + .next() + .ok_or_else(|| AccountError::FindPasswordError)?; Ok(SmtpCredentials::new( self.smtp_login.to_owned(), @@ -250,10 +275,7 @@ impl<'a> AccountConfig { pub fn pgp_encrypt_file(&self, addr: &str, path: PathBuf) -> Result> { if let Some(cmd) = self.pgp_encrypt_cmd.as_ref() { let encrypt_file_cmd = format!("{} {} {:?}", cmd, addr, path); - run_cmd(&encrypt_file_cmd).map(Some).context(format!( - "cannot run pgp encrypt command {:?}", - encrypt_file_cmd - )) + Ok(run_cmd(&encrypt_file_cmd).map(Some)?) } else { Ok(None) } @@ -263,10 +285,7 @@ impl<'a> AccountConfig { pub fn pgp_decrypt_file(&self, path: PathBuf) -> Result> { if let Some(cmd) = self.pgp_decrypt_cmd.as_ref() { let decrypt_file_cmd = format!("{} {:?}", cmd, path); - run_cmd(&decrypt_file_cmd).map(Some).context(format!( - "cannot run pgp decrypt command {:?}", - decrypt_file_cmd - )) + Ok(run_cmd(&decrypt_file_cmd).map(Some)?) } else { Ok(None) } @@ -276,13 +295,10 @@ impl<'a> AccountConfig { pub fn get_download_file_path>(&self, file_name: S) -> Result { let file_path = self.downloads_dir.join(file_name.as_ref()); 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( &self, original_file_path: &PathBuf, @@ -303,7 +319,9 @@ impl<'a> AccountConfig { .file_stem() .and_then(OsStr::to_str) .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); debug!("run command: {}", cmd); - run_cmd(&cmd).context("cannot run notify cmd")?; + run_cmd(&cmd)?; Ok(()) } @@ -335,9 +353,8 @@ impl<'a> AccountConfig { .get(&mbox.trim().to_lowercase()) .map(|s| s.as_str()) .unwrap_or(mbox); - shellexpand::full(mbox) - .map(String::from) - .with_context(|| format!("cannot expand mailbox path {:?}", mbox)) + let mbox = shellexpand::full(mbox).map(String::from)?; + Ok(mbox) } } @@ -374,8 +391,11 @@ pub struct ImapBackendConfig { impl ImapBackendConfig { /// Gets the IMAP password of the user account. pub fn imap_passwd(&self) -> Result { - let passwd = run_cmd(&self.imap_passwd_cmd).context("cannot run IMAP passwd cmd")?; - let passwd = passwd.lines().next().context("cannot find password")?; + let passwd = run_cmd(&self.imap_passwd_cmd)?; + let passwd = passwd + .lines() + .next() + .ok_or_else(|| AccountError::FindPasswordError)?; Ok(passwd.to_string()) } } diff --git a/cli/src/config/deserialized_account_config.rs b/lib/src/account/deserialized_account_config.rs similarity index 99% rename from cli/src/config/deserialized_account_config.rs rename to lib/src/account/deserialized_account_config.rs index 8ada7f9..7539a41 100644 --- a/cli/src/config/deserialized_account_config.rs +++ b/lib/src/account/deserialized_account_config.rs @@ -1,7 +1,7 @@ use serde::Deserialize; use std::{collections::HashMap, path::PathBuf}; -use crate::config::{Format, Hooks}; +use crate::account::{Format, Hooks}; pub trait ToDeserializedBaseAccountConfig { fn to_base(&self) -> DeserializedBaseAccountConfig; diff --git a/cli/src/config/deserialized_config.rs b/lib/src/account/deserialized_config.rs similarity index 72% rename from cli/src/config/deserialized_config.rs rename to lib/src/account/deserialized_config.rs index e26b5a0..500d00a 100644 --- a/cli/src/config/deserialized_config.rs +++ b/lib/src/account/deserialized_config.rs @@ -1,10 +1,10 @@ -use anyhow::{Context, Result}; -use log::{debug, info, trace}; +use log::{debug, trace}; 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 crate::config::DeserializedAccountConfig; +use crate::account::DeserializedAccountConfig; pub const DEFAULT_PAGE_SIZE: usize = 10; 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_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 = result::Result; + /// Represents the user config file. #[derive(Debug, Default, Clone, Deserialize)] #[serde(rename_all = "kebab-case")] @@ -42,32 +54,35 @@ pub struct DeserializedConfig { impl DeserializedConfig { /// Tries to create a config from an optional path. pub fn from_opt_path(path: Option<&str>) -> Result { - info!("begin: try to parse config from path"); + trace!(">> parse config from path"); debug!("path: {:?}", 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 config = toml::from_str(&content).context("cannot parse config file")?; - info!("end: try to parse config from path"); + let content = fs::read_to_string(path)?; + let config = toml::from_str(&content)?; + trace!("config: {:?}", config); + trace!("<< parse config from path"); 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 { - let path = - env::var("XDG_CONFIG_HOME").context("cannot find \"XDG_CONFIG_HOME\" env var")?; + let path = env::var("XDG_CONFIG_HOME")?; let path = PathBuf::from(path).join("himalaya").join("config.toml"); 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 { let home_var = if cfg!(target_family = "windows") { "USERPROFILE" } else { "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(".config") .join("himalaya") @@ -75,14 +90,15 @@ impl DeserializedConfig { 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 { let home_var = if cfg!(target_family = "windows") { "USERPROFILE" } else { "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"); Ok(path) } @@ -92,6 +108,5 @@ impl DeserializedConfig { Self::path_from_xdg() .or_else(|_| Self::path_from_xdg_alt()) .or_else(|_| Self::path_from_home()) - .context("cannot find config path") } } diff --git a/cli/src/config/format.rs b/lib/src/account/format.rs similarity index 100% rename from cli/src/config/format.rs rename to lib/src/account/format.rs diff --git a/cli/src/config/hooks.rs b/lib/src/account/hooks.rs similarity index 100% rename from cli/src/config/hooks.rs rename to lib/src/account/hooks.rs diff --git a/lib/src/account/mod.rs b/lib/src/account/mod.rs new file mode 100644 index 0000000..5409db8 --- /dev/null +++ b/lib/src/account/mod.rs @@ -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::*; diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 1b4a90c..9c8fa74 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,8 +1,2 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); - } -} +pub mod account; +mod process; diff --git a/lib/src/process.rs b/lib/src/process.rs new file mode 100644 index 0000000..3f19104 --- /dev/null +++ b/lib/src/process.rs @@ -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 = result::Result; + +pub fn run_cmd(cmd: &str) -> Result { + 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)?) +} diff --git a/tests/test_notmuch_backend.rs b/tests/test_notmuch_backend.rs index 161fb41..dbd44c5 100644 --- a/tests/test_notmuch_backend.rs +++ b/tests/test_notmuch_backend.rs @@ -1,11 +1,13 @@ #[cfg(feature = "notmuch-backend")] use std::{collections::HashMap, env, fs, iter::FromIterator}; + #[cfg(feature = "notmuch-backend")] use himalaya::{ backends::{Backend, MaildirBackend, NotmuchBackend, NotmuchEnvelopes}, - config::{AccountConfig, MaildirBackendConfig, NotmuchBackendConfig}, }; +#[cfg(feature = "notmuch-backend")] +use himalaya_lib::account::{AccountConfig, MaildirBackendConfig, NotmuchBackendConfig} #[cfg(feature = "notmuch-backend")] #[test]