Skip to content

Commit

Permalink
refactor message with clap derive api (part 2)
Browse files Browse the repository at this point in the history
  • Loading branch information
soywod committed Dec 7, 2023
1 parent a47902a commit b8ef771
Show file tree
Hide file tree
Showing 18 changed files with 413 additions and 132 deletions.
23 changes: 22 additions & 1 deletion src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use email::{
add::{imap::AddFlagsImap, maildir::AddFlagsMaildir},
remove::{imap::RemoveFlagsImap, maildir::RemoveFlagsMaildir},
set::{imap::SetFlagsImap, maildir::SetFlagsMaildir},
Flags,
Flag, Flags,
},
folder::{
add::{imap::AddFolderImap, maildir::AddFolderMaildir},
Expand Down Expand Up @@ -800,6 +800,27 @@ impl Backend {
id_mapper.create_alias(&*id)?;
Ok(id)
}

pub async fn add_flag(&self, folder: &str, ids: &[usize], flag: Flag) -> Result<()> {
let backend_kind = self.toml_account_config.add_flags_kind();
let id_mapper = self.build_id_mapper(folder, backend_kind)?;
let ids = Id::multiple(id_mapper.get_ids(ids)?);
self.backend.add_flag(folder, &ids, flag).await
}

pub async fn set_flag(&self, folder: &str, ids: &[usize], flag: Flag) -> Result<()> {
let backend_kind = self.toml_account_config.set_flags_kind();
let id_mapper = self.build_id_mapper(folder, backend_kind)?;
let ids = Id::multiple(id_mapper.get_ids(ids)?);
self.backend.set_flag(folder, &ids, flag).await
}

pub async fn remove_flag(&self, folder: &str, ids: &[usize], flag: Flag) -> Result<()> {
let backend_kind = self.toml_account_config.remove_flags_kind();
let id_mapper = self.build_id_mapper(folder, backend_kind)?;
let ids = Id::multiple(id_mapper.get_ids(ids)?);
self.backend.remove_flag(folder, &ids, flag).await
}
}

impl Deref for Backend {
Expand Down
10 changes: 5 additions & 5 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,27 +80,27 @@ pub struct Cli {

#[derive(Subcommand, Debug)]
pub enum HimalayaCommand {
/// Subcommand to manage accounts
/// Manage accounts
#[command(subcommand)]
#[command(alias = "accounts")]
Account(AccountSubcommand),

/// Subcommand to manage folders
/// Manage folders
#[command(subcommand)]
#[command(alias = "folders")]
Folder(FolderSubcommand),

/// Subcommand to manage envelopes
/// Manage envelopes
#[command(subcommand)]
#[command(alias = "envelopes")]
Envelope(EnvelopeSubcommand),

/// Subcommand to manage flags
/// Manage flags
#[command(subcommand)]
#[command(alias = "flags")]
Flag(FlagSubcommand),

/// Subcommand to manage messages
/// Manage messages
#[command(subcommand)]
#[command(alias = "messages", alias = "msgs", alias = "msg")]
Message(MessageSubcommand),
Expand Down
8 changes: 8 additions & 0 deletions src/email/envelope/arg/ids.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
use clap::Parser;

/// The envelope id argument parser
#[derive(Debug, Parser)]
pub struct EnvelopeIdArg {
/// The envelope id
#[arg(value_name = "ID", required = true)]
pub id: usize,
}

/// The envelopes ids arguments parser
#[derive(Debug, Parser)]
pub struct EnvelopeIdsArgs {
Expand Down
26 changes: 26 additions & 0 deletions src/email/message/arg/body.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use std::ops::Deref;

use clap::Parser;

/// The raw message body argument parser
#[derive(Debug, Parser)]
pub struct BodyRawArg {
/// Prefill the template with a custom body
#[arg(raw = true, required = false)]
#[arg(name = "body-raw", value_delimiter = ' ')]
pub raw: Vec<String>,
}

impl BodyRawArg {
pub fn raw(self) -> String {
self.raw.join(" ").replace("\r", "").replace("\n", "\r\n")
}
}

impl Deref for BodyRawArg {
type Target = Vec<String>;

fn deref(&self) -> &Self::Target {
&self.raw
}
}
20 changes: 20 additions & 0 deletions src/email/message/arg/header.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use clap::Parser;

/// The envelope id argument parser
#[derive(Debug, Parser)]
pub struct HeaderRawArgs {
/// Prefill the template with custom headers
///
/// A raw header should follow the pattern KEY:VAL.
#[arg(long = "header", short = 'H', required = false)]
#[arg(name = "header-raw", value_name = "KEY:VAL", value_parser = raw_header_parser)]
pub raw: Vec<(String, String)>,
}

pub fn raw_header_parser(raw_header: &str) -> Result<(String, String), String> {
if let Some((key, val)) = raw_header.split_once(":") {
Ok((key.trim().to_owned(), val.trim().to_owned()))
} else {
Err(format!("cannot parse raw header {raw_header:?}"))
}
}
2 changes: 2 additions & 0 deletions src/email/message/arg/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod body;
pub mod header;
4 changes: 3 additions & 1 deletion src/email/message/command/copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ impl MessageCopyCommand {
let ids = &self.envelopes.ids;
backend.copy_messages(from_folder, to_folder, ids).await?;

printer.print("Message(s) successfully copied from {from_folder} to {to_folder}!")
printer.print(format!(
"Message(s) successfully copied from {from_folder} to {to_folder}!"
))
}
}
83 changes: 83 additions & 0 deletions src/email/message/command/forward.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use anyhow::{anyhow, Result};
use atty::Stream;
use clap::Parser;
use log::info;
use std::io::{self, BufRead};

use crate::{
account::arg::name::AccountNameFlag,
backend::Backend,
cache::arg::disable::DisableCacheFlag,
config::TomlConfig,
envelope::arg::ids::EnvelopeIdArg,
folder::arg::name::FolderNameArg,
message::arg::{body::BodyRawArg, header::HeaderRawArgs},
printer::Printer,
ui::editor,
};

/// Forward a new message
#[derive(Debug, Parser)]
pub struct MessageForwardCommand {
#[command(flatten)]
pub folder: FolderNameArg,

#[command(flatten)]
pub envelope: EnvelopeIdArg,

/// Forward to all recipients
#[arg(long, short = 'A')]
pub all: bool,

#[command(flatten)]
pub headers: HeaderRawArgs,

#[command(flatten)]
pub body: BodyRawArg,

#[command(flatten)]
pub cache: DisableCacheFlag,

#[command(flatten)]
pub account: AccountNameFlag,
}

impl MessageForwardCommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
info!("executing message forward command");

let folder = &self.folder.name;
let account = self.account.name.as_ref().map(String::as_str);
let cache = self.cache.disable;

let (toml_account_config, account_config) =
config.clone().into_account_configs(account, cache)?;
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 tpl = backend
.get_messages(folder, &[id])
.await?
.first()
.ok_or(anyhow!("cannot find message"))?
.to_forward_tpl_builder(&account_config)
.with_headers(self.headers.raw)
.with_body(body)
.build()
.await?;
editor::edit_tpl_with_editor(&account_config, printer, &backend, tpl).await
}
}
46 changes: 46 additions & 0 deletions src/email/message/command/mailto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use anyhow::Result;
use clap::Parser;
use log::info;
use mail_builder::MessageBuilder;
use url::Url;

use crate::{backend::Backend, config::TomlConfig, printer::Printer, ui::editor};

/// Parse and edit a message from a mailto URL string
#[derive(Debug, Parser)]
pub struct MessageMailtoCommand {
/// The mailto url
#[arg()]
pub url: Url,
}

impl MessageMailtoCommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
info!("executing message mailto command");

let (toml_account_config, account_config) =
config.clone().into_account_configs(None, false)?;
let backend = Backend::new(toml_account_config, account_config.clone(), true).await?;

let mut builder = MessageBuilder::new().to(self.url.path());

for (key, val) in self.url.query_pairs() {
match key.to_lowercase().as_bytes() {
b"cc" => builder = builder.cc(val.to_string()),
b"bcc" => builder = builder.bcc(val.to_string()),
b"subject" => builder = builder.subject(val),
b"body" => builder = builder.text_body(val),
_ => (),
}
}

let tpl = account_config
.generate_tpl_interpreter()
.with_show_only_headers(account_config.email_writing_headers())
.build()
.from_msg_builder(builder)
.await?;

editor::edit_tpl_with_editor(&account_config, printer, &backend, tpl).await
}
}
30 changes: 28 additions & 2 deletions src/email/message/command/mod.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
pub mod copy;
pub mod delete;
pub mod forward;
pub mod mailto;
pub mod move_;
pub mod read;
pub mod reply;
pub mod save;
pub mod send;
pub mod write;

