mirror of
https://github.com/soywod/himalaya.git
synced 2024-07-08 18:45:13 +00:00
fix new tpl sig
This commit is contained in:
parent
8e2a703e2b
commit
e065d8d905
|
@ -1,13 +1,13 @@
|
||||||
//! Module related to completion handling.
|
//! Module related to completion handling.
|
||||||
//!
|
//!
|
||||||
//! This module gathers all completion actions triggered by the CLI.
|
//! This module gathers all completion commands.
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use clap::{App, Shell};
|
use clap::{App, Shell};
|
||||||
use std::{io, str::FromStr};
|
use std::{io, str::FromStr};
|
||||||
|
|
||||||
/// Generate completion script from the given [`clap::App`] for the given shell slice.
|
/// Generate completion script from the given [`clap::App`] for the given shell slice.
|
||||||
pub fn generate<'a>(shell: Option<&'a str>, mut app: App<'a, 'a>) -> Result<()> {
|
pub fn generate<'a>(mut app: App<'a, 'a>, shell: Option<&'a str>) -> Result<()> {
|
||||||
let shell = Shell::from_str(shell.unwrap_or_default())
|
let shell = Shell::from_str(shell.unwrap_or_default())
|
||||||
.map_err(|err| anyhow!(err))
|
.map_err(|err| anyhow!(err))
|
||||||
.context("cannot parse shell")?;
|
.context("cannot parse shell")?;
|
||||||
|
|
|
@ -183,60 +183,6 @@ impl Config {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the signature of the given acccount in combination witht the sigantion delimiter.
|
|
||||||
/// If the account doesn't have a signature, then the global signature is used.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// use himalaya::config::model::{Config, Account};
|
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let config = Config {
|
|
||||||
/// signature: Some("Global signature".to_string()),
|
|
||||||
/// .. Config::default()
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// // a config without a global signature
|
|
||||||
/// let config_no_global = Config::default();
|
|
||||||
///
|
|
||||||
/// let account1 = Account::new_with_signature(Some("Account Name"), "mail@address.com", Some("Cya"));
|
|
||||||
/// let account2 = Account::new(Some("Bruh"), "mail@address.com");
|
|
||||||
///
|
|
||||||
/// // Hint: Don't forget the default signature delimiter: '\n-- \n'
|
|
||||||
/// assert_eq!(config.signature(&account1), Some("\n-- \nCya".to_string()));
|
|
||||||
/// assert_eq!(config.signature(&account2), Some("\n-- \nGlobal signature".to_string()));
|
|
||||||
///
|
|
||||||
/// assert_eq!(config_no_global.signature(&account2), None);
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn signature(&self, account: &ConfigAccountEntry) -> Option<String> {
|
|
||||||
let default_sig_delim = String::from("-- \n");
|
|
||||||
let sig_delim = account
|
|
||||||
.signature_delimiter
|
|
||||||
.as_ref()
|
|
||||||
.or_else(|| self.signature_delimiter.as_ref())
|
|
||||||
.unwrap_or(&default_sig_delim);
|
|
||||||
let sig = account
|
|
||||||
.signature
|
|
||||||
.as_ref()
|
|
||||||
.or_else(|| self.signature.as_ref());
|
|
||||||
sig.and_then(|sig| shellexpand::full(sig).ok())
|
|
||||||
.map(|sig| sig.to_string())
|
|
||||||
.and_then(|sig| fs::read_to_string(sig).ok())
|
|
||||||
.or_else(|| sig.map(|sig| sig.to_owned()))
|
|
||||||
.map(|sig| format!("\n{}{}", sig_delim, sig))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn default_page_size(&self, account: &ConfigAccountEntry) -> usize {
|
|
||||||
account
|
|
||||||
.default_page_size
|
|
||||||
.as_ref()
|
|
||||||
.or_else(|| self.default_page_size.as_ref())
|
|
||||||
.or(Some(&DEFAULT_PAGE_SIZE))
|
|
||||||
.unwrap()
|
|
||||||
.to_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exec_watch_cmds(&self, account: &ConfigAccountEntry) -> Result<()> {
|
pub fn exec_watch_cmds(&self, account: &ConfigAccountEntry) -> Result<()> {
|
||||||
let cmds = account
|
let cmds = account
|
||||||
.watch_cmds
|
.watch_cmds
|
||||||
|
@ -551,10 +497,10 @@ impl<'a> TryFrom<(&'a Config, Option<&str>)> for Account {
|
||||||
.or_else(|| config.signature.as_ref());
|
.or_else(|| config.signature.as_ref());
|
||||||
let signature = signature
|
let signature = signature
|
||||||
.and_then(|sig| shellexpand::full(sig).ok())
|
.and_then(|sig| shellexpand::full(sig).ok())
|
||||||
.map(|sig| sig.to_string())
|
.map(String::from)
|
||||||
.and_then(|sig| fs::read_to_string(sig).ok())
|
.and_then(|sig| fs::read_to_string(sig).ok())
|
||||||
.or_else(|| signature.map(|sig| sig.to_owned()))
|
.or_else(|| signature.map(|sig| sig.to_owned()))
|
||||||
.map(|sig| format!("\n{}{}", signature_delim, sig))
|
.map(|sig| format!("\n{}{}", signature_delim, sig.trim_end()))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let account = Account {
|
let account = Account {
|
||||||
|
|
|
@ -7,16 +7,16 @@ use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
|
||||||
/// Mailbox commands.
|
/// Mailbox commands.
|
||||||
pub enum Commands {
|
pub enum Command {
|
||||||
/// List all available mailboxes.
|
/// List all available mailboxes.
|
||||||
List,
|
List,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mailbox command matcher.
|
/// Mailbox command matcher.
|
||||||
pub fn matches(m: &ArgMatches) -> Result<Option<Commands>> {
|
pub fn matches(m: &ArgMatches) -> Result<Option<Command>> {
|
||||||
if let Some(_) = m.subcommand_matches("mailboxes") {
|
if let Some(_) = m.subcommand_matches("mailboxes") {
|
||||||
debug!("mailboxes command matched");
|
debug!("mailboxes command matched");
|
||||||
return Ok(Some(Commands::List));
|
return Ok(Some(Command::List));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|
|
@ -34,8 +34,8 @@ pub enum Command<'a> {
|
||||||
Send(RawMsg<'a>),
|
Send(RawMsg<'a>),
|
||||||
Write(AttachmentsPaths<'a>),
|
Write(AttachmentsPaths<'a>),
|
||||||
|
|
||||||
Flag(msg::flag::arg::Command<'a>),
|
Flag(Option<msg::flag::arg::Command<'a>>),
|
||||||
Tpl(msg::tpl::arg::Command<'a>),
|
Tpl(Option<msg::tpl::arg::Command<'a>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Message command matcher.
|
/// Message command matcher.
|
||||||
|
@ -63,10 +63,6 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Command<'a>>> {
|
||||||
return Ok(Some(Command::Delete(uid)));
|
return Ok(Some(Command::Delete(uid)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(m) = msg::flag::arg::matches(&m)? {
|
|
||||||
return Ok(Some(Command::Flag(m)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(m) = m.subcommand_matches("forward") {
|
if let Some(m) = m.subcommand_matches("forward") {
|
||||||
debug!("forward command matched");
|
debug!("forward command matched");
|
||||||
let uid = m.value_of("uid").unwrap();
|
let uid = m.value_of("uid").unwrap();
|
||||||
|
@ -176,10 +172,6 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Command<'a>>> {
|
||||||
return Ok(Some(Command::Send(msg)));
|
return Ok(Some(Command::Send(msg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(m) = msg::tpl::arg::matches(&m)? {
|
|
||||||
return Ok(Some(Command::Tpl(m)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(m) = m.subcommand_matches("write") {
|
if let Some(m) = m.subcommand_matches("write") {
|
||||||
debug!("write command matched");
|
debug!("write command matched");
|
||||||
let attachment_paths: Vec<&str> = m.values_of("attachments").unwrap_or_default().collect();
|
let attachment_paths: Vec<&str> = m.values_of("attachments").unwrap_or_default().collect();
|
||||||
|
@ -187,6 +179,14 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Command<'a>>> {
|
||||||
return Ok(Some(Command::Write(attachment_paths)));
|
return Ok(Some(Command::Write(attachment_paths)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(m) = m.subcommand_matches("template") {
|
||||||
|
return Ok(Some(Command::Tpl(msg::tpl::arg::matches(&m)?)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(m) = m.subcommand_matches("flag") {
|
||||||
|
return Ok(Some(Command::Flag(msg::flag::arg::matches(&m)?)));
|
||||||
|
}
|
||||||
|
|
||||||
debug!("default list command matched");
|
debug!("default list command matched");
|
||||||
Ok(Some(Command::List(None, 0)))
|
Ok(Some(Command::List(None, 0)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ use std::fmt;
|
||||||
/// This part of the msg/msg would be stored in this struct.
|
/// This part of the msg/msg would be stored in this struct.
|
||||||
#[derive(Clone, Serialize, Debug, PartialEq, Eq)]
|
#[derive(Clone, Serialize, Debug, PartialEq, Eq)]
|
||||||
pub struct Body {
|
pub struct Body {
|
||||||
/// The text version of a body (if available)
|
/// The plain version of a body (if available)
|
||||||
pub text: Option<String>,
|
pub plain: Option<String>,
|
||||||
|
|
||||||
/// The html version of a body (if available)
|
/// The html version of a body (if available)
|
||||||
pub html: Option<String>,
|
pub html: Option<String>,
|
||||||
|
@ -61,7 +61,7 @@ impl Body {
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new_with_text<S: ToString>(text: S) -> Self {
|
pub fn new_with_text<S: ToString>(text: S) -> Self {
|
||||||
Self {
|
Self {
|
||||||
text: Some(text.to_string()),
|
plain: Some(text.to_string()),
|
||||||
html: None,
|
html: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ impl Body {
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new_with_html<S: ToString>(html: S) -> Self {
|
pub fn new_with_html<S: ToString>(html: S) -> Self {
|
||||||
Self {
|
Self {
|
||||||
text: None,
|
plain: None,
|
||||||
html: Some(html.to_string()),
|
html: Some(html.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ impl Body {
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new_with_both<S: ToString>(text: S, html: S) -> Self {
|
pub fn new_with_both<S: ToString>(text: S, html: S) -> Self {
|
||||||
Self {
|
Self {
|
||||||
text: Some(text.to_string()),
|
plain: Some(text.to_string()),
|
||||||
html: Some(html.to_string()),
|
html: Some(html.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ impl Body {
|
||||||
impl Default for Body {
|
impl Default for Body {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
text: None,
|
plain: None,
|
||||||
html: None,
|
html: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ impl Default for Body {
|
||||||
|
|
||||||
impl fmt::Display for Body {
|
impl fmt::Display for Body {
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let content = if let Some(text) = self.text.clone() {
|
let content = if let Some(text) = self.plain.clone() {
|
||||||
text
|
text
|
||||||
} else if let Some(html) = self.html.clone() {
|
} else if let Some(html) = self.html.clone() {
|
||||||
html
|
html
|
||||||
|
|
|
@ -16,8 +16,7 @@ use crate::ui::editor;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use lettre::message::{
|
use lettre::message::{
|
||||||
header::ContentTransferEncoding, header::ContentType, Attachment as lettre_Attachment, Mailbox,
|
header::ContentType, Attachment as lettre_Attachment, Mailbox, Message, MultiPart, SinglePart,
|
||||||
Message, MultiPart, SinglePart,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -64,7 +63,7 @@ impl fmt::Display for MsgSerialized {
|
||||||
|
|
||||||
/// This struct represents a whole msg with its attachments, body-content
|
/// This struct represents a whole msg with its attachments, body-content
|
||||||
/// and its headers.
|
/// and its headers.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
|
||||||
pub struct Msg {
|
pub struct Msg {
|
||||||
/// All added attachments are listed in this vector.
|
/// All added attachments are listed in this vector.
|
||||||
pub attachments: Vec<Attachment>,
|
pub attachments: Vec<Attachment>,
|
||||||
|
@ -80,6 +79,9 @@ pub struct Msg {
|
||||||
/// This includes the general content text and the signature.
|
/// This includes the general content text and the signature.
|
||||||
pub body: Body,
|
pub body: Body,
|
||||||
|
|
||||||
|
/// The signature of the message.
|
||||||
|
pub sig: String,
|
||||||
|
|
||||||
/// The UID of the msg. In general, a message should already have one, unless you're writing a
|
/// The UID of the msg. In general, a message should already have one, unless you're writing a
|
||||||
/// new message, then we're generating it.
|
/// new message, then we're generating it.
|
||||||
uid: Option<u32>,
|
uid: Option<u32>,
|
||||||
|
@ -167,11 +169,6 @@ impl Msg {
|
||||||
if headers.from.is_empty() {
|
if headers.from.is_empty() {
|
||||||
headers.from = vec![account.address()];
|
headers.from = vec![account.address()];
|
||||||
}
|
}
|
||||||
|
|
||||||
if let None = headers.signature {
|
|
||||||
headers.signature = Some(account.signature.to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
let body = Body::new_with_text(if let Some(sig) = headers.signature.as_ref() {
|
let body = Body::new_with_text(if let Some(sig) = headers.signature.as_ref() {
|
||||||
format!("\n{}", sig)
|
format!("\n{}", sig)
|
||||||
} else {
|
} else {
|
||||||
|
@ -181,6 +178,7 @@ impl Msg {
|
||||||
Self {
|
Self {
|
||||||
headers,
|
headers,
|
||||||
body,
|
body,
|
||||||
|
sig: account.signature.to_owned(),
|
||||||
..Self::default()
|
..Self::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -280,7 +278,7 @@ impl Msg {
|
||||||
// each line which includes a string.
|
// each line which includes a string.
|
||||||
let mut new_body = self
|
let mut new_body = self
|
||||||
.body
|
.body
|
||||||
.text
|
.plain
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.lines()
|
.lines()
|
||||||
|
@ -354,7 +352,11 @@ impl Msg {
|
||||||
// apply a line which should indicate where the forwarded message begins
|
// apply a line which should indicate where the forwarded message begins
|
||||||
body.push_str(&format!(
|
body.push_str(&format!(
|
||||||
"\n---------- Forwarded Message ----------\n{}",
|
"\n---------- Forwarded Message ----------\n{}",
|
||||||
self.body.text.clone().unwrap_or_default().replace("\r", ""),
|
self.body
|
||||||
|
.plain
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.replace("\r", ""),
|
||||||
));
|
));
|
||||||
|
|
||||||
body.push_str(&account.signature);
|
body.push_str(&account.signature);
|
||||||
|
@ -685,16 +687,16 @@ impl Msg {
|
||||||
let mut msg_parts = MultiPart::mixed().build();
|
let mut msg_parts = MultiPart::mixed().build();
|
||||||
|
|
||||||
// -- Body --
|
// -- Body --
|
||||||
if self.body.text.is_some() && self.body.html.is_some() {
|
if self.body.plain.is_some() && self.body.html.is_some() {
|
||||||
msg_parts = msg_parts.multipart(MultiPart::alternative_plain_html(
|
msg_parts = msg_parts.multipart(MultiPart::alternative_plain_html(
|
||||||
self.body.text.clone().unwrap(),
|
self.body.plain.clone().unwrap(),
|
||||||
self.body.html.clone().unwrap(),
|
self.body.html.clone().unwrap(),
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
let msg_body = SinglePart::builder()
|
let msg_body = SinglePart::builder()
|
||||||
.header(ContentType::TEXT_PLAIN)
|
.header(ContentType::TEXT_PLAIN)
|
||||||
.header(self.headers.encoding)
|
.header(self.headers.encoding)
|
||||||
.body(self.body.text.clone().unwrap_or_default());
|
.body(self.body.plain.clone().unwrap_or_default());
|
||||||
|
|
||||||
msg_parts = msg_parts.singlepart(msg_body);
|
msg_parts = msg_parts.singlepart(msg_body);
|
||||||
}
|
}
|
||||||
|
@ -740,42 +742,16 @@ impl Msg {
|
||||||
|
|
||||||
Ok(raw_message)
|
Ok(raw_message)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the [`ContentTransferEncoding`] of the body.
|
|
||||||
pub fn get_encoding(&self) -> ContentTransferEncoding {
|
|
||||||
self.headers.encoding
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the whole message: Header + Body as a String
|
|
||||||
pub fn get_full_message(&self) -> String {
|
|
||||||
format!("{}\n{}", self.headers.get_header_as_string(), self.body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Traits --
|
|
||||||
impl Default for Msg {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
attachments: Vec::new(),
|
|
||||||
flags: Flags::default(),
|
|
||||||
headers: Headers::default(),
|
|
||||||
body: Body::default(),
|
|
||||||
// the uid is generated in the "to_sendable_msg" function if the server didn't apply a
|
|
||||||
// message id to it.
|
|
||||||
uid: None,
|
|
||||||
date: None,
|
|
||||||
raw: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Msg {
|
impl fmt::Display for Msg {
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
formatter,
|
formatter,
|
||||||
"{}\n{}",
|
"{}\n{}{}",
|
||||||
self.headers.get_header_as_string(),
|
self.headers.get_header_as_string(),
|
||||||
self.body
|
self.body,
|
||||||
|
self.sig
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -880,7 +856,7 @@ impl TryFrom<&Fetch> for Msg {
|
||||||
// don't. This condition hits, if the body isn't in a multipart, so we can
|
// don't. This condition hits, if the body isn't in a multipart, so we can
|
||||||
// immediately fetch the body from the first part of the mail.
|
// immediately fetch the body from the first part of the mail.
|
||||||
match parsed.ctype.mimetype.as_ref() {
|
match parsed.ctype.mimetype.as_ref() {
|
||||||
"text/plain" => body.text = parsed.get_body().ok(),
|
"text/plain" => body.plain = parsed.get_body().ok(),
|
||||||
"text/html" => body.html = parsed.get_body().ok(),
|
"text/html" => body.html = parsed.get_body().ok(),
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
|
@ -889,8 +865,8 @@ impl TryFrom<&Fetch> for Msg {
|
||||||
// now it might happen, that the body is *in* a multipart, if
|
// now it might happen, that the body is *in* a multipart, if
|
||||||
// that's the case, look, if we've already applied a body
|
// that's the case, look, if we've already applied a body
|
||||||
// (body.is_empty()) and set it, if needed
|
// (body.is_empty()) and set it, if needed
|
||||||
if body.text.is_none() && subpart.ctype.mimetype == "text/plain" {
|
if body.plain.is_none() && subpart.ctype.mimetype == "text/plain" {
|
||||||
body.text = subpart.get_body().ok();
|
body.plain = subpart.get_body().ok();
|
||||||
} else if body.html.is_none() && subpart.ctype.mimetype == "text/html" {
|
} else if body.html.is_none() && subpart.ctype.mimetype == "text/html" {
|
||||||
body.html = subpart.get_body().ok();
|
body.html = subpart.get_body().ok();
|
||||||
}
|
}
|
||||||
|
@ -918,6 +894,7 @@ impl TryFrom<&Fetch> for Msg {
|
||||||
uid,
|
uid,
|
||||||
date,
|
date,
|
||||||
raw,
|
raw,
|
||||||
|
..Self::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,26 +3,61 @@
|
||||||
//! This module provides subcommands, arguments and a command matcher related to message template.
|
//! This module provides subcommands, arguments and a command matcher related to message template.
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{self, App, Arg, ArgMatches, SubCommand};
|
use clap::{self, App, AppSettings, Arg, ArgMatches, SubCommand, Values};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
|
||||||
use crate::domain::msg::{self, arg::uid_arg};
|
use crate::domain::msg::{self, arg::uid_arg};
|
||||||
|
|
||||||
|
type Subject<'a> = Option<&'a str>;
|
||||||
|
type From<'a> = Option<Values<'a>>;
|
||||||
|
type To<'a> = Option<Values<'a>>;
|
||||||
|
type Cc<'a> = Option<Values<'a>>;
|
||||||
|
type Bcc<'a> = Option<Values<'a>>;
|
||||||
|
type Headers<'a> = Option<Values<'a>>;
|
||||||
|
type Body<'a> = Option<&'a str>;
|
||||||
|
type Signature<'a> = Option<&'a str>;
|
||||||
type Uid<'a> = &'a str;
|
type Uid<'a> = &'a str;
|
||||||
type All = bool;
|
type All = bool;
|
||||||
|
|
||||||
/// Message template commands.
|
/// Message template commands.
|
||||||
pub enum Command<'a> {
|
pub enum Command<'a> {
|
||||||
New,
|
New(
|
||||||
|
Subject<'a>,
|
||||||
|
From<'a>,
|
||||||
|
To<'a>,
|
||||||
|
Cc<'a>,
|
||||||
|
Bcc<'a>,
|
||||||
|
Headers<'a>,
|
||||||
|
Body<'a>,
|
||||||
|
Signature<'a>,
|
||||||
|
),
|
||||||
Reply(Uid<'a>, All),
|
Reply(Uid<'a>, All),
|
||||||
Forward(Uid<'a>),
|
Forward(Uid<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Message template command matcher.
|
/// Message template command matcher.
|
||||||
pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Command<'a>>> {
|
pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Command<'a>>> {
|
||||||
if let Some(_) = m.subcommand_matches("new") {
|
if let Some(m) = m.subcommand_matches("new") {
|
||||||
debug!("new command matched");
|
debug!("new command matched");
|
||||||
return Ok(Some(Command::New));
|
let subject = m.value_of("subject");
|
||||||
|
debug!("subject: `{:?}`", subject);
|
||||||
|
let from = m.values_of("from");
|
||||||
|
debug!("from: `{:?}`", from);
|
||||||
|
let to = m.values_of("to");
|
||||||
|
debug!("to: `{:?}`", to);
|
||||||
|
let cc = m.values_of("cc");
|
||||||
|
debug!("cc: `{:?}`", cc);
|
||||||
|
let bcc = m.values_of("bcc");
|
||||||
|
debug!("bcc: `{:?}`", bcc);
|
||||||
|
let headers = m.values_of("header");
|
||||||
|
debug!("headers: `{:?}`", headers);
|
||||||
|
let body = m.value_of("body");
|
||||||
|
debug!("body: `{:?}`", body);
|
||||||
|
let sig = m.value_of("signature");
|
||||||
|
debug!("signature: `{:?}`", sig);
|
||||||
|
return Ok(Some(Command::New(
|
||||||
|
subject, from, to, cc, bcc, headers, body, sig,
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(m) = m.subcommand_matches("reply") {
|
if let Some(m) = m.subcommand_matches("reply") {
|
||||||
|
@ -100,6 +135,7 @@ pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
|
||||||
vec![SubCommand::with_name("template")
|
vec![SubCommand::with_name("template")
|
||||||
.aliases(&["tpl"])
|
.aliases(&["tpl"])
|
||||||
.about("Generates a message template")
|
.about("Generates a message template")
|
||||||
|
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("new")
|
SubCommand::with_name("new")
|
||||||
.aliases(&["n"])
|
.aliases(&["n"])
|
||||||
|
|
|
@ -1,25 +1,42 @@
|
||||||
use std::convert::TryFrom;
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
convert::TryFrom,
|
||||||
|
io::{self, BufRead},
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use log::trace;
|
use atty::Stream;
|
||||||
|
use clap::Values;
|
||||||
|
use log::{debug, trace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::entity::Account,
|
config::entity::Account,
|
||||||
domain::{
|
domain::{
|
||||||
imap::service::ImapServiceInterface,
|
imap::service::ImapServiceInterface,
|
||||||
msg::entity::{Msg, MsgSerialized},
|
msg::{
|
||||||
|
body::Body,
|
||||||
|
entity::{Msg, MsgSerialized},
|
||||||
|
headers::Headers,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
output::service::OutputServiceInterface,
|
output::service::OutputServiceInterface,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn new<OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>(
|
pub fn new<'a, OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>(
|
||||||
|
subject: Option<&'a str>,
|
||||||
|
from: Option<Values<'a>>,
|
||||||
|
to: Option<Values<'a>>,
|
||||||
|
cc: Option<Values<'a>>,
|
||||||
|
bcc: Option<Values<'a>>,
|
||||||
|
headers: Option<Values<'a>>,
|
||||||
|
body: Option<&'a str>,
|
||||||
|
sig: Option<&'a str>,
|
||||||
account: &Account,
|
account: &Account,
|
||||||
output: &OutputService,
|
output: &OutputService,
|
||||||
imap: &mut ImapService,
|
imap: &mut ImapService,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let msg = Msg::new(&account);
|
let mut msg = Msg::new(&account);
|
||||||
// FIXME
|
override_msg_with_args(&mut msg, subject, from, to, cc, bcc, headers, body, sig);
|
||||||
// override_msg_with_args(&mut msg, &matches);
|
|
||||||
trace!("message: {:#?}", msg);
|
trace!("message: {:#?}", msg);
|
||||||
output.print(MsgSerialized::try_from(&msg)?)?;
|
output.print(MsgSerialized::try_from(&msg)?)?;
|
||||||
imap.logout()?;
|
imap.logout()?;
|
||||||
|
@ -62,89 +79,85 @@ pub fn forward<OutputService: OutputServiceInterface, ImapService: ImapServiceIn
|
||||||
// == Helper functions ==
|
// == Helper functions ==
|
||||||
// -- Template Subcommands --
|
// -- Template Subcommands --
|
||||||
// These functions are more used for the "template" subcommand
|
// These functions are more used for the "template" subcommand
|
||||||
// fn override_msg_with_args(msg: &mut Msg) {
|
fn override_msg_with_args<'a>(
|
||||||
// // -- Collecting credentials --
|
msg: &mut Msg,
|
||||||
// let from: Vec<String> = match matches.values_of("from") {
|
subject: Option<&'a str>,
|
||||||
// Some(from) => from.map(|arg| arg.to_string()).collect(),
|
from: Option<Values<'a>>,
|
||||||
// None => msg.headers.from.clone(),
|
to: Option<Values<'a>>,
|
||||||
// };
|
cc: Option<Values<'a>>,
|
||||||
|
bcc: Option<Values<'a>>,
|
||||||
|
headers: Option<Values<'a>>,
|
||||||
|
body: Option<&'a str>,
|
||||||
|
sig: Option<&'a str>,
|
||||||
|
) {
|
||||||
|
// -- Collecting credentials --
|
||||||
|
let from: Vec<String> = match from {
|
||||||
|
Some(from) => from.map(|arg| arg.to_string()).collect(),
|
||||||
|
None => msg.headers.from.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
// let to: Vec<String> = match matches.values_of("to") {
|
let to: Vec<String> = match to {
|
||||||
// Some(to) => to.map(|arg| arg.to_string()).collect(),
|
Some(to) => to.map(|arg| arg.to_string()).collect(),
|
||||||
// None => Vec::new(),
|
None => Vec::new(),
|
||||||
// };
|
};
|
||||||
|
|
||||||
// let subject = matches
|
let subject = subject.map(String::from);
|
||||||
// .value_of("subject")
|
let cc: Option<Vec<String>> = cc.map(|cc| cc.map(|arg| arg.to_string()).collect());
|
||||||
// .and_then(|subject| Some(subject.to_string()));
|
let bcc: Option<Vec<String>> = bcc.map(|bcc| bcc.map(|arg| arg.to_string()).collect());
|
||||||
|
let signature = sig.map(String::from).or(msg.headers.signature.to_owned());
|
||||||
|
|
||||||
// let cc: Option<Vec<String>> = matches
|
let custom_headers: Option<HashMap<String, Vec<String>>> = {
|
||||||
// .values_of("cc")
|
if let Some(matched_headers) = headers {
|
||||||
// .and_then(|cc| Some(cc.map(|arg| arg.to_string()).collect()));
|
let mut custom_headers: HashMap<String, Vec<String>> = HashMap::new();
|
||||||
|
|
||||||
// let bcc: Option<Vec<String>> = matches
|
// collect the custom headers
|
||||||
// .values_of("bcc")
|
for header in matched_headers {
|
||||||
// .and_then(|bcc| Some(bcc.map(|arg| arg.to_string()).collect()));
|
let mut header = header.split(":");
|
||||||
|
let key = header.next().unwrap_or_default();
|
||||||
|
let val = header.next().unwrap_or_default().trim_start();
|
||||||
|
|
||||||
// let signature = matches
|
custom_headers.insert(key.to_string(), vec![val.to_string()]);
|
||||||
// .value_of("signature")
|
}
|
||||||
// .and_then(|signature| Some(signature.to_string()))
|
|
||||||
// .or(msg.headers.signature.clone());
|
|
||||||
|
|
||||||
// let custom_headers: Option<HashMap<String, Vec<String>>> = {
|
Some(custom_headers)
|
||||||
// if let Some(matched_headers) = matches.values_of("header") {
|
} else {
|
||||||
// let mut custom_headers: HashMap<String, Vec<String>> = HashMap::new();
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// // collect the custom headers
|
let body = {
|
||||||
// for header in matched_headers {
|
if atty::isnt(Stream::Stdin) {
|
||||||
// let mut header = header.split(":");
|
let body = io::stdin()
|
||||||
// let key = header.next().unwrap_or_default();
|
.lock()
|
||||||
// let val = header.next().unwrap_or_default().trim_start();
|
.lines()
|
||||||
|
.filter_map(|line| line.ok())
|
||||||
|
.map(|line| line.to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n");
|
||||||
|
debug!("overriden body from stdin: {:?}", body);
|
||||||
|
body
|
||||||
|
} else if let Some(body) = body {
|
||||||
|
debug!("overriden body: {:?}", body);
|
||||||
|
body.to_string()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// debug!("overriden header: {}={}", key, val);
|
let body = Body::new_with_text(body);
|
||||||
|
|
||||||
// custom_headers.insert(key.to_string(), vec![val.to_string()]);
|
// -- Creating and printing --
|
||||||
// }
|
let headers = Headers {
|
||||||
|
from,
|
||||||
|
subject,
|
||||||
|
to,
|
||||||
|
cc,
|
||||||
|
bcc,
|
||||||
|
signature,
|
||||||
|
custom_headers,
|
||||||
|
..msg.headers.clone()
|
||||||
|
};
|
||||||
|
|
||||||
// Some(custom_headers)
|
msg.headers = headers;
|
||||||
// } else {
|
msg.body = body;
|
||||||
// None
|
}
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// let body = {
|
|
||||||
// if atty::isnt(Stream::Stdin) {
|
|
||||||
// let body = io::stdin()
|
|
||||||
// .lock()
|
|
||||||
// .lines()
|
|
||||||
// .filter_map(|line| line.ok())
|
|
||||||
// .map(|line| line.to_string())
|
|
||||||
// .collect::<Vec<String>>()
|
|
||||||
// .join("\n");
|
|
||||||
// debug!("overriden body from stdin: {:?}", body);
|
|
||||||
// body
|
|
||||||
// } else if let Some(body) = matches.value_of("body") {
|
|
||||||
// debug!("overriden body: {:?}", body);
|
|
||||||
// body.to_string()
|
|
||||||
// } else {
|
|
||||||
// String::new()
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// let body = Body::new_with_text(body);
|
|
||||||
|
|
||||||
// // -- Creating and printing --
|
|
||||||
// let headers = Headers {
|
|
||||||
// from,
|
|
||||||
// subject,
|
|
||||||
// to,
|
|
||||||
// cc,
|
|
||||||
// bcc,
|
|
||||||
// signature,
|
|
||||||
// custom_headers,
|
|
||||||
// ..msg.headers.clone()
|
|
||||||
// };
|
|
||||||
|
|
||||||
// msg.headers = headers;
|
|
||||||
// msg.body = body;
|
|
||||||
// }
|
|
||||||
|
|
28
src/main.rs
28
src/main.rs
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap;
|
use clap::{self, AppSettings};
|
||||||
use env_logger;
|
use env_logger;
|
||||||
use std::{convert::TryFrom, env};
|
use std::{convert::TryFrom, env};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -24,8 +24,10 @@ fn create_app<'a>() -> clap::App<'a, 'a> {
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.about(env!("CARGO_PKG_DESCRIPTION"))
|
.about(env!("CARGO_PKG_DESCRIPTION"))
|
||||||
.author(env!("CARGO_PKG_AUTHORS"))
|
.author(env!("CARGO_PKG_AUTHORS"))
|
||||||
.args(&output::arg::args())
|
.setting(AppSettings::GlobalVersion)
|
||||||
|
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||||
.args(&config::arg::args())
|
.args(&config::arg::args())
|
||||||
|
.args(&output::arg::args())
|
||||||
.arg(mbox::arg::source_arg())
|
.arg(mbox::arg::source_arg())
|
||||||
.subcommands(compl::arg::subcmds())
|
.subcommands(compl::arg::subcmds())
|
||||||
.subcommands(imap::arg::subcmds())
|
.subcommands(imap::arg::subcmds())
|
||||||
|
@ -59,7 +61,7 @@ fn main() -> Result<()> {
|
||||||
// See https://github.com/soywod/himalaya/issues/115.
|
// See https://github.com/soywod/himalaya/issues/115.
|
||||||
match compl::arg::matches(&m)? {
|
match compl::arg::matches(&m)? {
|
||||||
Some(compl::arg::Command::Generate(shell)) => {
|
Some(compl::arg::Command::Generate(shell)) => {
|
||||||
return compl::handler::generate(shell, create_app());
|
return compl::handler::generate(create_app(), shell);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
@ -84,7 +86,7 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
// Check mailbox matches.
|
// Check mailbox matches.
|
||||||
match mbox::arg::matches(&m)? {
|
match mbox::arg::matches(&m)? {
|
||||||
Some(mbox::arg::Commands::List) => {
|
Some(mbox::arg::Command::List) => {
|
||||||
return mbox::handler::list(&output, &mut imap);
|
return mbox::handler::list(&output, &mut imap);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
@ -130,26 +132,30 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(msg::arg::Command::Flag(m)) => match m {
|
Some(msg::arg::Command::Flag(m)) => match m {
|
||||||
msg::flag::arg::Command::Set(uid, flags) => {
|
Some(msg::flag::arg::Command::Set(uid, flags)) => {
|
||||||
return msg::flag::handler::set(uid, flags, &mut imap);
|
return msg::flag::handler::set(uid, flags, &mut imap);
|
||||||
}
|
}
|
||||||
msg::flag::arg::Command::Add(uid, flags) => {
|
Some(msg::flag::arg::Command::Add(uid, flags)) => {
|
||||||
return msg::flag::handler::add(uid, flags, &mut imap);
|
return msg::flag::handler::add(uid, flags, &mut imap);
|
||||||
}
|
}
|
||||||
msg::flag::arg::Command::Remove(uid, flags) => {
|
Some(msg::flag::arg::Command::Remove(uid, flags)) => {
|
||||||
return msg::flag::handler::remove(uid, flags, &mut imap);
|
return msg::flag::handler::remove(uid, flags, &mut imap);
|
||||||
}
|
}
|
||||||
|
_ => (),
|
||||||
},
|
},
|
||||||
Some(msg::arg::Command::Tpl(m)) => match m {
|
Some(msg::arg::Command::Tpl(m)) => match m {
|
||||||
msg::tpl::arg::Command::New => {
|
Some(msg::tpl::arg::Command::New(sub, from, to, cc, bcc, h, body, sig)) => {
|
||||||
return msg::tpl::handler::new(&account, &output, &mut imap);
|
return msg::tpl::handler::new(
|
||||||
|
sub, from, to, cc, bcc, h, body, sig, &account, &output, &mut imap,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
msg::tpl::arg::Command::Reply(uid, all) => {
|
Some(msg::tpl::arg::Command::Reply(uid, all)) => {
|
||||||
return msg::tpl::handler::reply(uid, all, &account, &output, &mut imap);
|
return msg::tpl::handler::reply(uid, all, &account, &output, &mut imap);
|
||||||
}
|
}
|
||||||
msg::tpl::arg::Command::Forward(uid) => {
|
Some(msg::tpl::arg::Command::Forward(uid)) => {
|
||||||
return msg::tpl::handler::forward(uid, &account, &output, &mut imap);
|
return msg::tpl::handler::forward(uid, &account, &output, &mut imap);
|
||||||
}
|
}
|
||||||
|
_ => (),
|
||||||
},
|
},
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue