From 94e9711c58c4850836653275a70330e9d0352dcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Sat, 18 Sep 2021 00:29:49 +0200 Subject: [PATCH] split input into submodules --- src/config/entity.rs | 4 +- src/config/mod.rs | 2 +- src/domain/imap/service.rs | 10 +-- src/domain/msg/entity.rs | 35 +++----- src/domain/msg/handler.rs | 21 ++--- src/domain/msg/mod.rs | 1 + src/domain/msg/utils.rs | 15 ++++ src/domain/smtp/mod.rs | 2 +- src/input.rs | 162 ------------------------------------- src/lib.rs | 4 - src/output/mod.rs | 2 +- src/ui/choice.rs | 92 +++++++++++++++++++++ src/ui/editor.rs | 70 ++++++++++++++++ src/ui/mod.rs | 4 +- 14 files changed, 212 insertions(+), 212 deletions(-) create mode 100644 src/domain/msg/utils.rs delete mode 100644 src/input.rs create mode 100644 src/ui/choice.rs create mode 100644 src/ui/editor.rs diff --git a/src/config/entity.rs b/src/config/entity.rs index 2d4341c..1ba7d74 100644 --- a/src/config/entity.rs +++ b/src/config/entity.rs @@ -90,7 +90,7 @@ impl Config { Some(name) => self .accounts .get(name) - .ok_or_else(|| anyhow!(format!("cannot find account `{}`", name))), + .ok_or_else(|| anyhow!("cannot find account `{}`", name)), } } @@ -513,7 +513,7 @@ impl<'a> TryFrom<(&'a Config, Option<&str>)> for Account { .accounts .get(name) .map(|account| (name.to_owned(), account)) - .ok_or_else(|| anyhow!(format!("cannot find account `{}`", name))), + .ok_or_else(|| anyhow!("cannot find account `{}`", name)), }?; let downloads_dir = account diff --git a/src/config/mod.rs b/src/config/mod.rs index 887e02c..313f7c7 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,4 +1,4 @@ -//! Modules related to the user's configuration. +//! Module related to the user's configuration. pub mod arg; pub mod entity; diff --git a/src/domain/imap/service.rs b/src/domain/imap/service.rs index 3cd2dc2..56234a9 100644 --- a/src/domain/imap/service.rs +++ b/src/domain/imap/service.rs @@ -339,7 +339,7 @@ impl<'a> ImapServiceInterface for ImapService<'a> { for fetch in fetches.iter() { let msg = Msg::try_from(fetch)?; let uid = fetch.uid.ok_or_else(|| { - anyhow!(format!("cannot retrieve message {}'s UID", fetch.message)) + anyhow!("cannot retrieve message {}'s UID", fetch.message) })?; let subject = msg.headers.subject.clone().unwrap_or_default(); @@ -386,10 +386,10 @@ impl<'a> ImapServiceInterface for ImapService<'a> { } fn logout(&mut self) -> Result<()> { - debug!("logout from IMAP server"); - self.sess()? - .logout() - .context("cannot logout from IMAP server")?; + if let Some(ref mut sess) = self.sess { + debug!("logout from IMAP server"); + sess.logout().context("cannot logout from IMAP server")?; + } Ok(()) } } diff --git a/src/domain/msg/entity.rs b/src/domain/msg/entity.rs index 7d29b2d..8613454 100644 --- a/src/domain/msg/entity.rs +++ b/src/domain/msg/entity.rs @@ -11,7 +11,7 @@ use crate::{ }; #[cfg(not(test))] -use crate::input; +use crate::ui::editor; use serde::Serialize; @@ -435,7 +435,7 @@ impl Msg { // tests, because we just need to look, if the headers are set // correctly #[cfg(not(test))] - let msg = input::open_editor_with_tpl(msg.as_bytes())?; + let msg = editor::open_editor_with_tpl(msg.as_bytes())?; // refresh the state of the msg self.parse_from_str(&msg)?; @@ -481,7 +481,7 @@ impl Msg { /// ``` pub fn parse_from_str(&mut self, content: &str) -> Result<()> { let parsed = mailparse::parse_mail(content.as_bytes()) - .with_context(|| format!("How the message looks like currently:\n{}", self))?; + .context(format!("How the message looks like currently:\n{}", self))?; self.headers = Headers::from(&parsed); @@ -577,10 +577,7 @@ impl Msg { // add "from" for mailaddress in &self.headers.from { msg = msg.from( - match mailaddress - .parse() - .with_context(|| "cannot parse `From` header") - { + match mailaddress.parse().context("cannot parse `From` header") { Ok(from) => from, Err(err) => return Err(anyhow!(err.to_string())), }, @@ -590,10 +587,7 @@ impl Msg { // add "to" for mailaddress in &self.headers.to { msg = msg.to( - match mailaddress - .parse() - .with_context(|| "cannot parse `To` header") - { + match mailaddress.parse().context("cannot parse `To` header") { Ok(from) => from, Err(err) => return Err(anyhow!(err.to_string())), }, @@ -605,10 +599,7 @@ impl Msg { if let Some(bcc) = &self.headers.bcc { for mailaddress in bcc { msg = msg.bcc( - match mailaddress - .parse() - .with_context(|| "cannot parse `Bcc` header") - { + match mailaddress.parse().context("cannot parse `Bcc` header") { Ok(from) => from, Err(err) => return Err(anyhow!(err.to_string())), }, @@ -620,10 +611,7 @@ impl Msg { if let Some(cc) = &self.headers.cc { for mailaddress in cc { msg = msg.cc( - match mailaddress - .parse() - .with_context(|| "cannot parse `Cc` header") - { + match mailaddress.parse().context("cannot parse `Cc` header") { Ok(from) => from, Err(err) => return Err(anyhow!(err.to_string())), }, @@ -636,7 +624,7 @@ impl Msg { msg = msg.in_reply_to( match in_reply_to .parse() - .with_context(|| "cannot parse `In-Reply-To` header") + .context("cannot parse `In-Reply-To` header") { Ok(from) => from, Err(err) => return Err(anyhow!(err.to_string())), @@ -665,7 +653,7 @@ impl Msg { msg = msg.reply_to( match mailaddress .parse() - .with_context(|| "cannot parse `Reply-To` header") + .context("cannot parse `Reply-To` header") { Ok(from) => from, Err(err) => return Err(anyhow!(err.to_string())), @@ -677,10 +665,7 @@ impl Msg { // add "sender" if let Some(sender) = &self.headers.sender { msg = msg.sender( - match sender - .parse() - .with_context(|| "cannot parse `Sender` header") - { + match sender.parse().context("cannot parse `Sender` header") { Ok(from) => from, Err(err) => return Err(anyhow!(err.to_string())), }, diff --git a/src/domain/msg/handler.rs b/src/domain/msg/handler.rs index 5416773..9a71ec8 100644 --- a/src/domain/msg/handler.rs +++ b/src/domain/msg/handler.rs @@ -17,13 +17,14 @@ use crate::{ imap::service::ImapServiceInterface, mbox::entity::Mbox, msg::{ + self, body::Body, entity::{Msg, Msgs}, }, smtp::service::SmtpServiceInterface, }, - input, output::service::{OutputService, OutputServiceInterface}, + ui::choice::{self, PostEditChoice}, }; use super::{entity::MsgSerialized, flag::entity::Flags, headers::Headers}; @@ -39,9 +40,9 @@ fn msg_interaction match choice { - input::PostEditChoice::Send => { + PostEditChoice::Send => { debug!("sending message…"); // prepare the msg to be send @@ -68,12 +69,12 @@ fn msg_interaction { + PostEditChoice::Edit => { // Did something goes wrong when the user changed the // content? if let Err(err) = msg.edit_body() { @@ -84,8 +85,8 @@ fn msg_interaction break, - input::PostEditChoice::RemoteDraft => { + PostEditChoice::LocalDraft => break, + PostEditChoice::RemoteDraft => { debug!("saving to draft…"); msg.flags.insert(Flag::Seen); @@ -93,7 +94,7 @@ fn msg_interaction { - input::remove_draft()?; + msg::utils::remove_draft()?; output.print("Message successfully saved to Drafts")?; } Err(err) => { @@ -103,8 +104,8 @@ fn msg_interaction { - input::remove_draft()?; + PostEditChoice::Discard => { + msg::utils::remove_draft()?; break; } }, diff --git a/src/domain/msg/mod.rs b/src/domain/msg/mod.rs index a71efd9..57ef7f7 100644 --- a/src/domain/msg/mod.rs +++ b/src/domain/msg/mod.rs @@ -38,3 +38,4 @@ pub mod body; pub mod flag; pub mod handler; pub mod tpl; +pub mod utils; diff --git a/src/domain/msg/utils.rs b/src/domain/msg/utils.rs new file mode 100644 index 0000000..67f9ca9 --- /dev/null +++ b/src/domain/msg/utils.rs @@ -0,0 +1,15 @@ +use anyhow::{Context, Result}; +use log::debug; +use std::{env, fs, path::PathBuf}; + +pub fn draft_path() -> PathBuf { + let path = env::temp_dir().join("himalaya-draft.mail"); + debug!("draft path: `{:?}`", path); + path +} + +pub fn remove_draft() -> Result<()> { + let path = draft_path(); + debug!("remove draft path: `{:?}`", path); + fs::remove_file(&path).context(format!("cannot delete draft file at `{:?}`", path)) +} diff --git a/src/domain/smtp/mod.rs b/src/domain/smtp/mod.rs index 81d6738..fed0ac5 100644 --- a/src/domain/smtp/mod.rs +++ b/src/domain/smtp/mod.rs @@ -1,3 +1,3 @@ -//! Modules related to SMTP. +//! Module related to SMTP. pub mod service; diff --git a/src/input.rs b/src/input.rs deleted file mode 100644 index 502f0d4..0000000 --- a/src/input.rs +++ /dev/null @@ -1,162 +0,0 @@ -use anyhow::{anyhow, Context, Result}; -use log::{debug, error, trace}; -use std::{ - env, - fs::{self, File}, - io::{self, Read, Write}, - path::PathBuf, - process::Command, -}; - -fn draft_path() -> PathBuf { - env::temp_dir().join("himalaya-draft.mail") -} - -pub fn remove_draft() -> Result<()> { - debug!("[input] remove draft"); - - let draft_path = draft_path(); - debug!("[input] draft path: {:?}", draft_path); - - fs::remove_file(&draft_path) - .with_context(|| format!("cannot delete draft file {:?}", draft_path)) -} - -pub fn open_editor_with_tpl(tpl: &[u8]) -> Result { - debug!("[input] open editor with tpl"); - trace!("{}", String::from_utf8(tpl.to_vec())?); - - let draft_path = draft_path(); - debug!("[input] draft path: {:?}", draft_path); - - if draft_path.exists() { - debug!("[input] draft found"); - loop { - match pre_edit_choice() { - Ok(choice) => match choice { - PreEditChoice::Edit => return open_editor_with_draft(), - PreEditChoice::Discard => break, - PreEditChoice::Quit => return Err(anyhow!("Edition aborted")), - }, - Err(err) => error!("{}", err), - } - } - } - - debug!("[Input] create draft"); - File::create(&draft_path) - .with_context(|| format!("cannot create draft file {:?}", draft_path))? - .write(tpl) - .with_context(|| format!("cannot write draft file {:?}", draft_path))?; - - debug!("[Input] open editor"); - Command::new(env::var("EDITOR").with_context(|| "cannot find `EDITOR` env var")?) - .arg(&draft_path) - .status() - .with_context(|| "cannot launch editor")?; - - debug!("[Input] read draft"); - let mut draft = String::new(); - File::open(&draft_path) - .with_context(|| format!("cannot open draft file {:?}", draft_path))? - .read_to_string(&mut draft) - .with_context(|| format!("cannot read draft file {:?}", draft_path))?; - - Ok(draft) -} - -pub fn open_editor_with_draft() -> Result { - debug!("[input] open editor with draft"); - - let draft_path = draft_path(); - debug!("[input] draft path: {:?}", draft_path); - - // Opens editor and saves user input to draft file - Command::new(env::var("EDITOR").with_context(|| "cannot find `EDITOR` env var")?) - .arg(&draft_path) - .status() - .with_context(|| "cannot launch editor")?; - - // Extracts draft file content - let mut draft = String::new(); - File::open(&draft_path) - .with_context(|| format!("cannot open file {:?}", draft_path))? - .read_to_string(&mut draft) - .with_context(|| format!("cannot read file {:?}", draft_path))?; - - Ok(draft) -} - -pub enum PreEditChoice { - Edit, - Discard, - Quit, -} - -pub fn pre_edit_choice() -> Result { - debug!("[input] pre edit choice"); - - println!("A draft was found:"); - print!("(e)dit, (d)iscard or (q)uit? "); - io::stdout() - .flush() - .with_context(|| "cannot flush stdout")?; - - let mut buf = String::new(); - io::stdin() - .read_line(&mut buf) - .with_context(|| "cannot read stdin")?; - - match buf.bytes().next().map(|bytes| bytes as char) { - Some('e') => { - debug!("[input] pre edit choice: edit matched"); - Ok(PreEditChoice::Edit) - } - Some('d') => { - debug!("[input] pre edit choice: discard matched"); - Ok(PreEditChoice::Discard) - } - Some('q') => { - debug!("[input] pre edit choice: quit matched"); - Ok(PreEditChoice::Quit) - } - Some(choice) => { - debug!("[input] pre edit choice: invalid choice {}", choice); - Err(anyhow!(format!("Invalid choice `{}`", choice))) - } - None => { - debug!("[input] pre edit choice: empty choice"); - Err(anyhow!("Empty choice")) - } - } -} - -pub enum PostEditChoice { - Send, - Edit, - LocalDraft, - RemoteDraft, - Discard, -} - -pub fn post_edit_choice() -> Result { - print!("(s)end, (e)dit, (l)ocal/(r)emote draft or (d)iscard? "); - io::stdout() - .flush() - .with_context(|| "cannot flush stdout")?; - - let mut buf = String::new(); - io::stdin() - .read_line(&mut buf) - .with_context(|| "cannot read stdin")?; - - match buf.bytes().next().map(|bytes| bytes as char) { - Some('s') => Ok(PostEditChoice::Send), - Some('l') => Ok(PostEditChoice::LocalDraft), - Some('r') => Ok(PostEditChoice::RemoteDraft), - Some('e') => Ok(PostEditChoice::Edit), - Some('d') => Ok(PostEditChoice::Discard), - Some(choice) => Err(anyhow!(format!("Invalid choice `{}`", choice))), - None => Err(anyhow!("Empty choice")), - } -} diff --git a/src/lib.rs b/src/lib.rs index cdfb644..d1965e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,10 +17,6 @@ pub mod compl; /// Everything which is related to the config files. For example the structure of your config file. pub mod config; -/// Handles the input-interaction with the user. For example if you want to edit the body of your -/// message, his module takes care of the draft and calls your ~(neo)vim~ your favourite editor. -pub mod input; - /// Handles the output. For example the JSON and HTML output. pub mod output; diff --git a/src/output/mod.rs b/src/output/mod.rs index b0118e0..70f7c0c 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -1,4 +1,4 @@ -//! Modules related to output formatting and printing. +//! Module related to output formatting and printing. pub mod cli; pub mod service; diff --git a/src/ui/choice.rs b/src/ui/choice.rs new file mode 100644 index 0000000..78a0a93 --- /dev/null +++ b/src/ui/choice.rs @@ -0,0 +1,92 @@ +use anyhow::{anyhow, Context, Result}; +use log::debug; +use std::io::{self, Write}; + +pub enum PreEditChoice { + Edit, + Discard, + Quit, +} + +pub fn pre_edit() -> Result { + println!("A draft was found:"); + print!("(e)dit, (d)iscard or (q)uit? "); + io::stdout().flush().context("cannot flush stdout")?; + + let mut buf = String::new(); + io::stdin() + .read_line(&mut buf) + .context("cannot read stdin")?; + + match buf.bytes().next().map(|bytes| bytes as char) { + Some('e') => { + debug!("edit choice matched"); + Ok(PreEditChoice::Edit) + } + Some('d') => { + debug!("discard choice matched"); + Ok(PreEditChoice::Discard) + } + Some('q') => { + debug!("quit choice matched"); + Ok(PreEditChoice::Quit) + } + Some(choice) => { + debug!("invalid choice `{}`", choice); + Err(anyhow!("invalid choice `{}`", choice)) + } + None => { + debug!("empty choice"); + Err(anyhow!("empty choice")) + } + } +} + +pub enum PostEditChoice { + Send, + Edit, + LocalDraft, + RemoteDraft, + Discard, +} + +pub fn post_edit() -> Result { + print!("(s)end, (e)dit, (l)ocal/(r)emote draft or (d)iscard? "); + io::stdout().flush().context("cannot flush stdout")?; + + let mut buf = String::new(); + io::stdin() + .read_line(&mut buf) + .context("cannot read stdin")?; + + match buf.bytes().next().map(|bytes| bytes as char) { + Some('s') => { + debug!("send choice matched"); + Ok(PostEditChoice::Send) + } + Some('l') => { + debug!("save local draft choice matched"); + Ok(PostEditChoice::LocalDraft) + } + Some('r') => { + debug!("save remote draft matched"); + Ok(PostEditChoice::RemoteDraft) + } + Some('e') => { + debug!("edit choice matched"); + Ok(PostEditChoice::Edit) + } + Some('d') => { + debug!("discard choice matched"); + Ok(PostEditChoice::Discard) + } + Some(choice) => { + debug!("invalid choice `{}`", choice); + Err(anyhow!("invalid choice `{}`", choice)) + } + None => { + debug!("empty choice"); + Err(anyhow!("empty choice")) + } + } +} diff --git a/src/ui/editor.rs b/src/ui/editor.rs new file mode 100644 index 0000000..aa35288 --- /dev/null +++ b/src/ui/editor.rs @@ -0,0 +1,70 @@ +use anyhow::{anyhow, Context, Result}; +use log::{debug, error}; +use std::{ + env, + fs::File, + io::{Read, Write}, + process::Command, +}; + +use crate::{ + domain::msg, + ui::choice::{self, PreEditChoice}, +}; + +pub fn open_editor_with_tpl(tpl: &[u8]) -> Result { + let path = msg::utils::draft_path(); + if path.exists() { + debug!("draft found"); + loop { + match choice::pre_edit() { + Ok(choice) => match choice { + PreEditChoice::Edit => return open_editor_with_draft(), + PreEditChoice::Discard => break, + PreEditChoice::Quit => return Err(anyhow!("edition aborted")), + }, + Err(err) => error!("{}", err), + } + } + } + + debug!("create draft"); + File::create(&path) + .context(format!("cannot create draft file `{:?}`", path))? + .write(tpl) + .context(format!("cannot write draft file `{:?}`", path))?; + + debug!("open editor"); + Command::new(env::var("EDITOR").context("cannot find `$EDITOR` env var")?) + .arg(&path) + .status() + .context("cannot launch editor")?; + + debug!("read draft"); + let mut draft = String::new(); + File::open(&path) + .context(format!("cannot open draft file `{:?}`", path))? + .read_to_string(&mut draft) + .context(format!("cannot read draft file `{:?}`", path))?; + + Ok(draft) +} + +pub fn open_editor_with_draft() -> Result { + let path = msg::utils::draft_path(); + + // Opens editor and saves user input to draft file + Command::new(env::var("EDITOR").context("cannot find `EDITOR` env var")?) + .arg(&path) + .status() + .context("cannot launch editor")?; + + // Extracts draft file content + let mut draft = String::new(); + File::open(&path) + .context(format!("cannot open file `{:?}`", path))? + .read_to_string(&mut draft) + .context(format!("cannot read file `{:?}`", path))?; + + Ok(draft) +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 020ecce..2174ede 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,3 +1,5 @@ -//! Modules related to User Interface. +//! Module related to User Interface. +pub mod choice; +pub mod editor; pub mod table;