use anyhow::Result;
use clap::{Arg, ArgAction, ArgMatches, Command};
use crate::{flag, folder, tpl, ui::table};
const ARG_CRITERIA: &str = "criterion";
const ARG_HEADERS: &str = "headers";
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 ARG_SANITIZE: &str = "sanitize";
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";
pub type All = bool;
pub type Criteria = String;
pub type Folder<'a> = &'a str;
pub type Headers<'a> = Vec<&'a str>;
pub type Id<'a> = &'a str;
pub type Ids<'a> = Vec<&'a str>;
pub type Page = usize;
pub type PageSize = usize;
pub type Query = String;
pub type Raw = bool;
pub type RawEmail = String;
pub type Sanitize = bool;
pub type TextMime<'a> = &'a str;
#[derive(Debug, PartialEq, Eq)]
pub enum Cmd<'a> {
Attachments(Ids<'a>),
Copy(Ids<'a>, Folder<'a>),
Delete(Ids<'a>),
Flag(Option<flag::args::Cmd<'a>>),
Forward(Id<'a>, tpl::args::Headers<'a>, tpl::args::Body<'a>),
List(table::args::MaxTableWidth, Option<PageSize>, Page),
Move(Ids<'a>, Folder<'a>),
Read(Ids<'a>, TextMime<'a>, Sanitize, Raw, Headers<'a>),
Reply(Id<'a>, All, tpl::args::Headers<'a>, tpl::args::Body<'a>),
Save(RawEmail),
Search(Query, table::args::MaxTableWidth, Option<PageSize>, Page),
Send(RawEmail),
Sort(
Criteria,
Query,
table::args::MaxTableWidth,
Option<PageSize>,
Page,
),
Tpl(Option<tpl::args::Cmd<'a>>),
Write(tpl::args::Headers<'a>, tpl::args::Body<'a>),
}
pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Cmd<'a>>> {
let cmd = if let Some(m) = m.subcommand_matches(CMD_ATTACHMENTS) {
let ids = parse_ids_arg(m);
Cmd::Attachments(ids)
} else if let Some(m) = m.subcommand_matches(CMD_COPY) {
let ids = parse_ids_arg(m);
let folder = folder::args::parse_target_arg(m);
Cmd::Copy(ids, folder)
} else if let Some(m) = m.subcommand_matches(CMD_DELETE) {
let ids = parse_ids_arg(m);
Cmd::Delete(ids)
} else if let Some(m) = m.subcommand_matches(flag::args::CMD_FLAG) {
Cmd::Flag(flag::args::matches(m)?)
} else if let Some(m) = m.subcommand_matches(CMD_FORWARD) {
let id = parse_id_arg(m);
let headers = tpl::args::parse_headers_arg(m);
let body = tpl::args::parse_body_arg(m);
Cmd::Forward(id, headers, body)
} else if let Some(m) = m.subcommand_matches(CMD_LIST) {
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) {
let ids = parse_ids_arg(m);
let folder = folder::args::parse_target_arg(m);
Cmd::Move(ids, folder)
} else if let Some(m) = m.subcommand_matches(CMD_READ) {
let ids = parse_ids_arg(m);
let mime = parse_mime_type_arg(m);
let sanitize = parse_sanitize_flag(m);
let raw = parse_raw_flag(m);
let headers = parse_headers_arg(m);
Cmd::Read(ids, mime, sanitize, raw, headers)
} else if let Some(m) = m.subcommand_matches(CMD_REPLY) {
let id = parse_id_arg(m);
let all = parse_reply_all_flag(m);
let headers = tpl::args::parse_headers_arg(m);
let body = tpl::args::parse_body_arg(m);
Cmd::Reply(id, all, headers, body)
} else if let Some(m) = m.subcommand_matches(CMD_SAVE) {
let email = parse_raw_arg(m);
Cmd::Save(email)
} else if let Some(m) = m.subcommand_matches(CMD_SEARCH) {
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) {
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) {
let email = parse_raw_arg(m);
Cmd::Send(email)
} 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(CMD_WRITE) {
let headers = tpl::args::parse_headers_arg(m);
let body = tpl::args::parse_body_arg(m);
Cmd::Write(headers, body)
} else {
Cmd::List(None, None, 0)
};
Ok(Some(cmd))
}
pub fn subcmds() -> Vec<Command> {
vec![
flag::args::subcmds(),
tpl::args::subcmds(),
vec![
Command::new(CMD_ATTACHMENTS)
.about("Downloads all emails attachments")
.arg(ids_arg()),
Command::new(CMD_LIST)
.alias("lst")
.about("List envelopes")
.arg(page_size_arg())
.arg(page_arg())
.arg(table::args::max_width()),
Command::new(CMD_SEARCH)
.aliases(["query", "q"])
.about("Filter envelopes matching the given query")
.arg(page_size_arg())
.arg(page_arg())
.arg(table::args::max_width())
.arg(query_arg()),
Command::new(CMD_SORT)
.about("Sort envelopes by the given criteria and matching the given query")
.arg(page_size_arg())
.arg(page_arg())
.arg(table::args::max_width())
.arg(criteria_arg())
.arg(query_arg()),
Command::new(CMD_WRITE)
.about("Write a new email")
.aliases(["new", "n"])
.args(tpl::args::args()),
Command::new(CMD_SEND)
.about("Send a raw email")
.arg(raw_arg()),
Command::new(CMD_SAVE)
.about("Save a raw email")
.arg(raw_arg()),
Command::new(CMD_READ)
.about("Read text bodies of emails")
.arg(mime_type_arg())
.arg(sanitize_flag())
.arg(raw_flag())
.arg(headers_arg())
.arg(ids_arg()),
Command::new(CMD_REPLY)
.about("Answer to an email")
.arg(reply_all_flag())
.args(tpl::args::args())
.arg(id_arg()),
Command::new(CMD_FORWARD)
.aliases(["fwd", "f"])
.about("Forward an email")
.args(tpl::args::args())
.arg(id_arg()),
Command::new(CMD_COPY)
.alias("cp")
.about("Copy emails to the given folder")
.arg(folder::args::target_arg())
.arg(ids_arg()),
Command::new(CMD_MOVE)
.alias("mv")
.about("Move emails to the given folder")
.arg(folder::args::target_arg())
.arg(ids_arg()),
Command::new(CMD_DELETE)
.aliases(["remove", "rm"])
.about("Delete emails")
.arg(ids_arg()),
],
]
.concat()
}
pub fn id_arg() -> Arg {
Arg::new(ARG_ID)
.help("Specifies the target email")
.value_name("ID")
.required(true)
}
pub fn parse_id_arg(matches: &ArgMatches) -> &str {
matches.get_one::<String>(ARG_ID).unwrap()
}
pub fn ids_arg() -> Arg {
Arg::new(ARG_IDS)
.help("Email ids")
.value_name("IDS")
.num_args(1..)
.required(true)
}
pub fn parse_ids_arg(matches: &ArgMatches) -> Vec<&str> {
matches
.get_many::<String>(ARG_IDS)
.unwrap()
.map(String::as_str)
.collect()
}
pub fn criteria_arg<'a>() -> Arg {
Arg::new(ARG_CRITERIA)
.help("Email sorting preferences")
.long("criterion")
.short('c')
.value_name("CRITERION:ORDER")
.action(ArgAction::Append)
.value_parser([
"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",
])
}
pub fn parse_criteria_arg(matches: &ArgMatches) -> String {
matches
.get_many::<String>(ARG_CRITERIA)
.unwrap_or_default()
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.join(" ")
}
pub fn reply_all_flag() -> Arg {
Arg::new(ARG_REPLY_ALL)
.help("Includes all recipients")
.long("all")
.short('a')
.action(ArgAction::SetTrue)
}
pub fn parse_reply_all_flag(matches: &ArgMatches) -> bool {
matches.get_flag(ARG_REPLY_ALL)
}
fn page_size_arg() -> Arg {
Arg::new(ARG_PAGE_SIZE)
.help("Page size")
.long("page-size")
.short('s')
.value_name("INT")
}
fn parse_page_size_arg(matches: &ArgMatches) -> Option<usize> {
matches
.get_one::<String>(ARG_PAGE_SIZE)
.and_then(|s| s.parse().ok())
}
fn page_arg() -> Arg {
Arg::new(ARG_PAGE)
.help("Page number")
.short('p')
.long("page")
.value_name("INT")
.default_value("1")
}
fn parse_page_arg(matches: &ArgMatches) -> usize {
matches
.get_one::<String>(ARG_PAGE)
.unwrap()
.parse()
.ok()
.map(|page| 1.max(page) - 1)
.unwrap_or_default()
}
pub fn headers_arg() -> Arg {
Arg::new(ARG_HEADERS)
.help("Shows additional headers with the email")
.long("header")
.short('H')
.value_name("STRING")
.action(ArgAction::Append)
}
pub fn parse_headers_arg(m: &ArgMatches) -> Vec<&str> {
m.get_many::<String>(ARG_HEADERS)
.unwrap_or_default()
.map(String::as_str)
.collect::<Vec<_>>()
}
pub fn sanitize_flag() -> Arg {
Arg::new(ARG_SANITIZE)
.help("Sanitizes text bodies")
.long("sanitize")
.short('s')
.action(ArgAction::SetTrue)
}
pub fn raw_flag() -> Arg {
Arg::new(ARG_RAW)
.help("Returns raw version of email")
.long("raw")
.short('r')
.action(ArgAction::SetTrue)
}
pub fn parse_sanitize_flag(m: &ArgMatches) -> bool {
m.get_flag(ARG_SANITIZE)
}
pub fn parse_raw_flag(m: &ArgMatches) -> bool {
m.get_flag(ARG_RAW)
}
pub fn raw_arg() -> Arg {
Arg::new(ARG_RAW).raw(true)
}
pub fn parse_raw_arg(m: &ArgMatches) -> String {
m.get_one::<String>(ARG_RAW).cloned().unwrap_or_default()
}
pub fn mime_type_arg() -> Arg {
Arg::new(ARG_MIME_TYPE)
.help("MIME type to use")
.short('t')
.long("mime-type")
.value_name("MIME")
.value_parser(["plain", "html"])
.default_value("plain")
}
pub fn parse_mime_type_arg(matches: &ArgMatches) -> &str {
matches.get_one::<String>(ARG_MIME_TYPE).unwrap()
}
pub fn query_arg() -> Arg {
Arg::new(ARG_QUERY)
.long_help("The query system depends on the backend, see the wiki for more details")
.value_name("QUERY")
.num_args(1..)
.required(true)
}
pub fn parse_query_arg(matches: &ArgMatches) -> String {
matches
.get_many::<String>(ARG_QUERY)
.unwrap_or_default()
.fold((false, vec![]), |(escape, mut cmds), cmd| {
match (cmd.as_str(), escape) {
("subject", _) | ("body", _) | ("text", _) => {
cmds.push(cmd.to_string());
(true, cmds)
}
(_, true) => {
cmds.push(format!("\"{}\"", cmd));
(false, cmds)
}
(_, false) => {
cmds.push(cmd.to_string());
(false, cmds)
}
}
})
.1
.join(" ")
}