add basic support of xdg-email (#162)

This commit is contained in:
Clément DOUIN 2021-08-04 21:42:59 +02:00
parent a70631de1c
commit 2acd5d71d3
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
5 changed files with 141 additions and 4 deletions

29
Cargo.lock generated
View file

@ -263,6 +263,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
dependencies = [
"matches",
"percent-encoding",
]
[[package]]
name = "funty"
version = "1.1.0"
@ -322,6 +332,7 @@ dependencies = [
"toml",
"tree_magic",
"unicode-width",
"url",
"uuid",
]
@ -674,6 +685,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "petgraph"
version = "0.5.1"
@ -1069,6 +1086,18 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "url"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
dependencies = [
"form_urlencoded",
"idna",
"matches",
"percent-encoding",
]
[[package]]
name = "uuid"
version = "0.8.2"

View file

@ -23,4 +23,5 @@ terminal_size = "0.1.15"
toml = "0.5.8"
tree_magic = "0.2.3"
unicode-width = "0.1.7"
url = "2.2.2"
uuid = {version = "0.8", features = ["v4"]}

View file

@ -1,17 +1,18 @@
use clap;
use clap::{self, ArgMatches};
use env_logger;
use error_chain::error_chain;
use log::{debug, error, trace};
use std::{env, path::PathBuf, process::exit};
use url::{self, Url};
use himalaya::{
ctx::Ctx,
comp::cli::{comp_matches, comp_subcmds},
config::{cli::config_args, model::Config},
ctx::Ctx,
flag::cli::{flag_matches, flag_subcmds},
imap::cli::{imap_matches, imap_subcmds},
mbox::cli::{mbox_matches, mbox_source_arg, mbox_subcmds},
msg::cli::{msg_matches, msg_subcmds},
msg::cli::{msg_matches, msg_matches_mailto, msg_subcmds},
output::{cli::output_args, model::Output},
};
@ -24,6 +25,9 @@ error_chain! {
MboxCli(himalaya::mbox::cli::Error, himalaya::mbox::cli::ErrorKind);
MsgCli(himalaya::msg::cli::Error, himalaya::msg::cli::ErrorKind);
}
foreign_links {
Url(url::ParseError);
}
}
fn parse_args<'a>() -> clap::App<'a, 'a> {
@ -47,6 +51,18 @@ fn run() -> Result<()> {
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "off"),
);
let raw_args: Vec<String> = env::args().collect();
if raw_args.len() > 1 && raw_args[1].starts_with("mailto:") {
let config = Config::new(None)?;
let account = config.find_account_by_name(None)?;
let output = Output::new("plain");
let mbox = "INBOX";
let arg_matches = ArgMatches::default();
let app = Ctx::new(&config, &account, &output, &mbox, &arg_matches);
let url = Url::parse(&raw_args[1])?;
return Ok(msg_matches_mailto(&app, &url)?);
}
let args = parse_args();
let arg_matches = args.get_matches();

View file

@ -7,6 +7,7 @@ use std::{
io::{self, BufRead},
ops::Deref,
};
use url::Url;
use crate::{
ctx::Ctx,
@ -594,3 +595,47 @@ fn msg_matches_save(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
imap_conn.logout();
Ok(true)
}
pub fn msg_matches_mailto(ctx: &Ctx, url: &Url) -> Result<()> {
debug!("mailto command matched");
let mut imap_conn = ImapConnector::new(&ctx.account)?;
let tpl = Tpl::mailto(&ctx, &url);
let content = input::open_editor_with_tpl(tpl.to_string().as_bytes())?;
let mut msg = Msg::from(content);
loop {
match input::post_edit_choice() {
Ok(choice) => match choice {
input::PostEditChoice::Send => {
debug!("sending message…");
let msg = msg.to_sendable_msg()?;
smtp::send(&ctx.account, &msg)?;
imap_conn.append_msg("Sent", &msg.formatted(), vec![Flag::Seen])?;
input::remove_draft()?;
ctx.output.print("Message successfully sent");
break;
}
input::PostEditChoice::Edit => {
let content = input::open_editor_with_draft()?;
msg = Msg::from(content);
}
input::PostEditChoice::LocalDraft => break,
input::PostEditChoice::RemoteDraft => {
debug!("saving to draft…");
imap_conn.append_msg("Drafts", &msg.to_vec()?, vec![Flag::Seen])?;
input::remove_draft()?;
ctx.output.print("Message successfully saved to Drafts");
break;
}
input::PostEditChoice::Discard => {
input::remove_draft()?;
break;
}
},
Err(err) => error!("{}", err),
}
}
imap_conn.logout();
Ok(())
}

View file

@ -1,7 +1,8 @@
use error_chain::error_chain;
use mailparse::{self, MailHeaderMap};
use serde::Serialize;
use std::{collections::HashMap, fmt};
use std::{borrow::Cow, collections::HashMap, fmt};
use url::Url;
use crate::{ctx::Ctx, msg::model::Msg};
@ -187,6 +188,51 @@ impl Tpl {
tpl
}
pub fn mailto(ctx: &Ctx, url: &Url) -> Self {
let mut headers = HashMap::new();
let mut cc = Vec::new();
let mut bcc = Vec::new();
let mut subject = Cow::default();
let mut body = Cow::default();
for (key, val) in url.query_pairs() {
match key.as_bytes() {
b"cc" => {
cc.push(val);
}
b"bcc" => {
bcc.push(val);
}
b"subject" => {
subject = val;
}
b"body" => {
body = val;
}
_ => (),
}
}
headers.insert(String::from("To"), url.path().to_string());
headers.insert(String::from("Subject"), subject.into());
if !cc.is_empty() {
headers.insert(String::from("Cc"), cc.join(", "));
}
if !bcc.is_empty() {
headers.insert(String::from("Bcc"), cc.join(", "));
}
let mut tpl = Self {
headers,
body: Some(body.into()),
signature: ctx.config.signature(&ctx.account),
raw: String::new(),
};
tpl.raw = tpl.to_string();
tpl
}
pub fn header<K: ToString, V: ToString>(&mut self, key: K, val: V) -> &Self {
self.headers.insert(key.to_string(), val.to_string());
self