use anyhow::Result;
use clap::Subcommand;

use crate::{config::TomlConfig, printer::Printer};

use self::{
copy::MessageCopyCommand, delete::MessageDeleteCommand, move_::MessageMoveCommand,
read::MessageReadCommand, save::MessageSaveCommand, send::MessageSendCommand,
copy::MessageCopyCommand, delete::MessageDeleteCommand, forward::MessageForwardCommand,
mailto::MessageMailtoCommand, move_::MessageMoveCommand, read::MessageReadCommand,
reply::MessageReplyCommand, save::MessageSaveCommand, send::MessageSendCommand,
write::MessageWriteCommand,
};

/// Subcommand to manage messages
Expand All @@ -22,6 +28,22 @@ pub enum MessageSubcommand {
#[command(arg_required_else_help = true)]
Read(MessageReadCommand),

/// Write a new message
#[command(alias = "new", alias = "compose")]
Write(MessageWriteCommand),

/// Reply to a message
#[command()]
Reply(MessageReplyCommand),

/// Forward a message
#[command(alias = "fwd")]
Forward(MessageForwardCommand),

/// Parse and edit a message from a mailto URL string
#[command()]
Mailto(MessageMailtoCommand),

/// Save a message to a folder
#[command(arg_required_else_help = true)]
#[command(alias = "add", alias = "create")]
Expand All @@ -48,6 +70,10 @@ impl MessageSubcommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
match self {
Self::Read(cmd) => cmd.execute(printer, config).await,
Self::Write(cmd) => cmd.execute(printer, config).await,
Self::Reply(cmd) => cmd.execute(printer, config).await,
Self::Forward(cmd) => cmd.execute(printer, config).await,
Self::Mailto(cmd) => cmd.execute(printer, config).await,
Self::Save(cmd) => cmd.execute(printer, config).await,
Self::Send(cmd) => cmd.execute(printer, config).await,
Self::Copy(cmd) => cmd.execute(printer, config).await,
Expand Down
4 changes: 3 additions & 1 deletion src/email/message/command/move_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ impl MessageMoveCommand {
let ids = &self.envelopes.ids;
backend.move_messages(from_folder, to_folder, ids).await?;

printer.print("Message(s) successfully moved from {from_folder} to {to_folder}!")
printer.print(format!(
"Message(s) successfully moved from {from_folder} to {to_folder}!"
))
}
}
Loading

0 comments on commit b8ef771

Please sign in to comment.