split input into submodules

This commit is contained in:
Clément DOUIN 2021-09-18 00:29:49 +02:00
parent 8fbbb324da
commit 94e9711c58
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
14 changed files with 212 additions and 212 deletions

View file

@ -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

View file

@ -1,4 +1,4 @@
//! Modules related to the user's configuration.
//! Module related to the user's configuration.
pub mod arg;
pub mod entity;

View file

@ -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(())
}
}

View file

@ -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())),
},

View file

@ -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<ImapService: ImapServiceInterface, SmtpService: SmtpServiceIn
msg.edit_body()?;
loop {
match input::post_edit_choice() {
match choice::post_edit() {
Ok(choice) => match choice {
input::PostEditChoice::Send => {
PostEditChoice::Send => {
debug!("sending message…");
// prepare the msg to be send
@ -68,12 +69,12 @@ fn msg_interaction<ImapService: ImapServiceInterface, SmtpService: SmtpServiceIn
imap.append_msg(&mbox, msg)?;
// remove the draft, since we sent it
input::remove_draft()?;
msg::utils::remove_draft()?;
output.print("Message successfully sent")?;
break;
}
// edit the body of the msg
input::PostEditChoice::Edit => {
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<ImapService: ImapServiceInterface, SmtpService: SmtpServiceIn
));
}
}
input::PostEditChoice::LocalDraft => 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<ImapService: ImapServiceInterface, SmtpService: SmtpServiceIn
let mbox = Mbox::from("Drafts");
match imap.append_msg(&mbox, msg) {
Ok(_) => {
input::remove_draft()?;
msg::utils::remove_draft()?;
output.print("Message successfully saved to Drafts")?;
}
Err(err) => {
@ -103,8 +104,8 @@ fn msg_interaction<ImapService: ImapServiceInterface, SmtpService: SmtpServiceIn
};
break;
}
input::PostEditChoice::Discard => {
input::remove_draft()?;
PostEditChoice::Discard => {
msg::utils::remove_draft()?;
break;
}
},

View file

@ -38,3 +38,4 @@ pub mod body;
pub mod flag;
pub mod handler;
pub mod tpl;
pub mod utils;

15
src/domain/msg/utils.rs Normal file
View file

@ -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))
}

View file

@ -1,3 +1,3 @@
//! Modules related to SMTP.
//! Module related to SMTP.
pub mod service;

View file

@ -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<String> {
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<String> {
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<PreEditChoice> {
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<PostEditChoice> {
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")),
}
}

View file

@ -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;

View file

@ -1,4 +1,4 @@
//! Modules related to output formatting and printing.
//! Module related to output formatting and printing.
pub mod cli;
pub mod service;

92
src/ui/choice.rs Normal file
View file

@ -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<PreEditChoice> {
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<PostEditChoice> {
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"))
}
}
}

70
src/ui/editor.rs Normal file
View file

@ -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<String> {
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<String> {
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)
}

View file

@ -1,3 +1,5 @@
//! Modules related to User Interface.
//! Module related to User Interface.
pub mod choice;
pub mod editor;
pub mod table;