adjust api, test commands with a greenmail instance

This commit is contained in:
Clément DOUIN 2023-12-09 09:36:26 +01:00
parent ef3214f36f
commit 04e721d591
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
31 changed files with 150 additions and 112 deletions

View file

@ -7,7 +7,7 @@ display-name = "My example account"
email = "example@localhost" email = "example@localhost"
sync = true sync = true
sync-dir = "./.sync" sync-dir = "/tmp/himalaya-sync-example"
# The default backend used for all the features like adding folders, # The default backend used for all the features like adding folders,
# listing envelopes or copying messages. # listing envelopes or copying messages.
@ -21,7 +21,7 @@ imap.ssl = false
imap.starttls = false imap.starttls = false
imap.insecure = true imap.insecure = true
imap.auth = "passwd" imap.auth = "passwd"
imap.passwd.raw = "example" imap.passwd.keyring = "example-passwd"
# Override the backend used for sending messages. # Override the backend used for sending messages.
message.send.backend = "smtp" message.send.backend = "smtp"
@ -35,3 +35,22 @@ smtp.starttls = false
smtp.insecure = true smtp.insecure = true
smtp.auth = "passwd" smtp.auth = "passwd"
smtp.passwd.raw = "example" smtp.passwd.raw = "example"
[example-2]
display-name = "My example account 2"
email = "example2@localhost"
backend = "imap"
imap.host = "localhost"
imap.port = 3143
imap.login = "example2@localhost"
imap.ssl = false
imap.starttls = false
imap.insecure = true
imap.auth = "passwd"
imap.passwd.raw = "example"
message.send.backend = "sendmail"
sendmail.cmd = "sendmail"

View file

@ -12,13 +12,13 @@ pub struct AccountNameArg {
} }
/// The account name flag parser. /// The account name flag parser.
#[derive(Debug, Parser)] #[derive(Debug, Default, Parser)]
pub struct AccountNameFlag { pub struct AccountNameFlag {
/// Override the default account. /// Override the default account.
/// ///
/// An account name corresponds to an entry in the table at the /// An account name corresponds to an entry in the table at the
/// root level of your TOML configuration file. /// root level of your TOML configuration file.
#[arg(long = "account", short = 'a', global = true)] #[arg(long = "account", short = 'a')]
#[arg(name = "account_name", value_name = "NAME")] #[arg(name = "account_name", value_name = "NAME")]
pub name: Option<String>, pub name: Option<String>,
} }

View file

@ -15,7 +15,7 @@ use self::{
/// ///
/// An account is a set of settings, identified by an account /// An account is a set of settings, identified by an account
/// name. Settings are directly taken from your TOML configuration /// name. Settings are directly taken from your TOML configuration
/// file. /// file. This subcommand allows you to manage them.
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum AccountSubcommand { pub enum AccountSubcommand {
#[command(alias = "cfg")] #[command(alias = "cfg")]
@ -24,7 +24,7 @@ pub enum AccountSubcommand {
#[command(alias = "lst")] #[command(alias = "lst")]
List(AccountListCommand), List(AccountListCommand),
#[command()] #[command(alias = "synchronize", alias = "synchronise")]
Sync(AccountSyncCommand), Sync(AccountSyncCommand),
} }

View file

@ -35,7 +35,7 @@ const SUB_PROGRESS_DONE_STYLE: Lazy<ProgressStyle> = Lazy::new(|| {
/// Synchronize an account. /// Synchronize an account.
/// ///
/// This command allows you to synchronize all folders and emails /// This command allows you to synchronize all folders and emails
/// (including envelopes and messages) of an account into a local /// (including envelopes and messages) of a given account into a local
/// Maildir folder. /// Maildir folder.
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct AccountSyncCommand { pub struct AccountSyncCommand {

View file

@ -123,7 +123,10 @@ impl From<Iter<'_, String, TomlAccountConfig>> for Accounts {
Account::new(name, &backends, account.default.unwrap_or_default()) Account::new(name, &backends, account.default.unwrap_or_default())
}) })
.collect(); .collect();
accounts.sort_by(|a, b| b.name.partial_cmp(&a.name).unwrap());
// sort accounts by name
accounts.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap());
Self(accounts) Self(accounts)
} }
} }

View file

