improve args management

This commit is contained in:
Clément DOUIN 2022-09-27 17:37:08 +02:00
parent 3feccc3225
commit 329af51534
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
10 changed files with 740 additions and 707 deletions

View file

@ -56,7 +56,7 @@ for all the options.*
## Features
- Mailbox listing
- Folder listing
- Email listing and searching
- Email composition based on `$EDITOR`
- Email manipulation (copy/move/delete)

View file

@ -1,316 +1,352 @@
//! Module related to message CLI.
//! Module related to email CLI.
//!
//! This module provides subcommands, arguments and a command matcher related to message.
//! This module provides subcommands, arguments and a command matcher related to email.
use anyhow::Result;
use clap::{self, App, Arg, ArgMatches, SubCommand};
use himalaya_lib::email::TplOverride;
use log::{debug, info, trace};
use log::{debug, trace};
use crate::{email, flag, folder, tpl, ui::table};
type Seq<'a> = &'a str;
type PageSize = usize;
type Page = usize;
type Mbox<'a> = &'a str;
type TextMime<'a> = &'a str;
type Raw = bool;
type All = bool;
type RawMsg<'a> = &'a str;
type Query = String;
type AttachmentPaths<'a> = Vec<&'a str>;
type MaxTableWidth = Option<usize>;
type Encrypt = bool;
type Criteria = String;
type Headers<'a> = Vec<&'a str>;
const ARG_ATTACHMENTS: &str = "attachment";
const ARG_CRITERIA: &str = "criterion";
const ARG_ENCRYPT: &str = "encrypt";
const ARG_HEADERS: &str = "header";
const ARG_ID: &str = "id";
const ARG_IDS: &str = "ids";
const ARG_MIME_TYPE: &str = "mime-type";
const ARG_PAGE: &str = "page";
const ARG_PAGE_SIZE: &str = "page-size";
const ARG_QUERY: &str = "query";
const ARG_RAW: &str = "raw";
const ARG_REPLY_ALL: &str = "reply-all";
const CMD_ATTACHMENTS: &str = "attachments";
const CMD_COPY: &str = "copy";
const CMD_DELETE: &str = "delete";
const CMD_FORWARD: &str = "forward";
const CMD_LIST: &str = "list";
const CMD_MOVE: &str = "move";
const CMD_READ: &str = "read";
const CMD_REPLY: &str = "reply";
const CMD_SAVE: &str = "save";
const CMD_SEARCH: &str = "search";
const CMD_SEND: &str = "send";
const CMD_SORT: &str = "sort";
const CMD_WRITE: &str = "write";
/// Message commands.
type Criteria = String;
type Encrypt = bool;
type Folder<'a> = &'a str;
type Page = usize;
type PageSize = usize;
type Query = String;
type Raw = bool;
type RawEmail<'a> = &'a str;
type TextMime<'a> = &'a str;
pub(crate) type All = bool;
pub(crate) type Attachments<'a> = Vec<&'a str>;
pub(crate) type Headers<'a> = Vec<&'a str>;
pub(crate) type Id<'a> = &'a str;
pub(crate) type Ids<'a> = &'a str;
/// Represents the email commands.
#[derive(Debug, PartialEq, Eq)]
pub enum Cmd<'a> {
Attachments(Seq<'a>),
Copy(Seq<'a>, Mbox<'a>),
Delete(Seq<'a>),
Forward(Seq<'a>, AttachmentPaths<'a>, Encrypt),
List(MaxTableWidth, Option<PageSize>, Page),
Move(Seq<'a>, Mbox<'a>),
Read(Seq<'a>, TextMime<'a>, Raw, Headers<'a>),
Reply(Seq<'a>, All, AttachmentPaths<'a>, Encrypt),
Save(RawMsg<'a>),
Search(Query, MaxTableWidth, Option<PageSize>, Page),
Sort(Criteria, Query, MaxTableWidth, Option<PageSize>, Page),
Send(RawMsg<'a>),
Write(TplOverride<'a>, AttachmentPaths<'a>, Encrypt),
Attachments(Id<'a>),
Copy(Id<'a>, Folder<'a>),
Delete(Id<'a>),
Forward(Id<'a>, Attachments<'a>, Encrypt),
List(table::args::MaxTableWidth, Option<PageSize>, Page),
Move(Id<'a>, Folder<'a>),
Read(Id<'a>, TextMime<'a>, Raw, Headers<'a>),
Reply(Id<'a>, All, Attachments<'a>, Encrypt),
Save(RawEmail<'a>),
Search(Query, table::args::MaxTableWidth, Option<PageSize>, Page),
Send(RawEmail<'a>),
Sort(
Criteria,
Query,
table::args::MaxTableWidth,
Option<PageSize>,
Page,
),
Write(TplOverride<'a>, Attachments<'a>, Encrypt),
Flag(Option<flag::args::Cmd<'a>>),
Tpl(Option<tpl::args::Cmd<'a>>),
}
/// Message command matcher.
/// Email command matcher.
pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Cmd<'a>>> {
info!("entering message command matcher");
trace!("matches: {:?}", m);
if let Some(m) = m.subcommand_matches("attachments") {
info!("attachments command matched");
let seq = m.value_of("seq").unwrap();
debug!("seq: {}", seq);
return Ok(Some(Cmd::Attachments(seq)));
}
let cmd = if let Some(m) = m.subcommand_matches(CMD_ATTACHMENTS) {
debug!("attachments command matched");
let id = parse_id_arg(m);
Cmd::Attachments(id)
} else if let Some(m) = m.subcommand_matches(CMD_COPY) {
debug!("copy command matched");
let id = parse_id_arg(m);
let folder = folder::args::parse_target_arg(m);
Cmd::Copy(id, folder)
} else if let Some(m) = m.subcommand_matches(CMD_DELETE) {
debug!("delete command matched");
let id = parse_id_arg(m);
Cmd::Delete(id)
} else if let Some(m) = m.subcommand_matches(CMD_FORWARD) {
debug!("forward command matched");
let id = parse_id_arg(m);
let attachments = parse_attachments_arg(m);
let encrypt = parse_encrypt_flag(m);
Cmd::Forward(id, attachments, encrypt)
} else if let Some(m) = m.subcommand_matches(CMD_LIST) {
debug!("list command matched");
let max_table_width = table::args::parse_max_width(m);
let page_size = parse_page_size_arg(m);
let page = parse_page_arg(m);
Cmd::List(max_table_width, page_size, page)
} else if let Some(m) = m.subcommand_matches(CMD_MOVE) {
debug!("move command matched");
let id = parse_id_arg(m);
let folder = folder::args::parse_target_arg(m);
Cmd::Move(id, folder)
} else if let Some(m) = m.subcommand_matches(CMD_READ) {
debug!("read command matched");
let id = parse_id_arg(m);
let mime = parse_mime_type_arg(m);
let raw = parse_raw_flag(m);
let headers = parse_headers_arg(m);
Cmd::Read(id, mime, raw, headers)
} else if let Some(m) = m.subcommand_matches(CMD_REPLY) {
debug!("reply command matched");
let id = parse_id_arg(m);
let all = parse_reply_all_flag(m);
let attachments = parse_attachments_arg(m);
let encrypt = parse_encrypt_flag(m);
Cmd::Reply(id, all, attachments, encrypt)
} else if let Some(m) = m.subcommand_matches(CMD_SAVE) {
debug!("save command matched");
let email = parse_raw_arg(m);
Cmd::Save(email)
} else if let Some(m) = m.subcommand_matches(CMD_SEARCH) {
debug!("search command matched");
let max_table_width = table::args::parse_max_width(m);
let page_size = parse_page_size_arg(m);
let page = parse_page_arg(m);
let query = parse_query_arg(m);
Cmd::Search(query, max_table_width, page_size, page)
} else if let Some(m) = m.subcommand_matches(CMD_SORT) {
debug!("sort command matched");
let max_table_width = table::args::parse_max_width(m);
let page_size = parse_page_size_arg(m);
let page = parse_page_arg(m);
let criteria = parse_criteria_arg(m);
let query = parse_query_arg(m);
Cmd::Sort(criteria, query, max_table_width, page_size, page)
} else if let Some(m) = m.subcommand_matches(CMD_SEND) {
debug!("send command matched");
let email = parse_raw_arg(m);
Cmd::Send(email)
} else if let Some(m) = m.subcommand_matches(CMD_WRITE) {
debug!("write command matched");
let attachments = parse_attachments_arg(m);
let encrypt = parse_encrypt_flag(m);
let tpl = tpl::args::parse_override_arg(m);
Cmd::Write(tpl, attachments, encrypt)
} else if let Some(m) = m.subcommand_matches(tpl::args::CMD_TPL) {
Cmd::Tpl(tpl::args::matches(m)?)
} else if let Some(m) = m.subcommand_matches(flag::args::CMD_FLAG) {
Cmd::Flag(flag::args::matches(m)?)
} else {
debug!("default list command matched");
Cmd::List(None, None, 0)
};
if let Some(m) = m.subcommand_matches("copy") {
info!("copy command matched");
let seq = m.value_of("seq").unwrap();
debug!("seq: {}", seq);
let mbox = m.value_of("folder-target").unwrap();
debug!(r#"target mailbox: "{:?}""#, mbox);
return Ok(Some(Cmd::Copy(seq, mbox)));
}
if let Some(m) = m.subcommand_matches("delete") {
info!("copy command matched");
let seq = m.value_of("seq").unwrap();
debug!("seq: {}", seq);
return Ok(Some(Cmd::Delete(seq)));
}
if let Some(m) = m.subcommand_matches("forward") {
info!("forward command matched");
let seq = m.value_of("seq").unwrap();
debug!("seq: {}", seq);
let paths: Vec<&str> = m.values_of("attachments").unwrap_or_default().collect();
debug!("attachments paths: {:?}", paths);
let encrypt = m.is_present("encrypt");
debug!("encrypt: {}", encrypt);
return Ok(Some(Cmd::Forward(seq, paths, encrypt)));
}
if let Some(m) = m.subcommand_matches("list") {
info!("list 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);
let page_size = m.value_of("page-size").and_then(|s| s.parse().ok());
debug!("page size: {:?}", page_size);
let page = m
.value_of("page")
.unwrap_or("1")
.parse()
.ok()
.map(|page| 1.max(page) - 1)
.unwrap_or_default();
debug!("page: {}", page);
return Ok(Some(Cmd::List(max_table_width, page_size, page)));
}
if let Some(m) = m.subcommand_matches("move") {
info!("move command matched");
let seq = m.value_of("seq").unwrap();
debug!("seq: {}", seq);
let mbox = m.value_of("folder-target").unwrap();
debug!("target mailbox: {:?}", mbox);
return Ok(Some(Cmd::Move(seq, mbox)));
}
if let Some(m) = m.subcommand_matches("read") {
info!("read command matched");
let seq = m.value_of("seq").unwrap();
debug!("seq: {}", seq);
let mime = m.value_of("mime-type").unwrap();
debug!("text mime: {}", mime);
let raw = m.is_present("raw");
debug!("raw: {}", raw);
let headers: Vec<&str> = m.values_of("headers").unwrap_or_default().collect();
debug!("headers: {:?}", headers);
return Ok(Some(Cmd::Read(seq, mime, raw, headers)));
}
if let Some(m) = m.subcommand_matches("reply") {
info!("reply command matched");
let seq = m.value_of("seq").unwrap();
debug!("seq: {}", seq);
let all = m.is_present("reply-all");
debug!("reply all: {}", all);
let paths: Vec<&str> = m.values_of("attachments").unwrap_or_default().collect();
debug!("attachments paths: {:?}", paths);
let encrypt = m.is_present("encrypt");
debug!("encrypt: {}", encrypt);
return Ok(Some(Cmd::Reply(seq, all, paths, encrypt)));
}
if let Some(m) = m.subcommand_matches("save") {
info!("save command matched");
let msg = m.value_of("message").unwrap_or_default();
trace!("message: {}", msg);
return Ok(Some(Cmd::Save(msg)));
}
if let Some(m) = m.subcommand_matches("search") {
info!("search 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);
let page_size = m.value_of("page-size").and_then(|s| s.parse().ok());
debug!("page size: {:?}", page_size);
let page = m
.value_of("page")
.unwrap()
.parse()
.ok()
.map(|page| 1.max(page) - 1)
.unwrap_or_default();
debug!("page: {}", page);
let query = m
.values_of("query")
.unwrap_or_default()
.fold((false, vec![]), |(escape, mut cmds), cmd| {
match (cmd, escape) {
// Next command is an arg and needs to be escaped
("subject", _) | ("body", _) | ("text", _) => {
cmds.push(cmd.to_string());
(true, cmds)
}
// Escaped arg commands
(_, true) => {
cmds.push(format!("\"{}\"", cmd));
(false, cmds)
}
// Regular commands
(_, false) => {
cmds.push(cmd.to_string());
(false, cmds)
}
}
})
.1
.join(" ");
debug!("query: {}", query);
return Ok(Some(Cmd::Search(query, max_table_width, page_size, page)));
}
if let Some(m) = m.subcommand_matches("sort") {
info!("sort 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);
let page_size = m.value_of("page-size").and_then(|s| s.parse().ok());
debug!("page size: {:?}", page_size);
let page = m
.value_of("page")
.unwrap()
.parse()
.ok()
.map(|page| 1.max(page) - 1)
.unwrap_or_default();
debug!("page: {:?}", page);
let criteria = m
.values_of("criterion")
.unwrap_or_default()
.collect::<Vec<_>>()
.join(" ");
debug!("criteria: {:?}", criteria);
let query = m
.values_of("query")
.unwrap_or_default()
.fold((false, vec![]), |(escape, mut cmds), cmd| {
match (cmd, escape) {
// Next command is an arg and needs to be escaped
("subject", _) | ("body", _) | ("text", _) => {
cmds.push(cmd.to_string());
(true, cmds)
}
// Escaped arg commands
(_, true) => {
cmds.push(format!("\"{}\"", cmd));
(false, cmds)
}
// Regular commands
(_, false) => {
cmds.push(cmd.to_string());
(false, cmds)
}
}
})
.1
.join(" ");
debug!("query: {:?}", query);
return Ok(Some(Cmd::Sort(
criteria,
query,
max_table_width,
page_size,
page,
)));
}
if let Some(m) = m.subcommand_matches("send") {
info!("send command matched");
let msg = m.value_of("message").unwrap_or_default();
trace!("message: {}", msg);
return Ok(Some(Cmd::Send(msg)));
}
if let Some(m) = m.subcommand_matches("write") {
info!("write command matched");
let attachment_paths: Vec<&str> = m.values_of("attachments").unwrap_or_default().collect();
debug!("attachments paths: {:?}", attachment_paths);
let encrypt = m.is_present("encrypt");
debug!("encrypt: {}", encrypt);
let tpl = tpl::args::from_args(m);
return Ok(Some(Cmd::Write(tpl, attachment_paths, encrypt)));
}
if let Some(m) = m.subcommand_matches("template") {
return Ok(Some(Cmd::Tpl(tpl::args::matches(m)?)));
}
if let Some(m) = m.subcommand_matches("flag") {
return Ok(Some(Cmd::Flag(flag::args::matches(m)?)));
}
info!("default list command matched");
Ok(Some(Cmd::List(None, None, 0)))
Ok(Some(cmd))
}
/// Message sequence number argument.
pub fn seq_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("seq")
.help("Specifies the targetted message")
.value_name("SEQ")
/// Represents the email subcommands.
pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
vec![
flag::args::subcmds(),
tpl::args::subcmds(),
vec![
SubCommand::with_name(CMD_ATTACHMENTS)
.aliases(&["attachment", "attach", "att", "at", "a"])
.about("Downloads all attachments of the targeted email")
.arg(email::args::id_arg()),
SubCommand::with_name(CMD_LIST)
.aliases(&["lst", "l"])
.about("Lists all emails")
.arg(page_size_arg())
.arg(page_arg())
.arg(table::args::max_width()),
SubCommand::with_name(CMD_SEARCH)
.aliases(&["s", "query", "q"])
.about("Lists emails matching the given IMAP query")
.arg(page_size_arg())
.arg(page_arg())
.arg(table::args::max_width())
.arg(query_arg()),
SubCommand::with_name(CMD_SORT)
.about("Sorts emails by the given criteria and matching the given IMAP query")
.arg(page_size_arg())
.arg(page_arg())
.arg(table::args::max_width())
.arg(criteria_arg())
.arg(query_arg()),
SubCommand::with_name(CMD_WRITE)
.about("Writes a new email")
.args(&tpl::args::args())
.arg(attachments_arg())
.arg(encrypt_flag()),
SubCommand::with_name(CMD_SEND)
.about("Sends a raw email")
.arg(raw_arg()),
SubCommand::with_name(CMD_SAVE)
.about("Saves a raw email")
.arg(raw_arg()),
SubCommand::with_name(CMD_READ)
.about("Reads text bodies of a email")
.arg(id_arg())
.arg(mime_type_arg())
.arg(raw_flag())
.arg(headers_arg()),
SubCommand::with_name(CMD_REPLY)
.aliases(&["rep", "r"])
.about("Answers to an email")
.arg(id_arg())
.arg(reply_all_flag())
.arg(attachments_arg())
.arg(encrypt_flag()),
SubCommand::with_name(CMD_FORWARD)
.aliases(&["fwd", "f"])
.about("Forwards an email")
.arg(id_arg())
.arg(attachments_arg())
.arg(encrypt_flag()),
SubCommand::with_name(CMD_COPY)
.aliases(&["cp", "c"])
.about("Copies an email to the targeted folder")
.arg(id_arg())
.arg(folder::args::target_arg()),
SubCommand::with_name(CMD_MOVE)
.aliases(&["mv"])
.about("Moves an email to the targeted folder")
.arg(id_arg())
.arg(folder::args::target_arg()),
SubCommand::with_name(CMD_DELETE)
.aliases(&["del", "d", "remove", "rm"])
.about("Deletes an email")
.arg(id_arg()),
],
]
.concat()
}
/// Represents the email id argument.
pub fn id_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name(ARG_ID)
.help("Specifies the targeted email")
.value_name("ID")
.required(true)
}
/// Message sequence range argument.
pub fn seq_range_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("seq-range")
.help("Specifies targetted message(s)")
.long_help("Specifies a range of targetted messages. The range follows the [RFC3501](https://datatracker.ietf.org/doc/html/rfc3501#section-9) format: `1:5` matches messages with sequence number between 1 and 5, `1,5` matches messages with sequence number 1 or 5, * matches all messages.")
.value_name("SEQ")
/// Represents the email id argument parser.
pub fn parse_id_arg<'a>(matches: &'a ArgMatches<'a>) -> &'a str {
matches.value_of(ARG_ID).unwrap()
}
/// Represents the email sort criteria argument.
pub fn criteria_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name(ARG_CRITERIA)
.long("criterion")
.short("c")
.help("Email sorting preferences")
.value_name("CRITERION:ORDER")
.takes_value(true)
.multiple(true)
.required(true)
.possible_values(&[
"arrival",
"arrival:asc",
"arrival:desc",
"cc",
"cc:asc",
"cc:desc",
"date",
"date:asc",
"date:desc",
"from",
"from:asc",
"from:desc",
"size",
"size:asc",
"size:desc",
"subject",
"subject:asc",
"subject:desc",
"to",
"to:asc",
"to:desc",
])
}
/// Represents the email sort criteria argument parser.
pub fn parse_criteria_arg<'a>(matches: &'a ArgMatches<'a>) -> String {
matches
.values_of(ARG_CRITERIA)
.unwrap_or_default()
.collect::<Vec<_>>()
.join(" ")
}
/// Represents the email ids argument.
pub fn id_range_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name(ARG_IDS)
.help("Specifies targeted email(s)")
.long_help("Specifies a range of targeted emails. The range follows the RFC3501 format.")
.value_name("RANGE")
.required(true)
}
/// Message reply all argument.
pub fn reply_all_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("reply-all")
/// Represents the email ids argument parser.
pub fn parse_ids_arg<'a>(matches: &'a ArgMatches<'a>) -> &'a str {
matches.value_of(email::args::ARG_IDS).unwrap()
}
/// Represents the email reply all argument.
pub fn reply_all_flag<'a>() -> Arg<'a, 'a> {
Arg::with_name(ARG_REPLY_ALL)
.help("Includes all recipients")
.short("A")
.long("all")
}
/// Message page size argument.
/// Represents the email reply all argument parser.
pub fn parse_reply_all_flag<'a>(matches: &'a ArgMatches<'a>) -> bool {
matches.is_present(ARG_REPLY_ALL)
}
/// Represents the page size argument.
fn page_size_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("page-size")
Arg::with_name(ARG_PAGE_SIZE)
.help("Page size")
.short("s")
.long("size")
.value_name("INT")
}
/// Message page argument.
/// Represents the page size argument parser.
fn parse_page_size_arg<'a>(matches: &'a ArgMatches<'a>) -> Option<usize> {
matches.value_of(ARG_PAGE_SIZE).and_then(|s| s.parse().ok())
}
/// Represents the page argument.
fn page_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("page")
Arg::with_name(ARG_PAGE)
.help("Page number")
.short("p")
.long("page")
@ -318,154 +354,136 @@ fn page_arg<'a>() -> Arg<'a, 'a> {
.default_value("0")
}
/// Message attachment argument.
/// Represents the page argument parser.
fn parse_page_arg<'a>(matches: &'a ArgMatches<'a>) -> usize {
matches
.value_of(ARG_PAGE)
.unwrap_or("1")
.parse()
.ok()
.map(|page| 1.max(page) - 1)
.unwrap_or_default()
}
/// Represents the email attachments argument.
pub fn attachments_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("attachments")
.help("Adds attachment to the message")
Arg::with_name(ARG_ATTACHMENTS)
.help("Adds attachment to the email")
.short("a")
.long("attachment")
.value_name("PATH")
.multiple(true)
}
/// Represents the message headers argument.
/// Represents the email attachments argument parser.
pub fn parse_attachments_arg<'a>(matches: &'a ArgMatches<'a>) -> Vec<&'a str> {
matches
.values_of(ARG_ATTACHMENTS)
.unwrap_or_default()
.collect()
}
/// Represents the email headers argument.
pub fn headers_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("headers")
.help("Shows additional headers with the message")
Arg::with_name(ARG_HEADERS)
.help("Shows additional headers with the email")
.short("h")
.long("header")
.value_name("STR")
.value_name("STRING")
.multiple(true)
}
/// Message encrypt argument.
pub fn encrypt_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("encrypt")
.help("Encrypts the message")
/// Represents the email headers argument parser.
pub fn parse_headers_arg<'a>(matches: &'a ArgMatches<'a>) -> Vec<&'a str> {
matches.values_of(ARG_HEADERS).unwrap_or_default().collect()
}
/// Represents the raw flag.
pub fn raw_flag<'a>() -> Arg<'a, 'a> {
Arg::with_name(ARG_RAW)
.help("Reads a raw email")
.long("raw")
.short("r")
}
/// Represents the raw flag parser.
pub fn parse_raw_flag<'a>(matches: &'a ArgMatches<'a>) -> bool {
matches.is_present(ARG_RAW)
}
/// Represents the email raw argument.
pub fn raw_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name(ARG_RAW).raw(true)
}
/// Represents the email raw argument parser.
pub fn parse_raw_arg<'a>(matches: &'a ArgMatches<'a>) -> &'a str {
matches.value_of(ARG_RAW).unwrap_or_default()
}
/// Represents the email encrypt flag.
pub fn encrypt_flag<'a>() -> Arg<'a, 'a> {
Arg::with_name(ARG_ENCRYPT)
.help("Encrypts the email")
.short("e")
.long("encrypt")
}
/// Message subcommands.
pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
vec![
flag::args::subcmds(),
tpl::args::subcmds(),
vec![
SubCommand::with_name("attachments")
.aliases(&["attachment", "att", "a"])
.about("Downloads all message attachments")
.arg(email::args::seq_arg()),
SubCommand::with_name("list")
.aliases(&["lst", "l"])
.about("Lists all messages")
.arg(page_size_arg())
.arg(page_arg())
.arg(table::args::max_width()),
SubCommand::with_name("search")
.aliases(&["s", "query", "q"])
.about("Lists messages matching the given IMAP query")
.arg(page_size_arg())
.arg(page_arg())
.arg(table::args::max_width())
.arg(
Arg::with_name("query")
.help("IMAP query")
.long_help("The IMAP query format follows the [RFC3501](https://tools.ietf.org/html/rfc3501#section-6.4.4). The query is case-insensitive.")
.value_name("QUERY")
.multiple(true)
.required(true),
),
SubCommand::with_name("sort")
.about("Sorts messages by the given criteria and matching the given IMAP query")
.arg(page_size_arg())
.arg(page_arg())
.arg(table::args::max_width())
.arg(
Arg::with_name("criterion")
.long("criterion")
.short("c")
.help("Defines the message sorting preferences")
.value_name("CRITERION:ORDER")
.takes_value(true)
.multiple(true)
.required(true)
.possible_values(&[
"arrival", "arrival:asc", "arrival:desc",
"cc", "cc:asc", "cc:desc",
"date", "date:asc", "date:desc",
"from", "from:asc", "from:desc",
"size", "size:asc", "size:desc",
"subject", "subject:asc", "subject:desc",
"to", "to:asc", "to:desc",
]),
)
.arg(
Arg::with_name("query")
.help("IMAP query")
.long_help("The IMAP query format follows the [RFC3501](https://tools.ietf.org/html/rfc3501#section-6.4.4). The query is case-insensitive.")
.value_name("QUERY")
.default_value("ALL")
.raw(true),
),
SubCommand::with_name("write")
.about("Writes a new message")
.args(&tpl::args::tpl_args())
.arg(attachments_arg())
.arg(encrypt_arg()),
SubCommand::with_name("send")
.about("Sends a raw message")
.arg(Arg::with_name("message").raw(true)),
SubCommand::with_name("save")
.about("Saves a raw message")
.arg(Arg::with_name("message").raw(true)),
SubCommand::with_name("read")
.about("Reads text bodies of a message")
.arg(seq_arg())
.arg(
Arg::with_name("mime-type")
.help("MIME type to use")
.short("t")
.long("mime-type")
.value_name("MIME")
.possible_values(&["plain", "html"])
.default_value("plain"),
)
.arg(
Arg::with_name("raw")
.help("Reads raw message")
.long("raw")
.short("r"),
)
.arg(headers_arg()),
SubCommand::with_name("reply")
.aliases(&["rep", "r"])
.about("Answers to a message")
.arg(seq_arg())
.arg(reply_all_arg())
.arg(attachments_arg())
.arg(encrypt_arg()),
SubCommand::with_name("forward")
.aliases(&["fwd", "f"])
.about("Forwards a message")
.arg(seq_arg())
.arg(attachments_arg())
.arg(encrypt_arg()),
SubCommand::with_name("copy")
.aliases(&["cp", "c"])
.about("Copies a message to the targetted mailbox")
.arg(seq_arg())
.arg(folder::args::target_arg()),
SubCommand::with_name("move")
.aliases(&["mv"])
.about("Moves a message to the targetted mailbox")
.arg(seq_arg())
.arg(folder::args::target_arg()),
SubCommand::with_name("delete")
.aliases(&["del", "d", "remove", "rm"])
.about("Deletes a message")
.arg(seq_arg()),
],
]
.concat()
/// Represents the email encrypt flag parser.
pub fn parse_encrypt_flag<'a>(matches: &'a ArgMatches<'a>) -> bool {
matches.is_present(ARG_ENCRYPT)
}
/// Represents the email MIME type argument.
pub fn mime_type_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name(ARG_MIME_TYPE)
.help("MIME type to use")
.short("t")
.long("mime-type")
.value_name("MIME")
.possible_values(&["plain", "html"])
.default_value("plain")
}
/// Represents the email MIME type argument parser.
pub fn parse_mime_type_arg<'a>(matches: &'a ArgMatches<'a>) -> &'a str {
matches.value_of(ARG_MIME_TYPE).unwrap()
}
/// Represents the email query argument.
pub fn query_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name(ARG_QUERY)
.help("IMAP query")
.long_help("The IMAP query format follows the RFC3501. The query is case-insensitive.")
.value_name("QUERY")
.multiple(true)
.required(true)
}
/// Represents the email query argument parser.
pub fn parse_query_arg<'a>(matches: &'a ArgMatches<'a>) -> String {
matches
.values_of(ARG_QUERY)
.unwrap_or_default()
.fold((false, vec![]), |(escape, mut cmds), cmd| {
match (cmd, escape) {
// Next command is an arg and needs to be escaped
("subject", _) | ("body", _) | ("text", _) => {
cmds.push(cmd.to_string());
(true, cmds)
}
// Escaped arg commands
(_, true) => {
cmds.push(format!("\"{}\"", cmd));
(false, cmds)
}
// Regular commands
(_, false) => {
cmds.push(cmd.to_string());
(false, cmds)
}
}
})
.1
.join(" ")
}

