mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-26 04:50:25 +00:00
commit
2d077faa88
37 changed files with 678 additions and 266 deletions
3
.github/workflows/tests.yaml
vendored
3
.github/workflows/tests.yaml
vendored
|
@ -10,6 +10,8 @@ jobs:
|
|||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install libnotmuch
|
||||
run: sudo apt-get install -y libnotmuch-dev
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Start GreenMail testing server
|
||||
|
@ -33,3 +35,4 @@ jobs:
|
|||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all-features
|
||||
|
|
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.5.8] - 2022-03-04
|
||||
|
||||
### Added
|
||||
|
||||
- Flowed format support [#206]
|
||||
- List accounts command [#244]
|
||||
- One cargo feature per backend [#318]
|
||||
|
||||
### Changed
|
||||
|
||||
- Vim doc about mailbox pickers [#298]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Some emojis break the table layout [#300]
|
||||
- Bad sender and date in reply and forward template [#321]
|
||||
|
||||
## [0.5.7] - 2022-03-01
|
||||
|
||||
### Added
|
||||
|
@ -321,7 +338,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Password from command [#22]
|
||||
- Set up README [#20]
|
||||
|
||||
[unreleased]: https://github.com/soywod/himalaya/compare/v0.5.7...HEAD
|
||||
[unreleased]: https://github.com/soywod/himalaya/compare/v0.5.8...HEAD
|
||||
[0.5.8]: https://github.com/soywod/himalaya/compare/v0.5.7...v0.5.8
|
||||
[0.5.7]: https://github.com/soywod/himalaya/compare/v0.5.6...v0.5.7
|
||||
[0.5.6]: https://github.com/soywod/himalaya/compare/v0.5.5...v0.5.6
|
||||
[0.5.5]: https://github.com/soywod/himalaya/compare/v0.5.4...v0.5.5
|
||||
|
@ -435,11 +453,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
[#199]: https://github.com/soywod/himalaya/issues/199
|
||||
[#204]: https://github.com/soywod/himalaya/issues/204
|
||||
[#205]: https://github.com/soywod/himalaya/issues/205
|
||||
[#206]: https://github.com/soywod/himalaya/issues/206
|
||||
[#215]: https://github.com/soywod/himalaya/issues/215
|
||||
[#220]: https://github.com/soywod/himalaya/issues/220
|
||||
[#227]: https://github.com/soywod/himalaya/issues/227
|
||||
[#228]: https://github.com/soywod/himalaya/issues/228
|
||||
[#229]: https://github.com/soywod/himalaya/issues/229
|
||||
[#244]: https://github.com/soywod/himalaya/issues/244
|
||||
[#249]: https://github.com/soywod/himalaya/issues/249
|
||||
[#256]: https://github.com/soywod/himalaya/issues/256
|
||||
[#259]: https://github.com/soywod/himalaya/issues/259
|
||||
|
@ -452,7 +472,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
[#280]: https://github.com/soywod/himalaya/issues/280
|
||||
[#288]: https://github.com/soywod/himalaya/issues/288
|
||||
[#289]: https://github.com/soywod/himalaya/issues/289
|
||||
[#298]: https://github.com/soywod/himalaya/issues/298
|
||||
[#300]: https://github.com/soywod/himalaya/issues/300
|
||||
[#303]: https://github.com/soywod/himalaya/issues/303
|
||||
[#305]: https://github.com/soywod/himalaya/issues/305
|
||||
[#308]: https://github.com/soywod/himalaya/issues/308
|
||||
[#309]: https://github.com/soywod/himalaya/issues/309
|
||||
[#318]: https://github.com/soywod/himalaya/issues/318
|
||||
[#321]: https://github.com/soywod/himalaya/issues/321
|
||||
|
|
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -436,7 +436,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "himalaya"
|
||||
version = "0.5.7"
|
||||
version = "0.5.8"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
|
@ -602,7 +602,7 @@ dependencies = [
|
|||
"idna",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"nom 7.0.0",
|
||||
"nom 7.1.0",
|
||||
"once_cell",
|
||||
"quoted_printable",
|
||||
"regex",
|
||||
|
@ -742,9 +742,9 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
|||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.1.4"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c64630dcdd71f1a64c435f54885086a0de5d6a12d104d69b165fb7d5286d677"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
|
@ -793,9 +793,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.0.0"
|
||||
version = "7.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ffd9d26838a953b4af82cbeb9f1592c6798916983959be223a7124e992742c1"
|
||||
checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109"
|
||||
dependencies = [
|
||||
"memchr 2.4.1",
|
||||
"minimal-lexical",
|
||||
|
|
21
Cargo.toml
21
Cargo.toml
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "himalaya"
|
||||
description = "Command-line interface for email management"
|
||||
version = "0.5.7"
|
||||
version = "0.5.8"
|
||||
authors = ["soywod <clement.douin@posteo.net>"]
|
||||
edition = "2018"
|
||||
license-file = "LICENSE"
|
||||
|
@ -16,6 +16,12 @@ repository = "https://github.com/soywod/himalaya"
|
|||
priority = "optional"
|
||||
section = "mail"
|
||||
|
||||
[features]
|
||||
imap-backend = ["imap", "imap-proto"]
|
||||
maildir-backend = ["maildir", "md5"]
|
||||
notmuch-backend = ["notmuch", "maildir-backend"]
|
||||
default = ["imap-backend", "maildir-backend"]
|
||||
|
||||
[dependencies]
|
||||
ammonia = "3.1.2"
|
||||
anyhow = "1.0.44"
|
||||
|
@ -25,15 +31,10 @@ clap = { version = "2.33.3", default-features = false, features = ["suggestions"
|
|||
env_logger = "0.8.3"
|
||||
erased-serde = "0.3.18"
|
||||
html-escape = "0.2.9"
|
||||
imap = "=3.0.0-alpha.4"
|
||||
imap-proto = "0.14.3"
|
||||
lettre = { version = "0.10.0-rc.1", features = ["serde"] }
|
||||
log = "0.4.14"
|
||||
maildir = "0.6.0"
|
||||
mailparse = "0.13.6"
|
||||
md5 = "0.7.0"
|
||||
native-tls = "0.2.8"
|
||||
notmuch = { version = "0.7.1", optional = true }
|
||||
regex = "1.5.4"
|
||||
rfc2047-decoder = "0.1.2"
|
||||
serde = { version = "1.0.118", features = ["derive"] }
|
||||
|
@ -46,3 +47,11 @@ tree_magic = "0.2.3"
|
|||
unicode-width = "0.1.7"
|
||||
url = "2.2.2"
|
||||
uuid = { version = "0.8", features = ["v4"] }
|
||||
|
||||
# Optional dependencies:
|
||||
|
||||
imap = { version = "=3.0.0-alpha.4", optional = true }
|
||||
imap-proto = { version = "0.14.3", optional = true }
|
||||
maildir = { version = "0.6.0", optional = true }
|
||||
md5 = { version = "0.7.0", optional = true }
|
||||
notmuch = { version = "0.7.1", optional = true }
|
||||
|
|
11
README.md
11
README.md
|
@ -67,15 +67,16 @@ for all the options.*
|
|||
## Features
|
||||
|
||||
- Mailbox listing
|
||||
- Email listing and filtering
|
||||
- Email listing and searching
|
||||
- Email composition based on `$EDITOR`
|
||||
- Email manipulation (copy/move/delete)
|
||||
- Multi-accounting
|
||||
- IMAP and Maildir support (POP and Notmuch are coming soon)
|
||||
- Account listing
|
||||
- IMAP, Maildir and Notmuch support
|
||||
- IMAP IDLE mode for real-time notifications
|
||||
- PGP end-to-end encryption
|
||||
- IDLE mode for real-time notifications
|
||||
- Vim plugin
|
||||
- Completions for bash/zsh/fish
|
||||
- Vim and Emacs plugins
|
||||
- Completions for various shells
|
||||
- JSON output
|
||||
- …
|
||||
|
||||
|
|
|
@ -80,6 +80,7 @@
|
|||
rustfmt
|
||||
rnix-lsp
|
||||
nixpkgs-fmt
|
||||
notmuch
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
//!
|
||||
//! [clap's docs.rs website]: https://docs.rs/clap/2.33.3/clap/enum.Shell.html
|
||||
|
||||
pub mod compl_arg;
|
||||
pub mod compl_handler;
|
||||
pub mod compl_args;
|
||||
pub mod compl_handlers;
|
||||
|
|
109
src/config/account.rs
Normal file
109
src/config/account.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
//! Account module.
|
||||
//!
|
||||
//! This module contains the definition of the printable account,
|
||||
//! which is only used by the "accounts" command to list all available
|
||||
//! accounts from the config file.
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
collections::hash_map::Iter,
|
||||
fmt::{self, Display},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::DeserializedAccountConfig,
|
||||
output::{PrintTable, PrintTableOpts, WriteColor},
|
||||
ui::{Cell, Row, Table},
|
||||
};
|
||||
|
||||
/// Represents the list of printable accounts.
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct Accounts(pub Vec<Account>);
|
||||
|
||||
impl Deref for Accounts {
|
||||
type Target = Vec<Account>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintTable for Accounts {
|
||||
fn print_table(&self, writter: &mut dyn WriteColor, opts: PrintTableOpts) -> Result<()> {
|
||||
writeln!(writter)?;
|
||||
Table::print(writter, self, opts)?;
|
||||
writeln!(writter)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the printable account.
|
||||
#[derive(Debug, Default, PartialEq, Eq, Serialize)]
|
||||
pub struct Account {
|
||||
/// Represents the account name.
|
||||
pub name: String,
|
||||
|
||||
/// Represents the backend name of the account.
|
||||
pub backend: String,
|
||||
|
||||
/// Represents the default state of the account.
|
||||
pub default: bool,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn new(name: &str, backend: &str, default: bool) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
backend: backend.into(),
|
||||
default,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Account {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl Table for Account {
|
||||
fn head() -> Row {
|
||||
Row::new()
|
||||
.cell(Cell::new("NAME").shrinkable().bold().underline().white())
|
||||
.cell(Cell::new("BACKEND").bold().underline().white())
|
||||
.cell(Cell::new("DEFAULT").bold().underline().white())
|
||||
}
|
||||
|
||||
fn row(&self) -> Row {
|
||||
let default = if self.default { "yes" } else { "" };
|
||||
Row::new()
|
||||
.cell(Cell::new(&self.name).shrinkable().green())
|
||||
.cell(Cell::new(&self.backend).blue())
|
||||
.cell(Cell::new(default).white())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Iter<'_, String, DeserializedAccountConfig>> for Accounts {
|
||||
fn from(map: Iter<'_, String, DeserializedAccountConfig>) -> Self {
|
||||
let mut accounts: Vec<_> = map
|
||||
.map(|(name, config)| match config {
|
||||
#[cfg(feature = "imap-backend")]
|
||||
DeserializedAccountConfig::Imap(config) => {
|
||||
Account::new(name, "imap", config.default.unwrap_or_default())
|
||||
}
|
||||
#[cfg(feature = "maildir-backend")]
|
||||
DeserializedAccountConfig::Maildir(config) => {
|
||||
Account::new(name, "maildir", config.default.unwrap_or_default())
|
||||
}
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
DeserializedAccountConfig::Notmuch(config) => {
|
||||
Account::new(name, "notmuch", config.default.unwrap_or_default())
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
accounts.sort_by(|a, b| b.name.partial_cmp(&a.name).unwrap());
|
||||
Self(accounts)
|
||||
}
|
||||
}
|
|
@ -1,9 +1,52 @@
|
|||
//! This module provides arguments related to the user account config.
|
||||
|
||||
use clap::Arg;
|
||||
use anyhow::Result;
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use log::{debug, info};
|
||||
|
||||
use crate::ui::table_arg;
|
||||
|
||||
type MaxTableWidth = Option<usize>;
|
||||
|
||||
/// Represents the account commands.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Cmd {
|
||||
/// Represents the list accounts command.
|
||||
List(MaxTableWidth),
|
||||
}
|
||||
|
||||
/// Represents the account command matcher.
|
||||
pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
|
||||
info!(">> account command matcher");
|
||||
|
||||
let cmd = if let Some(m) = m.subcommand_matches("accounts") {
|
||||
info!("accounts command matched");
|
||||
|
||||
let max_table_width = m
|
||||
.value_of("max-table-width")
|
||||
.and_then(|width| width.parse::<usize>().ok());
|
||||
debug!("max table width: {:?}", max_table_width);
|
||||
|
||||
Some(Cmd::List(max_table_width))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
info!("<< account command matcher");
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
/// Represents the account subcommands.
|
||||
pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
|
||||
vec![SubCommand::with_name("accounts")
|
||||
.aliases(&["account", "acc", "a"])
|
||||
.about("Lists accounts")
|
||||
.arg(table_arg::max_width())]
|
||||
}
|
||||
|
||||
/// Represents the user account name argument.
|
||||
/// This argument allows the user to select a different account than the default one.
|
||||
/// This argument allows the user to select a different account than
|
||||
/// the default one.
|
||||
pub fn name_arg<'a>() -> Arg<'a, 'a> {
|
||||
Arg::with_name("account")
|
||||
.long("account")
|
||||
|
|
|
@ -29,6 +29,9 @@ pub struct AccountConfig {
|
|||
pub notify_query: String,
|
||||
/// Represents the watch commands.
|
||||
pub watch_cmds: Vec<String>,
|
||||
/// Represents the text/plain format as defined in the
|
||||
/// [RFC2646](https://www.ietf.org/rfc/rfc2646.txt)
|
||||
pub format: Format,
|
||||
|
||||
/// Represents mailbox aliases.
|
||||
pub mailboxes: HashMap<String, String>,
|
||||
|
@ -66,11 +69,13 @@ impl<'a> AccountConfig {
|
|||
.accounts
|
||||
.iter()
|
||||
.find(|(_, account)| match account {
|
||||
#[cfg(feature = "imap-backend")]
|
||||
DeserializedAccountConfig::Imap(account) => account.default.unwrap_or_default(),
|
||||
#[cfg(feature = "maildir-backend")]
|
||||
DeserializedAccountConfig::Maildir(account) => {
|
||||
account.default.unwrap_or_default()
|
||||
}
|
||||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
DeserializedAccountConfig::Notmuch(account) => {
|
||||
account.default.unwrap_or_default()
|
||||
}
|
||||
|
@ -148,6 +153,7 @@ impl<'a> AccountConfig {
|
|||
.or_else(|| config.watch_cmds.as_ref())
|
||||
.unwrap_or(&vec![])
|
||||
.to_owned(),
|
||||
format: base_account.format.unwrap_or_default(),
|
||||
mailboxes: base_account.mailboxes.clone(),
|
||||
default: base_account.default.unwrap_or_default(),
|
||||
email: base_account.email.to_owned(),
|
||||
|
@ -165,6 +171,7 @@ impl<'a> AccountConfig {
|
|||
trace!("account config: {:?}", account_config);
|
||||
|
||||
let backend_config = match account {
|
||||
#[cfg(feature = "imap-backend")]
|
||||
DeserializedAccountConfig::Imap(config) => BackendConfig::Imap(ImapBackendConfig {
|
||||
imap_host: config.imap_host.clone(),
|
||||
imap_port: config.imap_port.clone(),
|
||||
|
@ -173,12 +180,13 @@ impl<'a> AccountConfig {
|
|||
imap_login: config.imap_login.clone(),
|
||||
imap_passwd_cmd: config.imap_passwd_cmd.clone(),
|
||||
}),
|
||||
#[cfg(feature = "maildir-backend")]
|
||||
DeserializedAccountConfig::Maildir(config) => {
|
||||
BackendConfig::Maildir(MaildirBackendConfig {
|
||||
maildir_dir: shellexpand::full(&config.maildir_dir)?.to_string().into(),
|
||||
})
|
||||
}
|
||||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
DeserializedAccountConfig::Notmuch(config) => {
|
||||
BackendConfig::Notmuch(NotmuchBackendConfig {
|
||||
notmuch_database_dir: shellexpand::full(&config.notmuch_database_dir)?
|
||||
|
@ -311,13 +319,16 @@ impl<'a> AccountConfig {
|
|||
/// Represents all existing kind of account (backend).
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BackendConfig {
|
||||
#[cfg(feature = "imap-backend")]
|
||||
Imap(ImapBackendConfig),
|
||||
#[cfg(feature = "maildir-backend")]
|
||||
Maildir(MaildirBackendConfig),
|
||||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
Notmuch(NotmuchBackendConfig),
|
||||
}
|
||||
|
||||
/// Represents the IMAP backend.
|
||||
#[cfg(feature = "imap-backend")]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ImapBackendConfig {
|
||||
/// Represents the IMAP host.
|
||||
|
@ -334,6 +345,7 @@ pub struct ImapBackendConfig {
|
|||
pub imap_passwd_cmd: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "imap-backend")]
|
||||
impl ImapBackendConfig {
|
||||
/// Gets the IMAP password of the user account.
|
||||
pub fn imap_passwd(&self) -> Result<String> {
|
||||
|
@ -346,6 +358,7 @@ impl ImapBackendConfig {
|
|||
}
|
||||
|
||||
/// Represents the Maildir backend.
|
||||
#[cfg(feature = "maildir-backend")]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct MaildirBackendConfig {
|
||||
/// Represents the Maildir directory path.
|
||||
|
@ -353,7 +366,7 @@ pub struct MaildirBackendConfig {
|
|||
}
|
||||
|
||||
/// Represents the Notmuch backend.
|
||||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct NotmuchBackendConfig {
|
||||
/// Represents the Notmuch database path.
|
||||
|
|
132
src/config/account_handlers.rs
Normal file
132
src/config/account_handlers.rs
Normal file
|
@ -0,0 +1,132 @@
|
|||
//! Account handlers module.
|
||||
//!
|
||||
//! This module gathers all account actions triggered by the CLI.
|
||||
|
||||
use anyhow::Result;
|
||||
use log::{info, trace};
|
||||
|
||||
use crate::{
|
||||
config::{AccountConfig, Accounts, DeserializedConfig},
|
||||
output::{PrintTableOpts, PrinterService},
|
||||
};
|
||||
|
||||
/// Lists all accounts.
|
||||
pub fn list<'a, P: PrinterService>(
|
||||
max_width: Option<usize>,
|
||||
config: &DeserializedConfig,
|
||||
account_config: &AccountConfig,
|
||||
printer: &mut P,
|
||||
) -> Result<()> {
|
||||
info!(">> account list handler");
|
||||
|
||||
let accounts: Accounts = config.accounts.iter().into();
|
||||
trace!("accounts: {:?}", accounts);
|
||||
|
||||
printer.print_table(
|
||||
Box::new(accounts),
|
||||
PrintTableOpts {
|
||||
format: &account_config.format,
|
||||
max_width,
|
||||
},
|
||||
)?;
|
||||
|
||||
info!("<< account list handler");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{collections::HashMap, fmt::Debug, io, iter::FromIterator};
|
||||
use termcolor::ColorSpec;
|
||||
|
||||
use crate::{
|
||||
config::{DeserializedAccountConfig, DeserializedImapAccountConfig},
|
||||
output::{Print, PrintTable, WriteColor},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_should_match_cmds_accounts() {
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct StringWritter {
|
||||
content: String,
|
||||
}
|
||||
|
||||
impl io::Write for StringWritter {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.content
|
||||
.push_str(&String::from_utf8(buf.to_vec()).unwrap());
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.content = String::default();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl termcolor::WriteColor for StringWritter {
|
||||
fn supports_color(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_color(&mut self, _spec: &ColorSpec) -> io::Result<()> {
|
||||
io::Result::Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> io::Result<()> {
|
||||
io::Result::Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteColor for StringWritter {}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct PrinterServiceTest {
|
||||
pub writter: StringWritter,
|
||||
}
|
||||
|
||||
impl PrinterService for PrinterServiceTest {
|
||||
fn print_table<T: Debug + PrintTable + erased_serde::Serialize + ?Sized>(
|
||||
&mut self,
|
||||
data: Box<T>,
|
||||
opts: PrintTableOpts,
|
||||
) -> Result<()> {
|
||||
data.print_table(&mut self.writter, opts)?;
|
||||
Ok(())
|
||||
}
|
||||
fn print<T: serde::Serialize + Print>(&mut self, _data: T) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn is_json(&self) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
let config = DeserializedConfig {
|
||||
accounts: HashMap::from_iter([(
|
||||
"account-1".into(),
|
||||
DeserializedAccountConfig::Imap(DeserializedImapAccountConfig {
|
||||
default: Some(true),
|
||||
..DeserializedImapAccountConfig::default()
|
||||
}),
|
||||
)]),
|
||||
..DeserializedConfig::default()
|
||||
};
|
||||
|
||||
let account_config = AccountConfig::default();
|
||||
let mut printer = PrinterServiceTest::default();
|
||||
|
||||
assert!(list(None, &config, &account_config, &mut printer).is_ok());
|
||||
assert_eq!(
|
||||
concat![
|
||||
"\n",
|
||||
"NAME │BACKEND │DEFAULT \n",
|
||||
"account-1 │imap │yes \n",
|
||||
"\n"
|
||||
],
|
||||
printer.writter.content
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
use serde::Deserialize;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use crate::config::Format;
|
||||
|
||||
pub trait ToDeserializedBaseAccountConfig {
|
||||
fn to_base(&self) -> DeserializedBaseAccountConfig;
|
||||
}
|
||||
|
@ -9,18 +11,22 @@ pub trait ToDeserializedBaseAccountConfig {
|
|||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum DeserializedAccountConfig {
|
||||
#[cfg(feature = "imap-backend")]
|
||||
Imap(DeserializedImapAccountConfig),
|
||||
#[cfg(feature = "maildir-backend")]
|
||||
Maildir(DeserializedMaildirAccountConfig),
|
||||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
Notmuch(DeserializedNotmuchAccountConfig),
|
||||
}
|
||||
|
||||
impl ToDeserializedBaseAccountConfig for DeserializedAccountConfig {
|
||||
fn to_base(&self) -> DeserializedBaseAccountConfig {
|
||||
match self {
|
||||
#[cfg(feature = "imap-backend")]
|
||||
Self::Imap(config) => config.to_base(),
|
||||
#[cfg(feature = "maildir-backend")]
|
||||
Self::Maildir(config) => config.to_base(),
|
||||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
Self::Notmuch(config) => config.to_base(),
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +53,9 @@ macro_rules! make_account_config {
|
|||
pub notify_query: Option<String>,
|
||||
/// Overrides the watch commands for this account.
|
||||
pub watch_cmds: Option<Vec<String>>,
|
||||
/// Represents the text/plain format as defined in the
|
||||
/// [RFC2646](https://www.ietf.org/rfc/rfc2646.txt)
|
||||
pub format: Option<Format>,
|
||||
|
||||
/// Makes this account the default one.
|
||||
pub default: Option<bool>,
|
||||
|
@ -89,6 +98,7 @@ macro_rules! make_account_config {
|
|||
notify_cmd: self.notify_cmd.clone(),
|
||||
notify_query: self.notify_query.clone(),
|
||||
watch_cmds: self.watch_cmds.clone(),
|
||||
format: self.format.clone(),
|
||||
|
||||
default: self.default.clone(),
|
||||
email: self.email.clone(),
|
||||
|
@ -112,6 +122,7 @@ macro_rules! make_account_config {
|
|||
|
||||
make_account_config!(DeserializedBaseAccountConfig,);
|
||||
|
||||
#[cfg(feature = "imap-backend")]
|
||||
make_account_config!(
|
||||
DeserializedImapAccountConfig,
|
||||
imap_host: String,
|
||||
|
@ -122,9 +133,10 @@ make_account_config!(
|
|||
imap_passwd_cmd: String
|
||||
);
|
||||
|
||||
#[cfg(feature = "maildir-backend")]
|
||||
make_account_config!(DeserializedMaildirAccountConfig, maildir_dir: String);
|
||||
|
||||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
make_account_config!(
|
||||
DeserializedNotmuchAccountConfig,
|
||||
notmuch_database_dir: String
|
||||
|
|
23
src/config/format.rs
Normal file
23
src/config/format.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
/// Represents the text/plain format as defined in the [RFC2646]. The
|
||||
/// format is then used by the table system to adjust the way it is
|
||||
/// rendered.
|
||||
///
|
||||
/// [RFC2646]: https://www.ietf.org/rfc/rfc2646.txt
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
|
||||
#[serde(tag = "type", content = "width", rename_all = "lowercase")]
|
||||
pub enum Format {
|
||||
// Forces the content width with a fixed amount of pixels.
|
||||
Fixed(usize),
|
||||
// Makes the content fit the terminal.
|
||||
Auto,
|
||||
// Does not restrict the content.
|
||||
Flowed,
|
||||
}
|
||||
|
||||
impl Default for Format {
|
||||
fn default() -> Self {
|
||||
Self::Auto
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
//! This barrel module provides everything related to the user configuration.
|
||||
|
||||
pub mod config_args;
|
||||
pub mod deserialized_config;
|
||||
pub use deserialized_config::*;
|
||||
|
||||
pub mod account_args;
|
||||
pub mod account_config;
|
||||
pub use account_config::*;
|
||||
pub mod deserialized_account_config;
|
||||
pub use deserialized_account_config::*;
|
76
src/lib.rs
76
src/lib.rs
|
@ -2,26 +2,26 @@ pub mod mbox {
|
|||
pub mod mbox;
|
||||
pub use mbox::*;
|
||||
|
||||
pub mod mbox_arg;
|
||||
pub mod mbox_handler;
|
||||
pub mod mbox_args;
|
||||
pub mod mbox_handlers;
|
||||
}
|
||||
|
||||
pub mod msg {
|
||||
pub mod envelope;
|
||||
pub use envelope::*;
|
||||
|
||||
pub mod msg_arg;
|
||||
pub mod msg_args;
|
||||
|
||||
pub mod msg_handler;
|
||||
pub mod msg_handlers;
|
||||
pub mod msg_utils;
|
||||
|
||||
pub mod flag_arg;
|
||||
pub mod flag_handler;
|
||||
pub mod flag_args;
|
||||
pub mod flag_handlers;
|
||||
|
||||
pub mod tpl_arg;
|
||||
pub use tpl_arg::TplOverride;
|
||||
pub mod tpl_args;
|
||||
pub use tpl_args::TplOverride;
|
||||
|
||||
pub mod tpl_handler;
|
||||
pub mod tpl_handlers;
|
||||
|
||||
pub mod msg_entity;
|
||||
pub use msg_entity::*;
|
||||
|
@ -34,37 +34,40 @@ pub mod msg {
|
|||
}
|
||||
|
||||
pub mod backends {
|
||||
pub use backend::*;
|
||||
pub mod backend;
|
||||
pub use backend::*;
|
||||
|
||||
pub use id_mapper::*;
|
||||
pub mod id_mapper;
|
||||
pub use id_mapper::*;
|
||||
|
||||
pub use self::imap::*;
|
||||
#[cfg(feature = "imap-backend")]
|
||||
pub mod imap {
|
||||
pub mod imap_arg;
|
||||
pub mod imap_args;
|
||||
|
||||
pub use imap_backend::*;
|
||||
pub mod imap_backend;
|
||||
pub use imap_backend::*;
|
||||
|
||||
pub mod imap_handler;
|
||||
pub mod imap_handlers;
|
||||
|
||||
pub use imap_mbox::*;
|
||||
pub mod imap_mbox;
|
||||
pub use imap_mbox::*;
|
||||
|
||||
pub use imap_mbox_attr::*;
|
||||
pub mod imap_mbox_attr;
|
||||
pub use imap_mbox_attr::*;
|
||||
|
||||
pub use imap_envelope::*;
|
||||
pub mod imap_envelope;
|
||||
pub use imap_envelope::*;
|
||||
|
||||
pub use imap_flag::*;
|
||||
pub mod imap_flag;
|
||||
pub use imap_flag::*;
|
||||
|
||||
pub mod msg_sort_criterion;
|
||||
}
|
||||
|
||||
pub use self::maildir::*;
|
||||
#[cfg(feature = "imap-backend")]
|
||||
pub use self::imap::*;
|
||||
|
||||
#[cfg(feature = "maildir-backend")]
|
||||
pub mod maildir {
|
||||
pub mod maildir_backend;
|
||||
pub use maildir_backend::*;
|
||||
|
@ -79,9 +82,10 @@ pub mod backends {
|
|||
pub use maildir_flag::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "notmuch")]
|
||||
pub use self::notmuch::*;
|
||||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "maildir-backend")]
|
||||
pub use self::maildir::*;
|
||||
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
pub mod notmuch {
|
||||
pub mod notmuch_backend;
|
||||
pub use notmuch_backend::*;
|
||||
|
@ -92,6 +96,9 @@ pub mod backends {
|
|||
pub mod notmuch_envelope;
|
||||
pub use notmuch_envelope::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
pub use self::notmuch::*;
|
||||
}
|
||||
|
||||
pub mod smtp {
|
||||
|
@ -99,7 +106,28 @@ pub mod smtp {
|
|||
pub use smtp_service::*;
|
||||
}
|
||||
|
||||
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;
|
||||
pub mod account_handlers;
|
||||
|
||||
pub mod account;
|
||||
pub use account::*;
|
||||
|
||||
pub mod account_config;
|
||||
pub use account_config::*;
|
||||
|
||||
pub mod format;
|
||||
pub use format::*;
|
||||
}
|
||||
|
||||
pub mod compl;
|
||||
pub mod config;
|
||||
pub mod output;
|
||||
pub mod ui;
|
||||
|
|
191
src/main.rs
191
src/main.rs
|
@ -3,35 +3,46 @@ use std::{convert::TryFrom, env};
|
|||
use url::Url;
|
||||
|
||||
use himalaya::{
|
||||
backends::{imap_arg, imap_handler, Backend, ImapBackend, MaildirBackend},
|
||||
compl::{compl_arg, compl_handler},
|
||||
backends::Backend,
|
||||
compl::{compl_args, compl_handlers},
|
||||
config::{
|
||||
account_args, config_args, AccountConfig, BackendConfig, DeserializedConfig,
|
||||
DEFAULT_INBOX_FOLDER,
|
||||
account_args, account_handlers, config_args, AccountConfig, BackendConfig,
|
||||
DeserializedConfig, DEFAULT_INBOX_FOLDER,
|
||||
},
|
||||
mbox::{mbox_arg, mbox_handler},
|
||||
msg::{flag_arg, flag_handler, msg_arg, msg_handler, tpl_arg, tpl_handler},
|
||||
output::{output_arg, OutputFmt, StdoutPrinter},
|
||||
mbox::{mbox_args, mbox_handlers},
|
||||
msg::{flag_args, flag_handlers, msg_args, msg_handlers, tpl_args, tpl_handlers},
|
||||
output::{output_args, OutputFmt, StdoutPrinter},
|
||||
smtp::LettreService,
|
||||
};
|
||||
|
||||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "imap-backend")]
|
||||
use himalaya::backends::{imap_args, imap_handlers, ImapBackend};
|
||||
|
||||
#[cfg(feature = "maildir-backend")]
|
||||
use himalaya::backends::MaildirBackend;
|
||||
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
use himalaya::{backends::NotmuchBackend, config::MaildirBackendConfig};
|
||||
|
||||
fn create_app<'a>() -> clap::App<'a, 'a> {
|
||||
clap::App::new(env!("CARGO_PKG_NAME"))
|
||||
let app = clap::App::new(env!("CARGO_PKG_NAME"))
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.about(env!("CARGO_PKG_DESCRIPTION"))
|
||||
.author(env!("CARGO_PKG_AUTHORS"))
|
||||
.global_setting(clap::AppSettings::GlobalVersion)
|
||||
.arg(&config_args::path_arg())
|
||||
.arg(&account_args::name_arg())
|
||||
.args(&output_arg::args())
|
||||
.arg(mbox_arg::source_arg())
|
||||
.subcommands(compl_arg::subcmds())
|
||||
.subcommands(imap_arg::subcmds())
|
||||
.subcommands(mbox_arg::subcmds())
|
||||
.subcommands(msg_arg::subcmds())
|
||||
.args(&output_args::args())
|
||||
.arg(mbox_args::source_arg())
|
||||
.subcommands(compl_args::subcmds())
|
||||
.subcommands(account_args::subcmds())
|
||||
.subcommands(mbox_args::subcmds())
|
||||
.subcommands(msg_args::subcmds());
|
||||
|
||||
#[cfg(feature = "imap-backend")]
|
||||
let app = app.subcommands(imap_args::subcmds());
|
||||
|
||||
app
|
||||
}
|
||||
|
||||
#[allow(clippy::single_match)]
|
||||
|
@ -49,22 +60,29 @@ fn main() -> Result<()> {
|
|||
let url = Url::parse(&raw_args[1])?;
|
||||
let mut smtp = LettreService::from(&account_config);
|
||||
|
||||
#[cfg(feature = "imap-backend")]
|
||||
let mut imap;
|
||||
|
||||
#[cfg(feature = "maildir-backend")]
|
||||
let mut maildir;
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
let maildir_config: MaildirBackendConfig;
|
||||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
let mut notmuch;
|
||||
|
||||
let backend: Box<&mut dyn Backend> = match backend_config {
|
||||
#[cfg(feature = "imap-backend")]
|
||||
BackendConfig::Imap(ref imap_config) => {
|
||||
imap = ImapBackend::new(&account_config, imap_config);
|
||||
Box::new(&mut imap)
|
||||
}
|
||||
#[cfg(feature = "maildir-backend")]
|
||||
BackendConfig::Maildir(ref maildir_config) => {
|
||||
maildir = MaildirBackend::new(&account_config, maildir_config);
|
||||
Box::new(&mut maildir)
|
||||
}
|
||||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
BackendConfig::Notmuch(ref notmuch_config) => {
|
||||
maildir_config = MaildirBackendConfig {
|
||||
maildir_dir: notmuch_config.notmuch_database_dir.clone(),
|
||||
|
@ -75,7 +93,7 @@ fn main() -> Result<()> {
|
|||
}
|
||||
};
|
||||
|
||||
return msg_handler::mailto(&url, &account_config, &mut printer, backend, &mut smtp);
|
||||
return msg_handlers::mailto(&url, &account_config, &mut printer, backend, &mut smtp);
|
||||
}
|
||||
|
||||
let app = create_app();
|
||||
|
@ -83,9 +101,9 @@ fn main() -> Result<()> {
|
|||
|
||||
// Check completion command BEFORE entities and services initialization.
|
||||
// Related issue: https://github.com/soywod/himalaya/issues/115.
|
||||
match compl_arg::matches(&m)? {
|
||||
Some(compl_arg::Command::Generate(shell)) => {
|
||||
return compl_handler::generate(create_app(), shell);
|
||||
match compl_args::matches(&m)? {
|
||||
Some(compl_args::Command::Generate(shell)) => {
|
||||
return compl_handlers::generate(create_app(), shell);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
@ -99,22 +117,29 @@ fn main() -> Result<()> {
|
|||
.or_else(|| account_config.mailboxes.get("inbox").map(|s| s.as_str()))
|
||||
.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||
let mut printer = StdoutPrinter::try_from(m.value_of("output"))?;
|
||||
#[cfg(feature = "imap-backend")]
|
||||
let mut imap;
|
||||
|
||||
#[cfg(feature = "maildir-backend")]
|
||||
let mut maildir;
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
let maildir_config: MaildirBackendConfig;
|
||||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
let mut notmuch;
|
||||
|
||||
let backend: Box<&mut dyn Backend> = match backend_config {
|
||||
#[cfg(feature = "imap-backend")]
|
||||
BackendConfig::Imap(ref imap_config) => {
|
||||
imap = ImapBackend::new(&account_config, imap_config);
|
||||
Box::new(&mut imap)
|
||||
}
|
||||
#[cfg(feature = "maildir-backend")]
|
||||
BackendConfig::Maildir(ref maildir_config) => {
|
||||
maildir = MaildirBackend::new(&account_config, maildir_config);
|
||||
Box::new(&mut maildir)
|
||||
}
|
||||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
BackendConfig::Notmuch(ref notmuch_config) => {
|
||||
maildir_config = MaildirBackendConfig {
|
||||
maildir_dir: notmuch_config.notmuch_database_dir.clone(),
|
||||
|
@ -128,40 +153,50 @@ fn main() -> Result<()> {
|
|||
let mut smtp = LettreService::from(&account_config);
|
||||
|
||||
// Check IMAP commands.
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
#[cfg(feature = "imap-backend")]
|
||||
if let BackendConfig::Imap(ref imap_config) = backend_config {
|
||||
let mut imap = ImapBackend::new(&account_config, imap_config);
|
||||
match imap_arg::matches(&m)? {
|
||||
Some(imap_arg::Command::Notify(keepalive)) => {
|
||||
return imap_handler::notify(keepalive, mbox, &mut imap);
|
||||
match imap_args::matches(&m)? {
|
||||
Some(imap_args::Command::Notify(keepalive)) => {
|
||||
return imap_handlers::notify(keepalive, mbox, &mut imap);
|
||||
}
|
||||
Some(imap_arg::Command::Watch(keepalive)) => {
|
||||
return imap_handler::watch(keepalive, mbox, &mut imap);
|
||||
Some(imap_args::Command::Watch(keepalive)) => {
|
||||
return imap_handlers::watch(keepalive, mbox, &mut imap);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// Check account commands.
|
||||
match account_args::matches(&m)? {
|
||||
Some(account_args::Cmd::List(max_width)) => {
|
||||
return account_handlers::list(max_width, &config, &account_config, &mut printer);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Check mailbox commands.
|
||||
match mbox_arg::matches(&m)? {
|
||||
Some(mbox_arg::Cmd::List(max_width)) => {
|
||||
return mbox_handler::list(max_width, &mut printer, backend);
|
||||
match mbox_args::matches(&m)? {
|
||||
Some(mbox_args::Cmd::List(max_width)) => {
|
||||
return mbox_handlers::list(max_width, &account_config, &mut printer, backend);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Check message commands.
|
||||
match msg_arg::matches(&m)? {
|
||||
Some(msg_arg::Cmd::Attachments(seq)) => {
|
||||
return msg_handler::attachments(seq, mbox, &account_config, &mut printer, backend);
|
||||
match msg_args::matches(&m)? {
|
||||
Some(msg_args::Cmd::Attachments(seq)) => {
|
||||
return msg_handlers::attachments(seq, mbox, &account_config, &mut printer, backend);
|
||||
}
|
||||
Some(msg_arg::Cmd::Copy(seq, mbox_dst)) => {
|
||||
return msg_handler::copy(seq, mbox, mbox_dst, &mut printer, backend);
|
||||
Some(msg_args::Cmd::Copy(seq, mbox_dst)) => {
|
||||
return msg_handlers::copy(seq, mbox, mbox_dst, &mut printer, backend);
|
||||
}
|
||||
Some(msg_arg::Cmd::Delete(seq)) => {
|
||||
return msg_handler::delete(seq, mbox, &mut printer, backend);
|
||||
Some(msg_args::Cmd::Delete(seq)) => {
|
||||
return msg_handlers::delete(seq, mbox, &mut printer, backend);
|
||||
}
|
||||
Some(msg_arg::Cmd::Forward(seq, attachment_paths, encrypt)) => {
|
||||
return msg_handler::forward(
|
||||
Some(msg_args::Cmd::Forward(seq, attachment_paths, encrypt)) => {
|
||||
return msg_handlers::forward(
|
||||
seq,
|
||||
attachment_paths,
|
||||
encrypt,
|
||||
|
@ -172,8 +207,8 @@ fn main() -> Result<()> {
|
|||
&mut smtp,
|
||||
);
|
||||
}
|
||||
Some(msg_arg::Cmd::List(max_width, page_size, page)) => {
|
||||
return msg_handler::list(
|
||||
Some(msg_args::Cmd::List(max_width, page_size, page)) => {
|
||||
return msg_handlers::list(
|
||||
max_width,
|
||||
page_size,
|
||||
page,
|
||||
|
@ -183,14 +218,14 @@ fn main() -> Result<()> {
|
|||
backend,
|
||||
);
|
||||
}
|
||||
Some(msg_arg::Cmd::Move(seq, mbox_dst)) => {
|
||||
return msg_handler::move_(seq, mbox, mbox_dst, &mut printer, backend);
|
||||
Some(msg_args::Cmd::Move(seq, mbox_dst)) => {
|
||||
return msg_handlers::move_(seq, mbox, mbox_dst, &mut printer, backend);
|
||||
}
|
||||
Some(msg_arg::Cmd::Read(seq, text_mime, raw)) => {
|
||||
return msg_handler::read(seq, text_mime, raw, mbox, &mut printer, backend);
|
||||
Some(msg_args::Cmd::Read(seq, text_mime, raw)) => {
|
||||
return msg_handlers::read(seq, text_mime, raw, mbox, &mut printer, backend);
|
||||
}
|
||||
Some(msg_arg::Cmd::Reply(seq, all, attachment_paths, encrypt)) => {
|
||||
return msg_handler::reply(
|
||||
Some(msg_args::Cmd::Reply(seq, all, attachment_paths, encrypt)) => {
|
||||
return msg_handlers::reply(
|
||||
seq,
|
||||
all,
|
||||
attachment_paths,
|
||||
|
@ -202,11 +237,11 @@ fn main() -> Result<()> {
|
|||
&mut smtp,
|
||||
);
|
||||
}
|
||||
Some(msg_arg::Cmd::Save(raw_msg)) => {
|
||||
return msg_handler::save(mbox, raw_msg, &mut printer, backend);
|
||||
Some(msg_args::Cmd::Save(raw_msg)) => {
|
||||
return msg_handlers::save(mbox, raw_msg, &mut printer, backend);
|
||||
}
|
||||
Some(msg_arg::Cmd::Search(query, max_width, page_size, page)) => {
|
||||
return msg_handler::search(
|
||||
Some(msg_args::Cmd::Search(query, max_width, page_size, page)) => {
|
||||
return msg_handlers::search(
|
||||
query,
|
||||
max_width,
|
||||
page_size,
|
||||
|
@ -217,8 +252,8 @@ fn main() -> Result<()> {
|
|||
backend,
|
||||
);
|
||||
}
|
||||
Some(msg_arg::Cmd::Sort(criteria, query, max_width, page_size, page)) => {
|
||||
return msg_handler::sort(
|
||||
Some(msg_args::Cmd::Sort(criteria, query, max_width, page_size, page)) => {
|
||||
return msg_handlers::sort(
|
||||
criteria,
|
||||
query,
|
||||
max_width,
|
||||
|
@ -230,11 +265,11 @@ fn main() -> Result<()> {
|
|||
backend,
|
||||
);
|
||||
}
|
||||
Some(msg_arg::Cmd::Send(raw_msg)) => {
|
||||
return msg_handler::send(raw_msg, &account_config, &mut printer, backend, &mut smtp);
|
||||
Some(msg_args::Cmd::Send(raw_msg)) => {
|
||||
return msg_handlers::send(raw_msg, &account_config, &mut printer, backend, &mut smtp);
|
||||
}
|
||||
Some(msg_arg::Cmd::Write(atts, encrypt)) => {
|
||||
return msg_handler::write(
|
||||
Some(msg_args::Cmd::Write(atts, encrypt)) => {
|
||||
return msg_handlers::write(
|
||||
atts,
|
||||
encrypt,
|
||||
&account_config,
|
||||
|
@ -243,24 +278,24 @@ fn main() -> Result<()> {
|
|||
&mut smtp,
|
||||
);
|
||||
}
|
||||
Some(msg_arg::Cmd::Flag(m)) => match m {
|
||||
Some(flag_arg::Cmd::Set(seq_range, flags)) => {
|
||||
return flag_handler::set(seq_range, mbox, &flags, &mut printer, backend);
|
||||
Some(msg_args::Cmd::Flag(m)) => match m {
|
||||
Some(flag_args::Cmd::Set(seq_range, flags)) => {
|
||||
return flag_handlers::set(seq_range, mbox, &flags, &mut printer, backend);
|
||||
}
|
||||
Some(flag_arg::Cmd::Add(seq_range, flags)) => {
|
||||
return flag_handler::add(seq_range, mbox, &flags, &mut printer, backend);
|
||||
Some(flag_args::Cmd::Add(seq_range, flags)) => {
|
||||
return flag_handlers::add(seq_range, mbox, &flags, &mut printer, backend);
|
||||
}
|
||||
Some(flag_arg::Cmd::Remove(seq_range, flags)) => {
|
||||
return flag_handler::remove(seq_range, mbox, &flags, &mut printer, backend);
|
||||
Some(flag_args::Cmd::Remove(seq_range, flags)) => {
|
||||
return flag_handlers::remove(seq_range, mbox, &flags, &mut printer, backend);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Some(msg_arg::Cmd::Tpl(m)) => match m {
|
||||
Some(tpl_arg::Cmd::New(tpl)) => {
|
||||
return tpl_handler::new(tpl, &account_config, &mut printer);
|
||||
Some(msg_args::Cmd::Tpl(m)) => match m {
|
||||
Some(tpl_args::Cmd::New(tpl)) => {
|
||||
return tpl_handlers::new(tpl, &account_config, &mut printer);
|
||||
}
|
||||
Some(tpl_arg::Cmd::Reply(seq, all, tpl)) => {
|
||||
return tpl_handler::reply(
|
||||
Some(tpl_args::Cmd::Reply(seq, all, tpl)) => {
|
||||
return tpl_handlers::reply(
|
||||
seq,
|
||||
all,
|
||||
tpl,
|
||||
|
@ -270,8 +305,8 @@ fn main() -> Result<()> {
|
|||
backend,
|
||||
);
|
||||
}
|
||||
Some(tpl_arg::Cmd::Forward(seq, tpl)) => {
|
||||
return tpl_handler::forward(
|
||||
Some(tpl_args::Cmd::Forward(seq, tpl)) => {
|
||||
return tpl_handlers::forward(
|
||||
seq,
|
||||
tpl,
|
||||
mbox,
|
||||
|
@ -280,11 +315,11 @@ fn main() -> Result<()> {
|
|||
backend,
|
||||
);
|
||||
}
|
||||
Some(tpl_arg::Cmd::Save(atts, tpl)) => {
|
||||
return tpl_handler::save(mbox, &account_config, atts, tpl, &mut printer, backend);
|
||||
Some(tpl_args::Cmd::Save(atts, tpl)) => {
|
||||
return tpl_handlers::save(mbox, &account_config, atts, tpl, &mut printer, backend);
|
||||
}
|
||||
Some(tpl_arg::Cmd::Send(atts, tpl)) => {
|
||||
return tpl_handler::send(
|
||||
Some(tpl_args::Cmd::Send(atts, tpl)) => {
|
||||
return tpl_handlers::send(
|
||||
mbox,
|
||||
&account_config,
|
||||
atts,
|
||||
|
|
|
@ -7,19 +7,27 @@ use log::{info, trace};
|
|||
|
||||
use crate::{
|
||||
backends::Backend,
|
||||
config::AccountConfig,
|
||||
output::{PrintTableOpts, PrinterService},
|
||||
};
|
||||
|
||||
/// Lists all mailboxes.
|
||||
pub fn list<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
|
||||
max_width: Option<usize>,
|
||||
config: &AccountConfig,
|
||||
printer: &mut P,
|
||||
backend: Box<&'a mut B>,
|
||||
) -> Result<()> {
|
||||
info!("entering list mailbox handler");
|
||||
let mboxes = backend.get_mboxes()?;
|
||||
trace!("mailboxes: {:?}", mboxes);
|
||||
printer.print_table(mboxes, PrintTableOpts { max_width })
|
||||
printer.print_table(
|
||||
mboxes,
|
||||
PrintTableOpts {
|
||||
format: &config.format,
|
||||
max_width,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -159,11 +167,12 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
let config = AccountConfig::default();
|
||||
let mut printer = PrinterServiceTest::default();
|
||||
let mut backend = TestBackend {};
|
||||
let backend = Box::new(&mut backend);
|
||||
|
||||
assert!(list(None, &mut printer, backend).is_ok());
|
||||
assert!(list(None, &config, &mut printer, backend).is_ok());
|
||||
assert_eq!(
|
||||
concat![
|
||||
"\n",
|
|
@ -2,8 +2,7 @@
|
|||
//!
|
||||
//! This module regroups email address entities and converters.
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use log::trace;
|
||||
use anyhow::Result;
|
||||
use mailparse;
|
||||
use std::fmt::Debug;
|
||||
|
||||
|
@ -63,71 +62,3 @@ pub fn from_addrs_to_sendable_addrs(addrs: &Addrs) -> Result<Vec<lettre::Address
|
|||
}
|
||||
Ok(sendable_addrs)
|
||||
}
|
||||
|
||||
/// Converts a [`imap_proto::Address`] into an address.
|
||||
pub fn from_imap_addr_to_addr(addr: &imap_proto::Address) -> Result<Addr> {
|
||||
let name = addr
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|name| {
|
||||
rfc2047_decoder::decode(&name.to_vec())
|
||||
.context("cannot decode address name")
|
||||
.map(Some)
|
||||
})
|
||||
.unwrap_or(Ok(None))?;
|
||||
let mbox = addr
|
||||
.mailbox
|
||||
.as_ref()
|
||||
.map(|mbox| {
|
||||
rfc2047_decoder::decode(&mbox.to_vec())
|
||||
.context("cannot decode address mailbox")
|
||||
.map(Some)
|
||||
})
|
||||
.unwrap_or(Ok(None))?;
|
||||
let host = addr
|
||||
.host
|
||||
.as_ref()
|
||||
.map(|host| {
|
||||
rfc2047_decoder::decode(&host.to_vec())
|
||||
.context("cannot decode address host")
|
||||
.map(Some)
|
||||
})
|
||||
.unwrap_or(Ok(None))?;
|
||||
|
||||
trace!("parsing address from imap address");
|
||||
trace!("name: {:?}", name);
|
||||
trace!("mbox: {:?}", mbox);
|
||||
trace!("host: {:?}", host);
|
||||
|
||||
Ok(Addr::Single(mailparse::SingleInfo {
|
||||
display_name: name,
|
||||
addr: match host {
|
||||
Some(host) => format!("{}@{}", mbox.unwrap_or_default(), host),
|
||||
None => mbox.unwrap_or_default(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
/// Converts a list of [`imap_proto::Address`] into a list of addresses.
|
||||
pub fn from_imap_addrs_to_addrs(proto_addrs: &[imap_proto::Address]) -> Result<Addrs> {
|
||||
let mut addrs = vec![];
|
||||
for addr in proto_addrs {
|
||||
addrs.push(
|
||||
from_imap_addr_to_addr(addr).context(format!("cannot parse address {:?}", addr))?,
|
||||
);
|
||||
}
|
||||
Ok(addrs.into())
|
||||
}
|
||||
|
||||
/// Converts an optional list of [`imap_proto::Address`] into an optional list of addresses.
|
||||
pub fn from_imap_addrs_to_some_addrs(
|
||||
addrs: &Option<Vec<imap_proto::Address>>,
|
||||
) -> Result<Option<Addrs>> {
|
||||
Ok(
|
||||
if let Some(addrs) = addrs.as_deref().map(from_imap_addrs_to_addrs) {
|
||||
Some(addrs?)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use anyhow::Result;
|
|||
use clap::{self, App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
use log::{debug, info};
|
||||
|
||||
use crate::msg::msg_arg;
|
||||
use crate::msg::msg_args;
|
||||
|
||||
type SeqRange<'a> = &'a str;
|
||||
type Flags = String;
|
||||
|
@ -89,21 +89,21 @@ pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
|
|||
SubCommand::with_name("add")
|
||||
.aliases(&["a"])
|
||||
.about("Adds flags to a message")
|
||||
.arg(msg_arg::seq_range_arg())
|
||||
.arg(msg_args::seq_range_arg())
|
||||
.arg(flags_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("set")
|
||||
.aliases(&["s", "change", "c"])
|
||||
.about("Replaces all message flags")
|
||||
.arg(msg_arg::seq_range_arg())
|
||||
.arg(msg_args::seq_range_arg())
|
||||
.arg(flags_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("remove")
|
||||
.aliases(&["rem", "rm", "r", "delete", "del", "d"])
|
||||
.about("Removes flags from a message")
|
||||
.arg(msg_arg::seq_range_arg())
|
||||
.arg(msg_args::seq_range_arg())
|
||||
.arg(flags_arg()),
|
||||
)]
|
||||
}
|
|
@ -7,8 +7,8 @@ use clap::{self, App, Arg, ArgMatches, SubCommand};
|
|||
use log::{debug, info, trace};
|
||||
|
||||
use crate::{
|
||||
mbox::mbox_arg,
|
||||
msg::{flag_arg, msg_arg, tpl_arg},
|
||||
mbox::mbox_args,
|
||||
msg::{flag_args, msg_args, tpl_args},
|
||||
ui::table_arg,
|
||||
};
|
||||
|
||||
|
@ -43,8 +43,8 @@ pub enum Cmd<'a> {
|
|||
Send(RawMsg<'a>),
|
||||
Write(AttachmentPaths<'a>, Encrypt),
|
||||
|
||||
Flag(Option<flag_arg::Cmd<'a>>),
|
||||
Tpl(Option<tpl_arg::Cmd<'a>>),
|
||||
Flag(Option<flag_args::Cmd<'a>>),
|
||||
Tpl(Option<tpl_args::Cmd<'a>>),
|
||||
}
|
||||
|
||||
/// Message command matcher.
|
||||
|
@ -262,11 +262,11 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Cmd<'a>>> {
|
|||
}
|
||||
|
||||
if let Some(m) = m.subcommand_matches("template") {
|
||||
return Ok(Some(Cmd::Tpl(tpl_arg::matches(m)?)));
|
||||
return Ok(Some(Cmd::Tpl(tpl_args::matches(m)?)));
|
||||
}
|
||||
|
||||
if let Some(m) = m.subcommand_matches("flag") {
|
||||
return Ok(Some(Cmd::Flag(flag_arg::matches(m)?)));
|
||||
return Ok(Some(Cmd::Flag(flag_args::matches(m)?)));
|
||||
}
|
||||
|
||||
info!("default list command matched");
|
||||
|
@ -338,13 +338,13 @@ pub fn encrypt_arg<'a>() -> Arg<'a, 'a> {
|
|||
/// Message subcommands.
|
||||
pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
|
||||
vec![
|
||||
flag_arg::subcmds(),
|
||||
tpl_arg::subcmds(),
|
||||
flag_args::subcmds(),
|
||||
tpl_args::subcmds(),
|
||||
vec![
|
||||
SubCommand::with_name("attachments")
|
||||
.aliases(&["attachment", "att", "a"])
|
||||
.about("Downloads all message attachments")
|
||||
.arg(msg_arg::seq_arg()),
|
||||
.arg(msg_args::seq_arg()),
|
||||
SubCommand::with_name("list")
|
||||
.aliases(&["lst", "l"])
|
||||
.about("Lists all messages")
|
||||
|
@ -442,12 +442,12 @@ pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
|
|||
.aliases(&["cp", "c"])
|
||||
.about("Copies a message to the targetted mailbox")
|
||||
.arg(seq_arg())
|
||||
.arg(mbox_arg::target_arg()),
|
||||
.arg(mbox_args::target_arg()),
|
||||
SubCommand::with_name("move")
|
||||
.aliases(&["mv"])
|
||||
.about("Moves a message to the targetted mailbox")
|
||||
.arg(seq_arg())
|
||||
.arg(mbox_arg::target_arg()),
|
||||
.arg(mbox_args::target_arg()),
|
||||
SubCommand::with_name("delete")
|
||||
.aliases(&["del", "d", "remove", "rm"])
|
||||
.about("Deletes a message")
|
|
@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Error, Result};
|
|||
use chrono::{DateTime, FixedOffset};
|
||||
use html_escape;
|
||||
use lettre::message::{header::ContentType, Attachment, MultiPart, SinglePart};
|
||||
use log::{debug, info, trace};
|
||||
use log::{debug, info, trace, warn};
|
||||
use regex::Regex;
|
||||
use std::{collections::HashSet, convert::TryInto, env::temp_dir, fmt::Debug, fs, path::PathBuf};
|
||||
use uuid::Uuid;
|
||||
|
@ -170,9 +170,6 @@ impl Msg {
|
|||
// In-Reply-To
|
||||
self.in_reply_to = self.message_id.to_owned();
|
||||
|
||||
// From
|
||||
self.from = Some(vec![account_addr.clone()].into());
|
||||
|
||||
// To
|
||||
let addrs = self
|
||||
.reply_to
|
||||
|
@ -198,11 +195,6 @@ impl Msg {
|
|||
self.bcc = None;
|
||||
}
|
||||
|
||||
// Subject
|
||||
if !self.subject.starts_with("Re:") {
|
||||
self.subject = format!("Re: {}", self.subject);
|
||||
}
|
||||
|
||||
// Body
|
||||
let plain_content = {
|
||||
let date = self
|
||||
|
@ -236,6 +228,14 @@ impl Msg {
|
|||
|
||||
self.parts = Parts(vec![Part::new_text_plain(plain_content)]);
|
||||
|
||||
// Subject
|
||||
if !self.subject.starts_with("Re:") {
|
||||
self.subject = format!("Re: {}", self.subject);
|
||||
}
|
||||
|
||||
// From
|
||||
self.from = Some(vec![account_addr.clone()].into());
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
|
@ -648,6 +648,16 @@ impl Msg {
|
|||
"subject" => {
|
||||
msg.subject = val;
|
||||
}
|
||||
"date" => {
|
||||
msg.date = DateTime::parse_from_rfc2822(
|
||||
val.split_at(val.find(" (").unwrap_or_else(|| val.len())).0,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("cannot parse message date {:?}, skipping it", val);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
"from" => {
|
||||
msg.from = from_slice_to_addrs(val)
|
||||
.context(format!("cannot parse header {:?}", key))?
|
||||
|
|
|
@ -110,7 +110,13 @@ pub fn list<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
|
|||
debug!("page size: {}", page_size);
|
||||
let msgs = imap.get_envelopes(mbox, page_size, page)?;
|
||||
trace!("envelopes: {:?}", msgs);
|
||||
printer.print_table(msgs, PrintTableOpts { max_width })
|
||||
printer.print_table(
|
||||
msgs,
|
||||
PrintTableOpts {
|
||||
format: &config.format,
|
||||
max_width,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Parses and edits a message from a [mailto] URL string.
|
||||
|
@ -275,7 +281,13 @@ pub fn search<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
|
|||
debug!("page size: {}", page_size);
|
||||
let msgs = backend.search_envelopes(mbox, &query, "", page_size, page)?;
|
||||
trace!("messages: {:#?}", msgs);
|
||||
printer.print_table(msgs, PrintTableOpts { max_width })
|
||||
printer.print_table(
|
||||
msgs,
|
||||
PrintTableOpts {
|
||||
format: &config.format,
|
||||
max_width,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Paginates messages from the selected mailbox matching the specified query, sorted by the given criteria.
|
||||
|
@ -294,7 +306,13 @@ pub fn sort<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
|
|||
debug!("page size: {}", page_size);
|
||||
let msgs = backend.search_envelopes(mbox, &query, &sort, page_size, page)?;
|
||||
trace!("envelopes: {:#?}", msgs);
|
||||
printer.print_table(msgs, PrintTableOpts { max_width })
|
||||
printer.print_table(
|
||||
msgs,
|
||||
PrintTableOpts {
|
||||
format: &config.format,
|
||||
max_width,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Send a raw message.
|
|
@ -6,7 +6,7 @@ use anyhow::Result;
|
|||
use clap::{self, App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
use log::{debug, info, trace};
|
||||
|
||||
use crate::msg::msg_arg;
|
||||
use crate::msg::msg_args;
|
||||
|
||||
type Seq<'a> = &'a str;
|
||||
type ReplyAll = bool;
|
||||
|
@ -169,27 +169,27 @@ pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
|
|||
SubCommand::with_name("reply")
|
||||
.aliases(&["rep", "re", "r"])
|
||||
.about("Generates a reply message template")
|
||||
.arg(msg_arg::seq_arg())
|
||||
.arg(msg_arg::reply_all_arg())
|
||||
.arg(msg_args::seq_arg())
|
||||
.arg(msg_args::reply_all_arg())
|
||||
.args(&tpl_args()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("forward")
|
||||
.aliases(&["fwd", "fw", "f"])
|
||||
.about("Generates a forward message template")
|
||||
.arg(msg_arg::seq_arg())
|
||||
.arg(msg_args::seq_arg())
|
||||
.args(&tpl_args()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("save")
|
||||
.about("Saves a message based on the given template")
|
||||
.arg(&msg_arg::attachment_arg())
|
||||
.arg(&msg_args::attachment_arg())
|
||||
.arg(Arg::with_name("template").raw(true)),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("send")
|
||||
.about("Sends a message based on the given template")
|
||||
.arg(&msg_arg::attachment_arg())
|
||||
.arg(&msg_args::attachment_arg())
|
||||
.arg(Arg::with_name("template").raw(true)),
|
||||
)]
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
//! Module related to output formatting and printing.
|
||||
|
||||
pub mod output_arg;
|
||||
pub mod output_args;
|
||||
|
||||
pub mod output_utils;
|
||||
pub use output_utils::*;
|
||||
|
|
|
@ -2,6 +2,8 @@ use anyhow::Result;
|
|||
use std::io;
|
||||
use termcolor::{self, StandardStream};
|
||||
|
||||
use crate::config::Format;
|
||||
|
||||
pub trait WriteColor: io::Write + termcolor::WriteColor {}
|
||||
|
||||
impl WriteColor for StandardStream {}
|
||||
|
@ -10,6 +12,7 @@ pub trait PrintTable {
|
|||
fn print_table(&self, writter: &mut dyn WriteColor, opts: PrintTableOpts) -> Result<()>;
|
||||
}
|
||||
|
||||
pub struct PrintTableOpts {
|
||||
pub struct PrintTableOpts<'a> {
|
||||
pub format: &'a Format,
|
||||
pub max_width: Option<usize>,
|
||||
}
|
||||
|
|
|
@ -10,7 +10,10 @@ use termcolor::{Color, ColorSpec};
|
|||
use terminal_size;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::output::{Print, PrintTableOpts, WriteColor};
|
||||
use crate::{
|
||||
config::Format,
|
||||
output::{Print, PrintTableOpts, WriteColor},
|
||||
};
|
||||
|
||||
/// Defines the default terminal size.
|
||||
/// This is used when the size cannot be determined by the `terminal_size` crate.
|
||||
|
@ -35,7 +38,14 @@ pub struct Cell {
|
|||
impl Cell {
|
||||
pub fn new<T: AsRef<str>>(value: T) -> Self {
|
||||
Self {
|
||||
value: String::from(value.as_ref()).replace(&['\r', '\n', '\t'][..], ""),
|
||||
// Removes carriage returns, new line feeds, tabulations
|
||||
// and [variation selectors].
|
||||
//
|
||||
// [variation selectors]: https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block)
|
||||
value: String::from(value.as_ref()).replace(
|
||||
|c| ['\r', '\n', '\t', '\u{fe0e}', '\u{fe0f}'].contains(&c),
|
||||
"",
|
||||
),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
@ -159,10 +169,15 @@ where
|
|||
|
||||
/// Writes the table to the writter.
|
||||
fn print(writter: &mut dyn WriteColor, items: &[Self], opts: PrintTableOpts) -> Result<()> {
|
||||
let max_width = opts
|
||||
.max_width
|
||||
.or_else(|| terminal_size::terminal_size().map(|(w, _)| w.0 as usize))
|
||||
.unwrap_or(DEFAULT_TERM_WIDTH);
|
||||
let is_format_flowed = matches!(opts.format, Format::Flowed);
|
||||
let max_width = match opts.format {
|
||||
Format::Fixed(width) => opts.max_width.unwrap_or(*width),
|
||||
Format::Flowed => 0,
|
||||
Format::Auto => opts
|
||||
.max_width
|
||||
.or_else(|| terminal_size::terminal_size().map(|(w, _)| w.0 as usize))
|
||||
.unwrap_or(DEFAULT_TERM_WIDTH),
|
||||
};
|
||||
let mut table = vec![Self::head()];
|
||||
let mut cell_widths: Vec<usize> =
|
||||
table[0].0.iter().map(|cell| cell.unicode_width()).collect();
|
||||
|
@ -190,7 +205,7 @@ where
|
|||
glue.print(writter)?;
|
||||
|
||||
let table_is_overflowing = table_width > max_width;
|
||||
if table_is_overflowing && cell.is_shrinkable() {
|
||||
if table_is_overflowing && !is_format_flowed && cell.is_shrinkable() {
|
||||
trace!("table is overflowing and cell is shrinkable");
|
||||
|
||||
let shrink_width = table_width - max_width;
|
||||
|
@ -324,7 +339,7 @@ mod tests {
|
|||
|
||||
macro_rules! write_items {
|
||||
($writter:expr, $($item:expr),*) => {
|
||||
Table::print($writter, &[$($item,)*], PrintTableOpts { max_width: Some(20) }).unwrap();
|
||||
Table::print($writter, &[$($item,)*], PrintTableOpts { format: &Format::Auto, max_width: Some(20) }).unwrap();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#[cfg(feature = "imap-backend")]
|
||||
use himalaya::{
|
||||
backends::{Backend, ImapBackend, ImapEnvelopes},
|
||||
config::{AccountConfig, ImapBackendConfig},
|
||||
};
|
||||
|
||||
#[cfg(feature = "imap-backend")]
|
||||
#[test]
|
||||
fn test_imap_backend() {
|
||||
// configure accounts
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
use std::{collections::HashMap, env, fs, iter::FromIterator};
|
||||
|
||||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
use himalaya::{
|
||||
backends::{Backend, MaildirBackend, NotmuchBackend, NotmuchEnvelopes},
|
||||
config::{AccountConfig, MaildirBackendConfig, NotmuchBackendConfig},
|
||||
};
|
||||
|
||||
#[cfg(feature = "notmuch")]
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
#[test]
|
||||
fn test_notmuch_backend() {
|
||||
// set up maildir folders and notmuch database
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
## Installation
|
||||
|
||||
First you need to install and configure the [himalaya
|
||||
CLI](https://github.com/soywod/himalaya#installation). Then you can install
|
||||
this plugin with your favorite plugin manager. For example with
|
||||
[vim-plug](https://github.com/junegunn/vim-plug), add to your `.vimrc`:
|
||||
CLI](https://github.com/soywod/himalaya#installation). Then you can
|
||||
install this plugin with your favorite plugin manager. For example
|
||||
with [vim-plug](https://github.com/junegunn/vim-plug), add to your
|
||||
`.vimrc`:
|
||||
|
||||
```viml
|
||||
Plug 'soywod/himalaya', {'rtp': 'vim'}
|
||||
|
@ -31,14 +32,15 @@ set hidden
|
|||
let g:himalaya_mailbox_picker = 'native' | 'fzf' | 'telescope'
|
||||
```
|
||||
|
||||
Defines the provider used for picking mailboxes:
|
||||
Defines the provider used for picking mailboxes (default keybind:
|
||||
`gm`):
|
||||
|
||||
- `native`: a vim native input
|
||||
- `fzf`: https://github.com/junegunn/fzf.vim
|
||||
- `telescope`: https://github.com/nvim-telescope/telescope.nvim
|
||||
|
||||
If no value given, the first loaded (and available) provider will be used (fzf
|
||||
> telescope > native).
|
||||
If no value given, the first loaded (and available) provider will be
|
||||
used (fzf > telescope > native).
|
||||
|
||||
### Telescope preview
|
||||
|
||||
|
|
Loading…
Reference in a new issue