@ -1,7 +1,7 @@
use clap::Parser; use clap::Parser;
/// The disable cache flag parser. /// The disable cache flag parser.
#[derive(Debug, Parser)] #[derive(Debug, Default, Parser)]
pub struct CacheDisableFlag { pub struct CacheDisableFlag {
/// Disable any sort of cache. /// Disable any sort of cache.
/// ///

View file

@ -82,6 +82,7 @@ pub enum HimalayaCommand {
Account(AccountSubcommand), Account(AccountSubcommand),
#[command(subcommand)] #[command(subcommand)]
#[command(visible_alias = "mailbox", aliases = ["mailboxes", "mboxes", "mbox"])]
#[command(alias = "folders")] #[command(alias = "folders")]
Folder(FolderSubcommand), Folder(FolderSubcommand),

View file

@ -8,7 +8,7 @@ use crate::{
cache::arg::disable::CacheDisableFlag, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, config::TomlConfig,
flag::arg::ids_and_flags::{into_tuple, IdsAndFlagsArgs}, flag::arg::ids_and_flags::{into_tuple, IdsAndFlagsArgs},
folder::arg::name::FolderNameArg, folder::arg::name::FolderNameOptionalFlag,
printer::Printer, printer::Printer,
}; };
@ -19,7 +19,7 @@ use crate::{
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct FlagAddCommand { pub struct FlagAddCommand {
#[command(flatten)] #[command(flatten)]
pub folder: FolderNameArg, pub folder: FolderNameOptionalFlag,
#[command(flatten)] #[command(flatten)]
pub args: IdsAndFlagsArgs, pub args: IdsAndFlagsArgs,

View file

@ -8,7 +8,7 @@ use crate::{
cache::arg::disable::CacheDisableFlag, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, config::TomlConfig,
flag::arg::ids_and_flags::{into_tuple, IdsAndFlagsArgs}, flag::arg::ids_and_flags::{into_tuple, IdsAndFlagsArgs},
folder::arg::name::FolderNameArg, folder::arg::name::FolderNameOptionalFlag,
printer::Printer, printer::Printer,
}; };
@ -19,7 +19,7 @@ use crate::{
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct FlagRemoveCommand { pub struct FlagRemoveCommand {
#[command(flatten)] #[command(flatten)]
pub folder: FolderNameArg, pub folder: FolderNameOptionalFlag,
#[command(flatten)] #[command(flatten)]
pub args: IdsAndFlagsArgs, pub args: IdsAndFlagsArgs,

View file

@ -8,7 +8,7 @@ use crate::{
cache::arg::disable::CacheDisableFlag, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, config::TomlConfig,
flag::arg::ids_and_flags::{into_tuple, IdsAndFlagsArgs}, flag::arg::ids_and_flags::{into_tuple, IdsAndFlagsArgs},
folder::arg::name::FolderNameArg, folder::arg::name::FolderNameOptionalFlag,
printer::Printer, printer::Printer,
}; };
@ -19,7 +19,7 @@ use crate::{
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct FlagSetCommand { pub struct FlagSetCommand {
#[command(flatten)] #[command(flatten)]
pub folder: FolderNameArg, pub folder: FolderNameOptionalFlag,
#[command(flatten)] #[command(flatten)]
pub args: IdsAndFlagsArgs, pub args: IdsAndFlagsArgs,

View file

@ -6,7 +6,7 @@ use std::ops::Deref;
pub struct MessageRawBodyArg { pub struct MessageRawBodyArg {
/// Prefill the template with a custom body. /// Prefill the template with a custom body.
#[arg(trailing_var_arg = true)] #[arg(trailing_var_arg = true)]
#[arg(name = "body-raw")] #[arg(name = "body_raw", value_name = "BODY")]
pub raw: Vec<String>, pub raw: Vec<String>,
} }

View file

@ -1,3 +1,20 @@
use clap::Parser;
pub mod body; pub mod body;
pub mod header; pub mod header;
pub mod reply; pub mod reply;
/// The raw message argument parser.
#[derive(Debug, Parser)]
pub struct MessageRawArg {
/// The raw message, including headers and body.
#[arg(trailing_var_arg = true)]
#[arg(name = "message_raw", value_name = "MESSAGE")]
pub raw: Vec<String>,
}
impl MessageRawArg {
pub fn raw(self) -> String {
self.raw.join(" ").replace("\r", "").replace("\n", "\r\n")
}
}

View file

@ -6,8 +6,8 @@ use uuid::Uuid;
use crate::{ use crate::{
account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag, account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameArg, config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs,
printer::Printer, folder::arg::name::FolderNameOptionalFlag, printer::Printer,
}; };
/// Download all attachments for the given message. /// Download all attachments for the given message.
@ -17,7 +17,7 @@ use crate::{
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct AttachmentDownloadCommand { pub struct AttachmentDownloadCommand {
#[command(flatten)] #[command(flatten)]
pub folder: FolderNameArg, pub folder: FolderNameOptionalFlag,
#[command(flatten)] #[command(flatten)]
pub envelopes: EnvelopeIdsArgs, pub envelopes: EnvelopeIdsArgs,

View file

@ -4,8 +4,8 @@ use log::info;
use crate::{ use crate::{
account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag, account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameArg, config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs,
printer::Printer, folder::arg::name::FolderNameOptionalFlag, printer::Printer,
}; };
/// Mark as deleted a message from a folder. /// Mark as deleted a message from a folder.
@ -17,7 +17,7 @@ use crate::{
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct MessageDeleteCommand { pub struct MessageDeleteCommand {
#[command(flatten)] #[command(flatten)]
pub folder: FolderNameArg, pub folder: FolderNameOptionalFlag,
#[command(flatten)] #[command(flatten)]
pub envelopes: EnvelopeIdsArgs, pub envelopes: EnvelopeIdsArgs,

View file

@ -1,8 +1,6 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use atty::Stream;
use clap::Parser; use clap::Parser;
use log::info; use log::info;
use std::io::{self, BufRead};
use crate::{ use crate::{
account::arg::name::AccountNameFlag, account::arg::name::AccountNameFlag,
@ -10,7 +8,7 @@ use crate::{
cache::arg::disable::CacheDisableFlag, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, config::TomlConfig,
envelope::arg::ids::EnvelopeIdArg, envelope::arg::ids::EnvelopeIdArg,
folder::arg::name::FolderNameArg, folder::arg::name::FolderNameOptionalFlag,
message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs}, message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs},
printer::Printer, printer::Printer,
ui::editor, ui::editor,
@ -25,7 +23,7 @@ use crate::{
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct MessageForwardCommand { pub struct MessageForwardCommand {
#[command(flatten)] #[command(flatten)]
pub folder: FolderNameArg, pub folder: FolderNameOptionalFlag,
#[command(flatten)] #[command(flatten)]
pub envelope: EnvelopeIdArg, pub envelope: EnvelopeIdArg,
@ -55,19 +53,6 @@ impl MessageForwardCommand {
config.clone().into_account_configs(account, cache)?; config.clone().into_account_configs(account, cache)?;
let backend = Backend::new(toml_account_config, account_config.clone(), true).await?; let backend = Backend::new(toml_account_config, account_config.clone(), true).await?;
let is_tty = atty::is(Stream::Stdin);
let is_json = printer.is_json();
let body = if !self.body.is_empty() && (is_tty || is_json) {
self.body.raw()
} else {
io::stdin()
.lock()
.lines()
.filter_map(Result::ok)
.collect::<Vec<String>>()
.join("\r\n")
};
let id = self.envelope.id; let id = self.envelope.id;
let tpl = backend let tpl = backend
.get_messages(folder, &[id]) .get_messages(folder, &[id])
@ -76,7 +61,7 @@ impl MessageForwardCommand {
.ok_or(anyhow!("cannot find message"))? .ok_or(anyhow!("cannot find message"))?
.to_forward_tpl_builder(&account_config) .to_forward_tpl_builder(&account_config)
.with_headers(self.headers.raw) .with_headers(self.headers.raw)
.with_body(body) .with_body(self.body.raw())
.build() .build()
.await?; .await?;
editor::edit_tpl_with_editor(&account_config, printer, &backend, tpl).await editor::edit_tpl_with_editor(&account_config, printer, &backend, tpl).await

View file

@ -4,7 +4,10 @@ use log::{debug, info};
use mail_builder::MessageBuilder; use mail_builder::MessageBuilder;
use url::Url; use url::Url;
use crate::{backend::Backend, config::TomlConfig, printer::Printer, ui::editor}; use crate::{
account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, printer::Printer, ui::editor,
};
/// Parse and edit a message from a mailto URL string. /// Parse and edit a message from a mailto URL string.
/// ///
@ -17,19 +20,31 @@ pub struct MessageMailtoCommand {
/// The mailto url. /// The mailto url.
#[arg()] #[arg()]
pub url: Url, pub url: Url,
#[command(flatten)]
pub cache: CacheDisableFlag,
#[command(flatten)]
pub account: AccountNameFlag,
} }
impl MessageMailtoCommand { impl MessageMailtoCommand {
pub fn new(url: &str) -> Result<Self> { pub fn new(url: &str) -> Result<Self> {
let url = Url::parse(url)?; Ok(Self {
Ok(Self { url }) url: Url::parse(url)?,
cache: Default::default(),
account: Default::default(),
})
} }
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
info!("executing message mailto command"); info!("executing message mailto command");
let account = self.account.name.as_ref().map(String::as_str);
let cache = self.cache.disable;
let (toml_account_config, account_config) = let (toml_account_config, account_config) =
config.clone().into_account_configs(None, false)?; config.clone().into_account_configs(account, cache)?;
let backend = Backend::new(toml_account_config, account_config.clone(), true).await?; let backend = Backend::new(toml_account_config, account_config.clone(), true).await?;
let mut builder = MessageBuilder::new().to(self.url.path()); let mut builder = MessageBuilder::new().to(self.url.path());

View file

@ -32,32 +32,34 @@ pub enum MessageSubcommand {
#[command(arg_required_else_help = true)] #[command(arg_required_else_help = true)]
Read(MessageReadCommand), Read(MessageReadCommand),
#[command(alias = "add", alias = "create", alias = "new", alias = "compose")] #[command(aliases = ["add", "create", "new", "compose"])]
Write(MessageWriteCommand), Write(MessageWriteCommand),
#[command()] #[command()]
Reply(MessageReplyCommand), Reply(MessageReplyCommand),
#[command(alias = "fwd")] #[command(aliases = ["fwd", "fd"])]
Forward(MessageForwardCommand), Forward(MessageForwardCommand),
#[command()] #[command()]
Mailto(MessageMailtoCommand), Mailto(MessageMailtoCommand),
#[command(arg_required_else_help = true)] #[command(arg_required_else_help = true)]
#[command(alias = "add", alias = "create")]
Save(MessageSaveCommand), Save(MessageSaveCommand),
#[command(arg_required_else_help = true)] #[command(arg_required_else_help = true)]
Send(MessageSendCommand), Send(MessageSendCommand),
#[command(arg_required_else_help = true)] #[command(arg_required_else_help = true)]
#[command(aliases = ["cpy", "cp"])]
Copy(MessageCopyCommand), Copy(MessageCopyCommand),
#[command(arg_required_else_help = true)] #[command(arg_required_else_help = true)]
#[command(alias = "mv")]
Move(MessageMoveCommand), Move(MessageMoveCommand),
#[command(arg_required_else_help = true)] #[command(arg_required_else_help = true)]
#[command(aliases = ["remove", "rm"])]
Delete(MessageDeleteCommand), Delete(MessageDeleteCommand),
} }

View file

@ -5,8 +5,8 @@ use mml::message::FilterParts;
use crate::{ use crate::{
account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag, account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameArg, config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs,
printer::Printer, folder::arg::name::FolderNameOptionalFlag, printer::Printer,
}; };
/// Read a message. /// Read a message.
@ -15,7 +15,7 @@ use crate::{
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct MessageReadCommand { pub struct MessageReadCommand {
#[command(flatten)] #[command(flatten)]
pub folder: FolderNameArg, pub folder: FolderNameOptionalFlag,
#[command(flatten)] #[command(flatten)]
pub envelopes: EnvelopeIdsArgs, pub envelopes: EnvelopeIdsArgs,

View file

@ -1,9 +1,7 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use atty::Stream;
use clap::Parser; use clap::Parser;
use email::flag::Flag; use email::flag::Flag;
use log::info; use log::info;
use std::io::{self, BufRead};
use crate::{ use crate::{
account::arg::name::AccountNameFlag, account::arg::name::AccountNameFlag,
@ -11,7 +9,7 @@ use crate::{
cache::arg::disable::CacheDisableFlag, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, config::TomlConfig,
envelope::arg::ids::EnvelopeIdArg, envelope::arg::ids::EnvelopeIdArg,
folder::arg::name::FolderNameArg, folder::arg::name::FolderNameOptionalFlag,
message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs, reply::MessageReplyAllArg}, message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs, reply::MessageReplyAllArg},
printer::Printer, printer::Printer,
ui::editor, ui::editor,
@ -26,7 +24,7 @@ use crate::{
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct MessageReplyCommand { pub struct MessageReplyCommand {
#[command(flatten)] #[command(flatten)]
pub folder: FolderNameArg, pub folder: FolderNameOptionalFlag,
#[command(flatten)] #[command(flatten)]
pub envelope: EnvelopeIdArg, pub envelope: EnvelopeIdArg,
@ -59,19 +57,6 @@ impl MessageReplyCommand {
config.clone().into_account_configs(account, cache)?; config.clone().into_account_configs(account, cache)?;
let backend = Backend::new(toml_account_config, account_config.clone(), true).await?; let backend = Backend::new(toml_account_config, account_config.clone(), true).await?;
let is_tty = atty::is(Stream::Stdin);
let is_json = printer.is_json();
let body = if !self.body.is_empty() && (is_tty || is_json) {
self.body.raw()
} else {
io::stdin()
.lock()
.lines()
.filter_map(Result::ok)
.collect::<Vec<String>>()
.join("\r\n")
};
let id = self.envelope.id; let id = self.envelope.id;
let tpl = backend let tpl = backend
.get_messages(folder, &[id]) .get_messages(folder, &[id])
@ -80,7 +65,7 @@ impl MessageReplyCommand {
.ok_or(anyhow!("cannot find message {id}"))? .ok_or(anyhow!("cannot find message {id}"))?
.to_reply_tpl_builder(&account_config) .to_reply_tpl_builder(&account_config)
.with_headers(self.headers.raw) .with_headers(self.headers.raw)
.with_body(body) .with_body(self.body.raw())
.with_reply_all(self.reply.all) .with_reply_all(self.reply.all)
.build() .build()
.await?; .await?;

View file

@ -6,7 +6,7 @@ use std::io::{self, BufRead};
use crate::{ use crate::{
account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag, account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, folder::arg::name::FolderNameArg, message::arg::body::MessageRawBodyArg, config::TomlConfig, folder::arg::name::FolderNameOptionalFlag, message::arg::MessageRawArg,
printer::Printer, printer::Printer,
}; };
@ -16,10 +16,10 @@ use crate::{
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct MessageSaveCommand { pub struct MessageSaveCommand {
#[command(flatten)] #[command(flatten)]
pub folder: FolderNameArg, pub folder: FolderNameOptionalFlag,
#[command(flatten)] #[command(flatten)]
pub body: MessageRawBodyArg, pub message: MessageRawArg,
#[command(flatten)] #[command(flatten)]
pub cache: CacheDisableFlag, pub cache: CacheDisableFlag,
@ -43,7 +43,7 @@ impl MessageSaveCommand {
let is_tty = atty::is(Stream::Stdin); let is_tty = atty::is(Stream::Stdin);
let is_json = printer.is_json(); let is_json = printer.is_json();
let msg = if is_tty || is_json { let msg = if is_tty || is_json {
self.body.raw() self.message.raw()
} else { } else {
io::stdin() io::stdin()
.lock() .lock()

View file

@ -7,7 +7,7 @@ use std::io::{self, BufRead};
use crate::{ use crate::{
account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag, account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, message::arg::body::MessageRawBodyArg, printer::Printer, config::TomlConfig, message::arg::MessageRawArg, printer::Printer,
}; };
/// Send a message. /// Send a message.
@ -17,7 +17,7 @@ use crate::{
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct MessageSendCommand { pub struct MessageSendCommand {
#[command(flatten)] #[command(flatten)]
pub body: MessageRawBodyArg, pub message: MessageRawArg,
#[command(flatten)] #[command(flatten)]
pub cache: CacheDisableFlag, pub cache: CacheDisableFlag,
@ -41,7 +41,7 @@ impl MessageSendCommand {
let is_tty = atty::is(Stream::Stdin); let is_tty = atty::is(Stream::Stdin);
let is_json = printer.is_json(); let is_json = printer.is_json();
let msg = if is_tty || is_json { let msg = if is_tty || is_json {
self.body.raw() self.message.raw()
} else { } else {
io::stdin() io::stdin()
.lock() .lock()

View file

@ -1,9 +1,7 @@
use anyhow::Result; use anyhow::Result;
use atty::Stream;
use clap::Parser; use clap::Parser;
use email::message::Message; use email::message::Message;
use log::info; use log::info;
use std::io::{self, BufRead};
use crate::{ use crate::{
account::arg::name::AccountNameFlag, account::arg::name::AccountNameFlag,
@ -47,22 +45,9 @@ impl MessageWriteCommand {
config.clone().into_account_configs(account, cache)?; config.clone().into_account_configs(account, cache)?;
let backend = Backend::new(toml_account_config, account_config.clone(), true).await?; let backend = Backend::new(toml_account_config, account_config.clone(), true).await?;
let is_tty = atty::is(Stream::Stdin);
let is_json = printer.is_json();
let body = if !self.body.is_empty() && (is_tty || is_json) {
self.body.raw()
} else {
io::stdin()
.lock()
.lines()
.filter_map(Result::ok)
.collect::<Vec<String>>()
.join("\r\n")
};
let tpl = Message::new_tpl_builder(&account_config) let tpl = Message::new_tpl_builder(&account_config)
.with_headers(self.headers.raw) .with_headers(self.headers.raw)
.with_body(body) .with_body(self.body.raw())
.build() .build()
.await?; .await?;

View file

@ -4,9 +4,9 @@ use std::ops::Deref;
/// The raw template body argument parser. /// The raw template body argument parser.
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct TemplateRawBodyArg { pub struct TemplateRawBodyArg {
/// Prefill the template with a custom body. /// Prefill the template with a custom MML body.
#[arg(trailing_var_arg = true)] #[arg(trailing_var_arg = true)]
#[arg(name = "body-raw")] #[arg(name = "body_raw", value_name = "BODY")]
pub raw: Vec<String>, pub raw: Vec<String>,
} }

View file

@ -1 +1,18 @@
pub mod body; pub mod body;
use clap::Parser;
/// The raw template argument parser.
#[derive(Debug, Parser)]
pub struct TemplateRawArg {
/// The raw template, including headers and MML body.
#[arg(trailing_var_arg = true)]
#[arg(name = "template_raw", value_name = "TEMPLATE")]
pub raw: Vec<String>,
}
impl TemplateRawArg {
pub fn raw(self) -> String {
self.raw.join(" ").replace("\r", "")
}
}

View file

@ -8,7 +8,7 @@ use crate::{
cache::arg::disable::CacheDisableFlag, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, config::TomlConfig,
envelope::arg::ids::EnvelopeIdArg, envelope::arg::ids::EnvelopeIdArg,
folder::arg::name::FolderNameArg, folder::arg::name::FolderNameOptionalFlag,
message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs}, message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs},
printer::Printer, printer::Printer,
}; };
@ -21,7 +21,7 @@ use crate::{
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct TemplateForwardCommand { pub struct TemplateForwardCommand {
#[command(flatten)] #[command(flatten)]
pub folder: FolderNameArg, pub folder: FolderNameOptionalFlag,
#[command(flatten)] #[command(flatten)]
pub envelope: EnvelopeIdArg, pub envelope: EnvelopeIdArg,

View file

@ -25,7 +25,7 @@ use self::{
/// <https://crates.io/crates/mml-lib>. /// <https://crates.io/crates/mml-lib>.
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum TemplateSubcommand { pub enum TemplateSubcommand {
#[command(alias = "create", alias = "new", alias = "compose")] #[command(aliases = ["add", "create", "new", "compose"])]
Write(TemplateWriteCommand), Write(TemplateWriteCommand),
#[command(arg_required_else_help = true)] #[command(arg_required_else_help = true)]
@ -35,7 +35,7 @@ pub enum TemplateSubcommand {
#[command(alias = "fwd")] #[command(alias = "fwd")]
Forward(TemplateForwardCommand), Forward(TemplateForwardCommand),
#[command(alias = "add")] #[command()]
Save(TemplateSaveCommand), Save(TemplateSaveCommand),
#[command()] #[command()]

View file

@ -8,7 +8,7 @@ use crate::{
cache::arg::disable::CacheDisableFlag, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, config::TomlConfig,
envelope::arg::ids::EnvelopeIdArg, envelope::arg::ids::EnvelopeIdArg,
folder::arg::name::FolderNameArg, folder::arg::name::FolderNameOptionalFlag,
message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs, reply::MessageReplyAllArg}, message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs, reply::MessageReplyAllArg},
printer::Printer, printer::Printer,
}; };
@ -22,7 +22,7 @@ use crate::{
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct TemplateReplyCommand { pub struct TemplateReplyCommand {
#[command(flatten)] #[command(flatten)]
pub folder: FolderNameArg, pub folder: FolderNameOptionalFlag,
#[command(flatten)] #[command(flatten)]
pub envelope: EnvelopeIdArg, pub envelope: EnvelopeIdArg,

View file

@ -7,8 +7,8 @@ use std::io::{self, BufRead};
use crate::{ use crate::{
account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag, account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, email::template::arg::body::TemplateRawBodyArg, config::TomlConfig, email::template::arg::TemplateRawArg,
folder::arg::name::FolderNameArg, printer::Printer, folder::arg::name::FolderNameOptionalFlag, printer::Printer,
}; };
/// Save a template to a folder. /// Save a template to a folder.
@ -20,10 +20,10 @@ use crate::{
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct TemplateSaveCommand { pub struct TemplateSaveCommand {
#[command(flatten)] #[command(flatten)]
pub folder: FolderNameArg, pub folder: FolderNameOptionalFlag,
#[command(flatten)] #[command(flatten)]
pub body: TemplateRawBodyArg, pub template: TemplateRawArg,
#[command(flatten)] #[command(flatten)]
pub cache: CacheDisableFlag, pub cache: CacheDisableFlag,
@ -47,7 +47,7 @@ impl TemplateSaveCommand {
let is_tty = atty::is(Stream::Stdin); let is_tty = atty::is(Stream::Stdin);
let is_json = printer.is_json(); let is_json = printer.is_json();
let tpl = if is_tty || is_json { let tpl = if is_tty || is_json {
self.body.raw() self.template.raw()
} else { } else {
io::stdin() io::stdin()
.lock() .lock()

View file

@ -8,7 +8,7 @@ use std::io::{self, BufRead};
use crate::{ use crate::{
account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag, account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, email::template::arg::body::TemplateRawBodyArg, printer::Printer, config::TomlConfig, email::template::arg::TemplateRawArg, printer::Printer,
}; };
/// Send a template. /// Send a template.
@ -20,7 +20,7 @@ use crate::{
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct TemplateSendCommand { pub struct TemplateSendCommand {
#[command(flatten)] #[command(flatten)]
pub body: TemplateRawBodyArg, pub template: TemplateRawArg,
#[command(flatten)] #[command(flatten)]
pub cache: CacheDisableFlag, pub cache: CacheDisableFlag,
@ -44,7 +44,7 @@ impl TemplateSendCommand {
let is_tty = atty::is(Stream::Stdin); let is_tty = atty::is(Stream::Stdin);
let is_json = printer.is_json(); let is_json = printer.is_json();
let tpl = if is_tty || is_json { let tpl = if is_tty || is_json {
self.body.raw() self.template.raw()
} else { } else {
io::stdin() io::stdin()
.lock() .lock()

View file

@ -1,11 +1,12 @@
use clap::Parser; use clap::Parser;
use email::account::config::DEFAULT_INBOX_FOLDER; use email::account::config::DEFAULT_INBOX_FOLDER;
/// The folder name argument parser. /// The optional folder name flag parser.
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct FolderNameArg { pub struct FolderNameOptionalFlag {
/// The name of the folder. /// The name of the folder.
#[arg(name = "folder_name", value_name = "FOLDER")] #[arg(long = "folder", short = 'f')]
#[arg(name = "folder_name", value_name = "NAME", default_value = DEFAULT_INBOX_FOLDER)]
pub name: String, pub name: String,
} }
@ -17,6 +18,14 @@ pub struct FolderNameOptionalArg {
pub name: String, pub name: String,
} }
/// The required folder name argument parser.
#[derive(Debug, Parser)]
pub struct FolderNameArg {
/// The name of the folder.
#[arg(name = "folder_name", value_name = "FOLDER")]
pub name: String,
}
/// The source folder name argument parser. /// The source folder name argument parser.
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct SourceFolderNameArg { pub struct SourceFolderNameArg {

View file

@ -16,8 +16,8 @@ use self::{
/// Manage folders. /// Manage folders.
/// ///
/// A folder (AKA mailbox, or directory) contains envelopes and /// A folder (as known as mailbox, or directory) contains one or more
/// messages. This subcommand allows you to manage them. /// emails. This subcommand allows you to manage them.
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum FolderSubcommand { pub enum FolderSubcommand {
#[command(alias = "add", alias = "new")] #[command(alias = "add", alias = "new")]