View file

@ -51,7 +51,7 @@ pub fn attachments<'a, P: Printer, B: Backend<'a> + ?Sized>(
printer.print_struct("Done!")
}
/// Copy a message from a mailbox to another.
/// Copy a message from a folder to another.
pub fn copy<'a, P: Printer, B: Backend<'a> + ?Sized>(
seq: &str,
mbox_src: &str,
@ -77,7 +77,7 @@ pub fn delete<'a, P: Printer, B: Backend<'a> + ?Sized>(
printer.print_struct(format!("Message(s) {} successfully deleted", seq))
}
/// Forward the given message UID from the selected mailbox.
/// Forward the given message UID from the selected folder.
pub fn forward<'a, P: Printer, B: Backend<'a> + ?Sized, S: Sender + ?Sized>(
seq: &str,
attachments_paths: Vec<&str>,
@ -104,7 +104,7 @@ pub fn forward<'a, P: Printer, B: Backend<'a> + ?Sized, S: Sender + ?Sized>(
Ok(())
}
/// List paginated messages from the selected mailbox.
/// List paginated messages from the selected folder.
pub fn list<'a, P: Printer, B: Backend<'a> + ?Sized>(
max_width: Option<usize>,
page_size: Option<usize>,
@ -195,7 +195,7 @@ pub fn mailto<'a, P: Printer, B: Backend<'a> + ?Sized, S: Sender + ?Sized>(
Ok(())
}
/// Move a message from a mailbox to another.
/// Move a message from a folder to another.
pub fn move_<'a, P: Printer, B: Backend<'a> + ?Sized>(
seq: &str,
mbox_src: &str,
@ -260,16 +260,14 @@ pub fn reply<'a, P: Printer, B: Backend<'a> + ?Sized, S: Sender + ?Sized>(
Ok(())
}
/// Saves a raw message to the targetted mailbox.
/// Saves a raw message to the targetted folder.
pub fn save<'a, P: Printer, B: Backend<'a> + ?Sized>(
mbox: &str,
raw_msg: &str,
printer: &mut P,
backend: &mut B,
) -> Result<()> {
info!("entering save message handler");
debug!("mailbox: {}", mbox);
debug!("folder: {}", mbox);
let is_tty = atty::is(Stream::Stdin);
debug!("is tty: {}", is_tty);
@ -290,7 +288,8 @@ pub fn save<'a, P: Printer, B: Backend<'a> + ?Sized>(
Ok(())
}
/// Paginate messages from the selected mailbox matching the specified query.
/// Paginate messages from the selected folder matching the specified
/// query.
pub fn search<'a, P: Printer, B: Backend<'a> + ?Sized>(
query: String,
max_width: Option<usize>,
@ -314,7 +313,8 @@ pub fn search<'a, P: Printer, B: Backend<'a> + ?Sized>(
)
}
/// Paginates messages from the selected mailbox matching the specified query, sorted by the given criteria.
/// Paginates messages from the selected folder matching the specified
/// query, sorted by the given criteria.
pub fn sort<'a, P: Printer, B: Backend<'a> + ?Sized>(
sort: String,
query: String,

View file

@ -1,7 +1,7 @@
//! Message flag CLI module.
//! Email flag CLI module.
//!
//! This module provides subcommands, arguments and a command matcher related to the message flag
//! domain.
//! This module provides subcommands, arguments and a command matcher
//! related to the email flag domain.
use anyhow::Result;
use clap::{self, App, AppSettings, Arg, ArgMatches, SubCommand};
@ -9,101 +9,91 @@ use log::{debug, info};
use crate::email;
type SeqRange<'a> = &'a str;
const ARG_FLAGS: &str = "flag";
const CMD_ADD: &str = "add";
const CMD_DEL: &str = "remove";
const CMD_SET: &str = "set";
pub(crate) const CMD_FLAG: &str = "flag";
type Flags = String;
/// Represents the flag commands.
#[derive(Debug, PartialEq, Eq)]
pub enum Cmd<'a> {
/// Represents the add flags command.
Add(SeqRange<'a>, Flags),
/// Represents the set flags command.
Set(SeqRange<'a>, Flags),
/// Represents the remove flags command.
Remove(SeqRange<'a>, Flags),
Add(email::args::Ids<'a>, Flags),
Set(email::args::Ids<'a>, Flags),
Del(email::args::Ids<'a>, Flags),
}
/// Defines the flag command matcher.
/// Represents the flag command matcher.
pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Cmd<'a>>> {
info!("entering message flag command matcher");
if let Some(m) = m.subcommand_matches("add") {
info!("add subcommand matched");
let seq_range = m.value_of("seq-range").unwrap();
debug!("seq range: {}", seq_range);
let flags: String = m
.values_of("flags")
.unwrap_or_default()
.collect::<Vec<_>>()
.join(" ");
debug!("flags: {:?}", flags);
return Ok(Some(Cmd::Add(seq_range, flags)));
}
if let Some(m) = m.subcommand_matches("set") {
info!("set subcommand matched");
let seq_range = m.value_of("seq-range").unwrap();
debug!("seq range: {}", seq_range);
let flags: String = m
.values_of("flags")
.unwrap_or_default()
.collect::<Vec<_>>()
.join(" ");
debug!("flags: {:?}", flags);
return Ok(Some(Cmd::Set(seq_range, flags)));
}
if let Some(m) = m.subcommand_matches("remove") {
let cmd = if let Some(m) = m.subcommand_matches(CMD_ADD) {
debug!("add subcommand matched");
let ids = email::args::parse_ids_arg(m);
let flags: String = parse_flags_arg(m);
Some(Cmd::Add(ids, flags))
} else if let Some(m) = m.subcommand_matches(CMD_SET) {
debug!("set subcommand matched");
let ids = email::args::parse_ids_arg(m);
let flags: String = parse_flags_arg(m);
Some(Cmd::Set(ids, flags))
} else if let Some(m) = m.subcommand_matches(CMD_DEL) {
info!("remove subcommand matched");
let seq_range = m.value_of("seq-range").unwrap();
debug!("seq range: {}", seq_range);
let flags: String = m
.values_of("flags")
.unwrap_or_default()
.collect::<Vec<_>>()
.join(" ");
debug!("flags: {:?}", flags);
return Ok(Some(Cmd::Remove(seq_range, flags)));
}
let ids = email::args::parse_ids_arg(m);
let flags: String = parse_flags_arg(m);
Some(Cmd::Del(ids, flags))
} else {
None
};
Ok(None)
Ok(cmd)
}
/// Defines the flags argument.
fn flags_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("flags")
.help("IMAP flags")
.long_help("IMAP flags. Flags are case-insensitive, and they do not need to be prefixed with `\\`.")
/// Represents the flag subcommands.
pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
vec![SubCommand::with_name(CMD_FLAG)
.aliases(&["flags", "flg"])
.about("Handles email flags")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
SubCommand::with_name(CMD_ADD)
.aliases(&["a"])
.about("Adds email flags")
.arg(email::args::id_range_arg())
.arg(flags_arg()),
)
.subcommand(
SubCommand::with_name(CMD_SET)
.aliases(&["s", "change", "c"])
.about("Sets email flags")
.arg(email::args::id_range_arg())
.arg(flags_arg()),
)
.subcommand(
SubCommand::with_name(CMD_DEL)
.aliases(&["rem", "rm", "r", "delete", "del", "d"])
.about("Removes email flags")
.arg(email::args::id_range_arg())
.arg(flags_arg()),
)]
}
/// Represents the flags argument.
pub fn flags_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name(ARG_FLAGS)
.long_help("Flags are case-insensitive, and they do not need to be prefixed with `\\`.")
.value_name("FLAGS…")
.multiple(true)
.required(true)
}
/// Contains flag subcommands.
pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
vec![SubCommand::with_name("flag")
.aliases(&["flags", "flg"])
.about("Handles flags")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
SubCommand::with_name("add")
.aliases(&["a"])
.about("Adds flags to a message")
.arg(email::args::seq_range_arg())
.arg(flags_arg()),
)
.subcommand(
SubCommand::with_name("set")
.aliases(&["s", "change", "c"])
.about("Replaces all message flags")
.arg(email::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(email::args::seq_range_arg())
.arg(flags_arg()),
)]
/// Represents the flags argument parser.
pub fn parse_flags_arg<'a>(matches: &'a ArgMatches<'a>) -> String {
matches
.values_of(ARG_FLAGS)
.unwrap_or_default()
.collect::<Vec<_>>()
.join(" ")
}

View file

@ -1,80 +1,98 @@
//! Mailbox CLI module.
//! Folder CLI module.
//!
//! This module provides subcommands, arguments and a command matcher related to the mailbox
//! domain.
//! This module provides subcommands, arguments and a command matcher
//! related to the folder domain.
use anyhow::Result;
use clap;
use log::{debug, info};
use clap::{self, App, Arg, ArgMatches, SubCommand};
use log::debug;
use crate::ui::table;
type MaxTableWidth = Option<usize>;
const ARG_SOURCE: &str = "source";
const ARG_TARGET: &str = "target";
const CMD_FOLDERS: &str = "folders";
/// Represents the mailbox commands.
/// Represents the folder commands.
#[derive(Debug, PartialEq, Eq)]
pub enum Cmd {
/// Represents the list mailboxes command.
List(MaxTableWidth),
List(table::args::MaxTableWidth),
}
/// Defines the mailbox command matcher.
pub fn matches(m: &clap::ArgMatches) -> Result<Option<Cmd>> {
info!("entering mailbox command matcher");
/// Represents the folder command matcher.
pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
let cmd = if let Some(m) = m.subcommand_matches(CMD_FOLDERS) {
debug!("folders command matched");
let max_table_width = table::args::parse_max_width(m);
Some(Cmd::List(max_table_width))
} else {
None
};
if let Some(m) = m.subcommand_matches("mailboxes") {
info!("mailboxes 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);
return Ok(Some(Cmd::List(max_table_width)));
}
Ok(None)
Ok(cmd)
}
/// Contains mailbox subcommands.
pub fn subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
vec![clap::SubCommand::with_name("mailboxes")
/// Represents folder subcommands.
pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
vec![SubCommand::with_name(CMD_FOLDERS)
.aliases(&[
"mailbox", "mboxes", "mbox", "mb", "m", "folders", "fold", "fo",
"folder",
"fold",
"fo",
"mailboxes",
"mailbox",
"mboxes",
"mbox",
"mb",
"m",
])
.about("Lists folders")
.arg(table::args::max_width())]
}
/// Defines the source mailbox argument.
pub fn source_arg<'a>() -> clap::Arg<'a, 'a> {
clap::Arg::with_name("folder-source")
/// Represents the source folder argument.
pub fn source_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name(ARG_SOURCE)
.short("f")
.long("folder")
.help("Specifies the folder source")
.value_name("SOURCE")
}
/// Defines the target mailbox argument.
pub fn target_arg<'a>() -> clap::Arg<'a, 'a> {
clap::Arg::with_name("folder-target")
/// Represents the source folder argument parser.
pub fn parse_source_arg<'a>(matches: &'a ArgMatches<'a>) -> &'a str {
matches.value_of(ARG_SOURCE).unwrap()
}
/// Represents the target folder argument.
pub fn target_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name(ARG_TARGET)
.help("Specifies the folder target")
.value_name("TARGET")
.required(true)
}
/// Represents the target folder argument parser.
pub fn parse_target_arg<'a>(matches: &'a ArgMatches<'a>) -> &'a str {
matches.value_of(ARG_TARGET).unwrap()
}
#[cfg(test)]
mod tests {
use clap::{App, ErrorKind};
use super::*;
#[test]
fn it_should_match_cmds() {
let arg = clap::App::new("himalaya")
let arg = App::new("himalaya")
.subcommands(subcmds())
.get_matches_from(&["himalaya", "mailboxes"]);
.get_matches_from(&["himalaya", "folders"]);
assert_eq!(Some(Cmd::List(None)), matches(&arg).unwrap());
let arg = clap::App::new("himalaya")
let arg = App::new("himalaya")
.subcommands(subcmds())
.get_matches_from(&["himalaya", "mailboxes", "--max-width", "20"]);
.get_matches_from(&["himalaya", "folders", "--max-width", "20"]);
assert_eq!(Some(Cmd::List(Some(20))), matches(&arg).unwrap());
}
@ -82,57 +100,53 @@ mod tests {
fn it_should_match_aliases() {
macro_rules! get_matches_from {
($alias:expr) => {
clap::App::new("himalaya")
App::new("himalaya")
.subcommands(subcmds())
.get_matches_from(&["himalaya", $alias])
.subcommand_name()
};
}
assert_eq!(Some("mailboxes"), get_matches_from!["mailboxes"]);
assert_eq!(Some("mailboxes"), get_matches_from!["mboxes"]);
assert_eq!(Some("mailboxes"), get_matches_from!["mbox"]);
assert_eq!(Some("mailboxes"), get_matches_from!["mb"]);
assert_eq!(Some("mailboxes"), get_matches_from!["m"]);
assert_eq!(Some("folders"), get_matches_from!["folders"]);
assert_eq!(Some("folders"), get_matches_from!["folder"]);
assert_eq!(Some("folders"), get_matches_from!["fold"]);
assert_eq!(Some("folders"), get_matches_from!["fo"]);
}
#[test]
fn it_should_match_source_arg() {
macro_rules! get_matches_from {
($($arg:expr),*) => {
clap::App::new("himalaya")
App::new("himalaya")
.arg(source_arg())
.get_matches_from(&["himalaya", $($arg,)*])
};
}
let app = get_matches_from![];
assert_eq!(None, app.value_of("folder-source"));
assert_eq!(None, app.value_of("source"));
let app = get_matches_from!["-m", "SOURCE"];
assert_eq!(Some("SOURCE"), app.value_of("folder-source"));
let app = get_matches_from!["-f", "SOURCE"];
assert_eq!(Some("SOURCE"), app.value_of("source"));
let app = get_matches_from!["--mailbox", "SOURCE"];
assert_eq!(Some("SOURCE"), app.value_of("folder-source"));
let app = get_matches_from!["--folder", "SOURCE"];
assert_eq!(Some("SOURCE"), app.value_of("source"));
}
#[test]
fn it_should_match_target_arg() {
macro_rules! get_matches_from {
($($arg:expr),*) => {
clap::App::new("himalaya")
App::new("himalaya")
.arg(target_arg())
.get_matches_from_safe(&["himalaya", $($arg,)*])
};
}
let app = get_matches_from![];
assert_eq!(
clap::ErrorKind::MissingRequiredArgument,
app.unwrap_err().kind
);
assert_eq!(ErrorKind::MissingRequiredArgument, app.unwrap_err().kind);
let app = get_matches_from!["TARGET"];
assert_eq!(Some("TARGET"), app.unwrap().value_of("folder-target"));
assert_eq!(Some("TARGET"), app.unwrap().value_of("target"));
}
}

View file

@ -1,26 +1,25 @@
//! Mailbox handling module.
//! Folder handling module.
//!
//! This module gathers all mailbox actions triggered by the CLI.
//! This module gathers all folder actions triggered by the CLI.
use anyhow::Result;
use himalaya_lib::{AccountConfig, Backend};
use log::{info, trace};
use log::trace;
use crate::printer::{PrintTableOpts, Printer};
/// Lists all mailboxes.
/// Lists all folders.
pub fn list<'a, P: Printer, B: Backend<'a> + ?Sized>(
max_width: Option<usize>,
config: &AccountConfig,
printer: &mut P,
backend: &mut B,
) -> Result<()> {
info!("entering list mailbox handler");
let mboxes = backend.folder_list()?;
trace!("mailboxes: {:?}", mboxes);
let folders = backend.folder_list()?;
trace!("folders: {:?}", folders);
printer.print_table(
// TODO: remove Box
Box::new(mboxes),
Box::new(folders),
PrintTableOpts {
format: &config.email_reading_format,
max_width,
@ -109,20 +108,18 @@ mod tests {
unimplemented!();
}
fn folder_list(&mut self) -> backend::Result<Folders> {
Ok(Folders {
folders: vec![
Folder {
delim: "/".into(),
name: "INBOX".into(),
desc: "desc".into(),
},
Folder {
delim: "/".into(),
name: "Sent".into(),
desc: "desc".into(),
},
],
})
Ok(Folders(vec![
Folder {
delim: "/".into(),
name: "INBOX".into(),
desc: "desc".into(),
},
Folder {
delim: "/".into(),
name: "Sent".into(),
desc: "desc".into(),
},
]))
}
fn folder_delete(&mut self, _: &str) -> backend::Result<()> {
unimplemented!();
@ -143,9 +140,6 @@ mod tests {
fn email_add(&mut self, _: &str, _: &[u8], _: &str) -> backend::Result<String> {
unimplemented!()
}
fn email_list(&mut self, _: &str, _: &str) -> backend::Result<Email> {
unimplemented!()
}
fn email_get(&mut self, _: &str, _: &str) -> backend::Result<Email> {
unimplemented!()
}
@ -167,6 +161,9 @@ mod tests {
fn flags_delete(&mut self, _: &str, _: &str, _: &str) -> backend::Result<()> {
unimplemented!()
}
fn as_any(&self) -> &(dyn std::any::Any + 'a) {
self
}
}
let account_config = AccountConfig::default();

View file

@ -42,7 +42,7 @@ pub fn matches(m: &ArgMatches) -> Result<Option<Command>> {
pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
vec![
clap::SubCommand::with_name("notify")
.about("Notifies when new messages arrive in the given mailbox")
.about("Notifies when new messages arrive in the given folder")
.aliases(&["idle"])
.arg(
clap::Arg::with_name("keepalive")

View file

@ -1,138 +1,163 @@
//! Module related to message template CLI.
//! Module related to email template CLI.
//!
//! This module provides subcommands, arguments and a command matcher related to message template.
//! This module provides subcommands, arguments and a command matcher
//! related to email templating.
use anyhow::Result;
use clap::{self, App, AppSettings, Arg, ArgMatches, SubCommand};
use himalaya_lib::email::TplOverride;
use log::{debug, info, trace};
use log::debug;
use crate::email;
type Seq<'a> = &'a str;
type ReplyAll = bool;
type AttachmentPaths<'a> = Vec<&'a str>;
const ARG_BCC: &str = "bcc";
const ARG_BODY: &str = "body";
const ARG_CC: &str = "cc";
const ARG_FROM: &str = "from";
const ARG_HEADERS: &str = "header";
const ARG_SIGNATURE: &str = "signature";
const ARG_SUBJECT: &str = "subject";
const ARG_TO: &str = "to";
const ARG_TPL: &str = "template";
const CMD_FORWARD: &str = "forward";
const CMD_NEW: &str = "new";
const CMD_REPLY: &str = "reply";
const CMD_SAVE: &str = "save";
const CMD_SEND: &str = "send";
pub(crate) const CMD_TPL: &str = "template";
type Tpl<'a> = &'a str;
pub fn from_args<'a>(matches: &'a ArgMatches<'a>) -> TplOverride {
TplOverride {
subject: matches.value_of("subject"),
from: matches.values_of("from").map(|v| v.collect()),
to: matches.values_of("to").map(|v| v.collect()),
cc: matches.values_of("cc").map(|v| v.collect()),
bcc: matches.values_of("bcc").map(|v| v.collect()),
headers: matches.values_of("headers").map(|v| v.collect()),
body: matches.value_of("body"),
sig: matches.value_of("signature"),
}
}
/// Message template commands.
/// Represents the template commands.
#[derive(Debug, PartialEq, Eq)]
pub enum Cmd<'a> {
Forward(email::args::Id<'a>, TplOverride<'a>),
New(TplOverride<'a>),
Reply(Seq<'a>, ReplyAll, TplOverride<'a>),
Forward(Seq<'a>, TplOverride<'a>),
Save(AttachmentPaths<'a>, Tpl<'a>),
Send(AttachmentPaths<'a>, Tpl<'a>),
Reply(email::args::Id<'a>, email::args::All, TplOverride<'a>),
Save(email::args::Attachments<'a>, Tpl<'a>),
Send(email::args::Attachments<'a>, Tpl<'a>),
}
/// Message template command matcher.
/// Represents the template command matcher.
pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Cmd<'a>>> {
info!("entering message template command matcher");
let cmd = if let Some(m) = m.subcommand_matches(CMD_FORWARD) {
debug!("forward subcommand matched");
let id = email::args::parse_id_arg(m);
let tpl = parse_override_arg(m);
Some(Cmd::Forward(id, tpl))
} else if let Some(m) = m.subcommand_matches(CMD_NEW) {
debug!("new subcommand matched");
let tpl = parse_override_arg(m);
Some(Cmd::New(tpl))
} else if let Some(m) = m.subcommand_matches(CMD_REPLY) {
debug!("reply subcommand matched");
let id = email::args::parse_id_arg(m);
let all = email::args::parse_reply_all_flag(m);
let tpl = parse_override_arg(m);
Some(Cmd::Reply(id, all, tpl))
} else if let Some(m) = m.subcommand_matches(CMD_SAVE) {
debug!("save subcommand matched");
let attachments = email::args::parse_attachments_arg(m);
let tpl = parse_raw_arg(m);
Some(Cmd::Save(attachments, tpl))
} else if let Some(m) = m.subcommand_matches(CMD_SEND) {
debug!("send subcommand matched");
let attachments = email::args::parse_attachments_arg(m);
let tpl = parse_raw_arg(m);
Some(Cmd::Send(attachments, tpl))
} else {
None
};
if let Some(m) = m.subcommand_matches("new") {
info!("new subcommand matched");
let tpl = from_args(m);
trace!("template override: {:?}", tpl);
return Ok(Some(Cmd::New(tpl)));
}
if let Some(m) = m.subcommand_matches("reply") {
info!("reply subcommand matched");
let seq = m.value_of("seq").unwrap();
debug!("sequence: {}", seq);
let all = m.is_present("reply-all");
debug!("reply all: {}", all);
let tpl = from_args(m);
trace!("template override: {:?}", tpl);
return Ok(Some(Cmd::Reply(seq, all, tpl)));
}
if let Some(m) = m.subcommand_matches("forward") {
info!("forward subcommand matched");
let seq = m.value_of("seq").unwrap();
debug!("sequence: {}", seq);
let tpl = from_args(m);
trace!("template args: {:?}", tpl);
return Ok(Some(Cmd::Forward(seq, tpl)));
}
if let Some(m) = m.subcommand_matches("save") {
info!("save subcommand matched");
let attachment_paths: Vec<&str> = m.values_of("attachments").unwrap_or_default().collect();
trace!("attachments paths: {:?}", attachment_paths);
let tpl = m.value_of("template").unwrap_or_default();
trace!("template: {}", tpl);
return Ok(Some(Cmd::Save(attachment_paths, tpl)));
}
if let Some(m) = m.subcommand_matches("send") {
info!("send subcommand matched");
let attachment_paths: Vec<&str> = m.values_of("attachments").unwrap_or_default().collect();
trace!("attachments paths: {:?}", attachment_paths);
let tpl = m.value_of("template").unwrap_or_default();
trace!("template: {}", tpl);
return Ok(Some(Cmd::Send(attachment_paths, tpl)));
}
Ok(None)
Ok(cmd)
}
/// Message template args.
pub fn tpl_args<'a>() -> Vec<Arg<'a, 'a>> {
/// Represents the template subcommands.
pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
vec![SubCommand::with_name(CMD_TPL)
.aliases(&["tpl"])
.about("Handles email templates")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
SubCommand::with_name(CMD_NEW)
.aliases(&["n"])
.about("Generates a template for a new email")
.args(&args()),
)
.subcommand(
SubCommand::with_name(CMD_REPLY)
.aliases(&["rep", "re", "r"])
.about("Generates a template for replying to an email")
.arg(email::args::id_arg())
.arg(email::args::reply_all_flag())
.args(&args()),
)
.subcommand(
SubCommand::with_name(CMD_FORWARD)
.aliases(&["fwd", "fw", "f"])
.about("Generates a template for forwarding an email")
.arg(email::args::id_arg())
.args(&args()),
)
.subcommand(
SubCommand::with_name(CMD_SAVE)
.about("Saves an email based on the given template")
.arg(&email::args::attachments_arg())
.arg(Arg::with_name(ARG_TPL).raw(true)),
)
.subcommand(
SubCommand::with_name(CMD_SEND)
.about("Sends an email based on the given template")
.arg(&email::args::attachments_arg())
.arg(Arg::with_name(ARG_TPL).raw(true)),
)]
}
/// Represents the template arguments.
pub fn args<'a>() -> Vec<Arg<'a, 'a>> {
vec![
Arg::with_name("subject")
Arg::with_name(ARG_SUBJECT)
.help("Overrides the Subject header")
.short("s")
.long("subject")
.value_name("STRING"),
Arg::with_name("from")
Arg::with_name(ARG_FROM)
.help("Overrides the From header")
.short("f")
.long("from")
.value_name("ADDR")
.multiple(true),
Arg::with_name("to")
Arg::with_name(ARG_TO)
.help("Overrides the To header")
.short("t")
.long("to")
.value_name("ADDR")
.multiple(true),
Arg::with_name("cc")
Arg::with_name(ARG_CC)
.help("Overrides the Cc header")
.short("c")
.long("cc")
.value_name("ADDR")
.multiple(true),
Arg::with_name("bcc")
Arg::with_name(ARG_BCC)
.help("Overrides the Bcc header")
.short("b")
.long("bcc")
.value_name("ADDR")
.multiple(true),
Arg::with_name("header")
Arg::with_name(ARG_HEADERS)
.help("Overrides a specific header")
.short("h")
.long("header")
.value_name("KEY: VAL")
.value_name("KEY:VAL")
.multiple(true),
Arg::with_name("body")
Arg::with_name(ARG_BODY)
.help("Overrides the body")
.short("B")
.long("body")
.value_name("STRING"),
Arg::with_name("signature")
Arg::with_name(ARG_SIGNATURE)
.help("Overrides the signature")
.short("S")
.long("signature")
@ -140,43 +165,21 @@ pub fn tpl_args<'a>() -> Vec<Arg<'a, 'a>> {
]
}
/// Message template subcommands.
pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
vec![SubCommand::with_name("template")
.aliases(&["tpl"])
.about("Generates a message template")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
SubCommand::with_name("new")
.aliases(&["n"])
.about("Generates a new message template")
.args(&tpl_args()),
)
.subcommand(
SubCommand::with_name("reply")
.aliases(&["rep", "re", "r"])
.about("Generates a reply message template")
.arg(email::args::seq_arg())
.arg(email::args::reply_all_arg())
.args(&tpl_args()),
)
.subcommand(
SubCommand::with_name("forward")
.aliases(&["fwd", "fw", "f"])
.about("Generates a forward message template")
.arg(email::args::seq_arg())
.args(&tpl_args()),
)
.subcommand(
SubCommand::with_name("save")
.about("Saves a message based on the given template")
.arg(&email::args::attachments_arg())
.arg(Arg::with_name("template").raw(true)),
)
.subcommand(
SubCommand::with_name("send")
.about("Sends a message based on the given template")
.arg(&email::args::attachments_arg())
.arg(Arg::with_name("template").raw(true)),
)]
/// Represents the template override argument parser.
pub fn parse_override_arg<'a>(matches: &'a ArgMatches<'a>) -> TplOverride {
TplOverride {
subject: matches.value_of(ARG_SUBJECT),
from: matches.values_of(ARG_FROM).map(Iterator::collect),
to: matches.values_of(ARG_TO).map(Iterator::collect),
cc: matches.values_of(ARG_CC).map(Iterator::collect),
bcc: matches.values_of(ARG_BCC).map(Iterator::collect),
headers: matches.values_of(ARG_HEADERS).map(Iterator::collect),
body: matches.value_of(ARG_BODY),
signature: matches.value_of(ARG_SIGNATURE),
}
}
/// Represents the raw template argument parser.
pub fn parse_raw_arg<'a>(matches: &'a ArgMatches<'a>) -> &'a str {
matches.value_of(ARG_TPL).unwrap_or_default()
}

View file

@ -106,7 +106,7 @@ fn main() -> Result<()> {
_ => (),
}
// Check mailbox commands.
// Check folder commands.
match folder::args::matches(&m)? {
Some(folder::args::Cmd::List(max_width)) => {
return folder::handlers::list(
@ -254,7 +254,7 @@ fn main() -> Result<()> {
backend.as_mut(),
);
}
Some(flag::args::Cmd::Remove(seq_range, ref flags)) => {
Some(flag::args::Cmd::Del(seq_range, ref flags)) => {
return flag::handlers::remove(
seq_range,
flags,

View file

@ -1,10 +1,21 @@
use clap::Arg;
use clap::{Arg, ArgMatches};
/// Defines the max table width argument.
const ARG_MAX_TABLE_WIDTH: &str = "max-table-width";
pub(crate) type MaxTableWidth = Option<usize>;
/// Represents the max table width argument.
pub fn max_width<'a>() -> Arg<'a, 'a> {
Arg::with_name("max-table-width")
Arg::with_name(ARG_MAX_TABLE_WIDTH)
.help("Defines a maximum width for the table")
.short("w")
.long("max-width")
.value_name("INT")
}
/// Represents the max table width argument parser.
pub fn parse_max_width<'a>(matches: &'a ArgMatches<'a>) -> Option<usize> {
matches
.value_of(ARG_MAX_TABLE_WIDTH)
.and_then(|width| width.parse::<usize>().ok())
}