diff --git a/CHANGELOG.md b/CHANGELOG.md index b295969..1f54ba5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,9 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Populate Config struct from TOML [#2] - Set up IMAP connection [#3] - List new emails [#6] +- Set up CLI arg parser [#15] [unreleased]: https://github.com/soywod/himalaya [#1]: https://github.com/soywod/himalaya/issues/1 [#2]: https://github.com/soywod/himalaya/issues/2 [#3]: https://github.com/soywod/himalaya/issues/3 +[#15]: https://github.com/soywod/himalaya/issues/15 diff --git a/Cargo.lock b/Cargo.lock index b9f0166..0b86a8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,12 +9,32 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + [[package]] name = "arrayvec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -95,6 +115,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "core-foundation" version = "0.9.1" @@ -146,10 +181,20 @@ dependencies = [ "wasi 0.9.0+wasi-snapshot-preview1", ] +[[package]] +name = "hermit-abi" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +dependencies = [ + "libc", +] + [[package]] name = "himalaya" version = "0.1.0" dependencies = [ + "clap", "imap", "native-tls", "rfc2047-decoder", @@ -489,6 +534,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "syn" version = "1.0.55" @@ -514,6 +565,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thread_local" version = "1.0.1" @@ -543,6 +603,12 @@ dependencies = [ "serde", ] +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + [[package]] name = "unicode-xid" version = "0.2.1" @@ -555,6 +621,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.2" diff --git a/Cargo.toml b/Cargo.toml index 06ba880..783b944 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ authors = ["soywod "] edition = "2018" [dependencies] +clap = "2.33.3" imap = "2.4.0" native-tls = "0.2" rfc2047-decoder = "0.1.2" diff --git a/src/imap.rs b/src/imap.rs index 85cc282..9b7f32b 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -96,17 +96,14 @@ fn date_from_fetch(fetch: &imap::types::Fetch) -> String { } } -pub fn read_new_emails(imap_sess: &mut ImapSession, mbox: &str) -> imap::Result<()> { +pub fn read_emails(imap_sess: &mut ImapSession, mbox: &str, query: &str) -> imap::Result<()> { imap_sess.select(mbox)?; - // let mboxes = imap_sess.list(Some(""), Some("*"))?; - // println!("Mboxes {:?}", mboxes); let seqs = imap_sess - .search("NOT SEEN")? + .search(query)? .iter() .map(|n| n.to_string()) - .collect::>() - .join(","); + .collect::>(); let table_head = vec![ table::Cell::new( @@ -128,7 +125,10 @@ pub fn read_new_emails(imap_sess: &mut ImapSession, mbox: &str) -> imap::Result< ]; let mut table_rows = imap_sess - .fetch(seqs, "(INTERNALDATE ENVELOPE)")? + .fetch( + seqs[..20.min(seqs.len())].join(","), + "(INTERNALDATE ENVELOPE)", + )? .iter() .map(|fetch| { vec![ @@ -146,3 +146,6 @@ pub fn read_new_emails(imap_sess: &mut ImapSession, mbox: &str) -> imap::Result< Ok(()) } + +// List mailboxes +// let mboxes = imap_sess.list(Some(""), Some("*"))?; diff --git a/src/main.rs b/src/main.rs index 96ab840..3ea5377 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,16 +2,90 @@ mod config; mod imap; mod table; -use std::env; +use clap::{App, Arg, SubCommand}; + +fn mailbox_arg() -> Arg<'static, 'static> { + Arg::with_name("mailbox") + .help("Name of the targeted mailbox") + .value_name("MAILBOX") + .required(true) +} + +fn uid_arg() -> Arg<'static, 'static> { + Arg::with_name("uid") + .help("UID of the targeted email") + .value_name("UID") + .required(true) +} fn main() { - let mbox = env::args().nth(1).unwrap_or(String::from("inbox")); - let args = env::args().skip(2).collect::>().join(" ").to_owned(); let config = config::read_file(); let mut imap_sess = imap::login(&config); - match args.as_str() { - "read new" => imap::read_new_emails(&mut imap_sess, &mbox).unwrap(), - _ => println!("Himalaya: command not found e"), + let matches = App::new("Himalaya") + .version("0.1.0") + .about("📫 Minimalist CLI mail client") + .author("soywod ") + .subcommand( + SubCommand::with_name("read") + .about("Reads an email by its UID") + .arg(mailbox_arg()) + .arg(uid_arg()), + ) + .subcommand( + SubCommand::with_name("query") + .about("Prints emails filtered by the given IMAP query") + .arg(mailbox_arg()) + .arg( + Arg::with_name("query") + .help("IMAP query (see https://tools.ietf.org/html/rfc3501#section-6.4.4)") + .value_name("COMMANDS") + .multiple(true) + .required(true), + ), + ) + .subcommand(SubCommand::with_name("write").about("Writes a new email")) + .subcommand( + SubCommand::with_name("forward") + .about("Forwards an email by its UID") + .arg(mailbox_arg()) + .arg(uid_arg()), + ) + .subcommand( + SubCommand::with_name("reply") + .about("Replies to an email by its UID") + .arg(mailbox_arg()) + .arg(uid_arg()) + .arg( + Arg::with_name("reply all") + .help("Replies to all recipients") + .short("a") + .long("all"), + ), + ) + .get_matches(); + + if let Some(matches) = matches.subcommand_matches("query") { + let mbox = matches.value_of("mailbox").unwrap_or("inbox"); + + if let Some(matches) = matches.values_of("query") { + let query = matches + .fold((false, vec![]), |(escape, mut cmds), cmd| { + if ["subject", "body", "text"].contains(&cmd.to_lowercase().as_str()) { + cmds.push(cmd.to_string()); + (true, cmds) + } else if escape { + cmds.push(format!("\"{}\"", cmd)); + (false, cmds) + } else { + cmds.push(cmd.to_string()); + (false, cmds) + } + }) + .1 + .join(" "); + + imap::read_emails(&mut imap_sess, &mbox, &query).unwrap(); + } } }