mirror of
https://github.com/soywod/himalaya.git
synced 2024-07-05 17:15:12 +00:00
start merging email with msg, add list msgs command
This commit is contained in:
parent
a803800d1c
commit
0a508f2e95
30
src/imap.rs
30
src/imap.rs
|
@ -4,7 +4,8 @@ use std::{fmt, net::TcpStream, result};
|
|||
|
||||
use crate::config;
|
||||
use crate::email::{self, Email};
|
||||
use crate::mailbox::Mailbox;
|
||||
use crate::mbox::Mbox;
|
||||
use crate::msg::Msg;
|
||||
|
||||
// Error wrapper
|
||||
|
||||
|
@ -79,17 +80,40 @@ impl<'a> ImapConnector<'a> {
|
|||
Ok(Self { config, sess })
|
||||
}
|
||||
|
||||
pub fn list_mboxes(&mut self) -> Result<Vec<Mailbox<'_>>> {
|
||||
pub fn close(&mut self) {
|
||||
match self.sess.close() {
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_mboxes(&mut self) -> Result<Vec<Mbox<'_>>> {
|
||||
let mboxes = self
|
||||
.sess
|
||||
.list(Some(""), Some("*"))?
|
||||
.iter()
|
||||
.map(Mailbox::from_name)
|
||||
.map(Mbox::from_name)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(mboxes)
|
||||
}
|
||||
|
||||
pub fn list_msgs(&mut self, mbox: &str, page_size: &u32, page: &u32) -> Result<Vec<Msg>> {
|
||||
let last_seq = self.sess.select(mbox)?.exists;
|
||||
let begin = last_seq - (page * page_size);
|
||||
let end = begin - (page_size - 1);
|
||||
let range = format!("{}:{}", begin, end);
|
||||
|
||||
let msgs = self
|
||||
.sess
|
||||
.fetch(range, "(UID BODY.PEEK[])")?
|
||||
.iter()
|
||||
.rev()
|
||||
.map(Msg::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(msgs)
|
||||
}
|
||||
|
||||
pub fn read_emails(&mut self, mbox: &str, query: &str) -> Result<Vec<Email<'_>>> {
|
||||
self.sess.select(mbox)?;
|
||||
|
||||
|
|
173
src/main.rs
173
src/main.rs
|
@ -2,7 +2,7 @@ mod config;
|
|||
mod email;
|
||||
mod imap;
|
||||
mod input;
|
||||
mod mailbox;
|
||||
mod mbox;
|
||||
mod msg;
|
||||
mod smtp;
|
||||
mod table;
|
||||
|
@ -15,6 +15,9 @@ use crate::imap::ImapConnector;
|
|||
use crate::msg::Msg;
|
||||
use crate::table::DisplayTable;
|
||||
|
||||
const DEFAULT_PAGE_SIZE: u32 = 10;
|
||||
const DEFAULT_PAGE: u32 = 0;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
ConfigError(config::Error),
|
||||
|
@ -89,14 +92,44 @@ fn uid_arg() -> Arg<'static, 'static> {
|
|||
}
|
||||
|
||||
fn run() -> Result<()> {
|
||||
let default_page_size = &DEFAULT_PAGE_SIZE.to_string();
|
||||
let default_page = &DEFAULT_PAGE.to_string();
|
||||
|
||||
let matches = App::new("Himalaya")
|
||||
.version("0.1.0")
|
||||
.about("📫 Minimalist CLI email client")
|
||||
.author("soywod <clement.douin@posteo.net>")
|
||||
.setting(AppSettings::ArgRequiredElseHelp)
|
||||
.subcommand(SubCommand::with_name("list").about("Lists all available mailboxes"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("mailboxes")
|
||||
.aliases(&["mboxes", "mb", "m"])
|
||||
.about("Lists all available mailboxes"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("list")
|
||||
.aliases(&["lst", "l"])
|
||||
.about("Lists emails sorted by arrival date")
|
||||
.arg(mailbox_arg())
|
||||
.arg(
|
||||
Arg::with_name("size")
|
||||
.help("Page size")
|
||||
.short("s")
|
||||
.long("size")
|
||||
.value_name("INT")
|
||||
.default_value(default_page_size),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("page")
|
||||
.help("Page number")
|
||||
.short("p")
|
||||
.long("page")
|
||||
.value_name("INT")
|
||||
.default_value(default_page),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("search")
|
||||
.aliases(&["query", "q", "s"])
|
||||
.about("Lists emails matching the given IMAP query")
|
||||
.arg(mailbox_arg())
|
||||
.arg(
|
||||
|
@ -109,6 +142,7 @@ fn run() -> Result<()> {
|
|||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("read")
|
||||
.aliases(&["r"])
|
||||
.about("Reads text bodies of an email")
|
||||
.arg(uid_arg())
|
||||
.arg(mailbox_arg())
|
||||
|
@ -124,6 +158,7 @@ fn run() -> Result<()> {
|
|||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("attachments")
|
||||
.aliases(&["attach", "a"])
|
||||
.about("Downloads all attachments from an email")
|
||||
.arg(uid_arg())
|
||||
.arg(mailbox_arg()),
|
||||
|
@ -131,6 +166,7 @@ fn run() -> Result<()> {
|
|||
.subcommand(SubCommand::with_name("write").about("Writes a new email"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("reply")
|
||||
.aliases(&["rep", "re"])
|
||||
.about("Answers to an email")
|
||||
.arg(uid_arg())
|
||||
.arg(mailbox_arg())
|
||||
|
@ -143,94 +179,127 @@ fn run() -> Result<()> {
|
|||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("forward")
|
||||
.aliases(&["fwd", "f"])
|
||||
.about("Forwards an email")
|
||||
.arg(uid_arg())
|
||||
.arg(mailbox_arg()),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if let Some(_) = matches.subcommand_matches("list") {
|
||||
if let Some(_) = matches.subcommand_matches("mailboxes") {
|
||||
let config = Config::new_from_file()?;
|
||||
let mboxes = ImapConnector::new(&config.imap)?.list_mboxes()?.to_table();
|
||||
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||
|
||||
println!("{}", mboxes);
|
||||
let mboxes = imap_conn.list_mboxes()?;
|
||||
println!("{}", mboxes.to_table());
|
||||
|
||||
imap_conn.close();
|
||||
}
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("list") {
|
||||
let config = Config::new_from_file()?;
|
||||
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||
|
||||
let mbox = matches.value_of("mailbox").unwrap();
|
||||
let page_size: u32 = matches
|
||||
.value_of("size")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap_or(DEFAULT_PAGE_SIZE);
|
||||
let page: u32 = matches
|
||||
.value_of("page")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap_or(DEFAULT_PAGE);
|
||||
|
||||
let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?;
|
||||
println!("{}", msgs.to_table());
|
||||
|
||||
imap_conn.close();
|
||||
}
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("search") {
|
||||
let config = Config::new_from_file()?;
|
||||
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||
|
||||
let mbox = matches.value_of("mailbox").unwrap();
|
||||
|
||||
if let Some(matches) = matches.values_of("query") {
|
||||
let query = matches
|
||||
.fold((false, vec![]), |(escape, mut cmds), cmd| {
|
||||
match (cmd, escape) {
|
||||
// Next command is an arg and needs to be escaped
|
||||
("subject", _) | ("body", _) | ("text", _) => {
|
||||
cmds.push(cmd.to_string());
|
||||
(true, cmds)
|
||||
}
|
||||
// Escaped arg commands
|
||||
(_, true) => {
|
||||
cmds.push(format!("\"{}\"", cmd));
|
||||
(false, cmds)
|
||||
}
|
||||
// Regular commands
|
||||
(_, false) => {
|
||||
cmds.push(cmd.to_string());
|
||||
(false, cmds)
|
||||
}
|
||||
let query = matches
|
||||
.values_of("query")
|
||||
.unwrap_or_default()
|
||||
.fold((false, vec![]), |(escape, mut cmds), cmd| {
|
||||
match (cmd, escape) {
|
||||
// Next command is an arg and needs to be escaped
|
||||
("subject", _) | ("body", _) | ("text", _) => {
|
||||
cmds.push(cmd.to_string());
|
||||
(true, cmds)
|
||||
}
|
||||
})
|
||||
.1
|
||||
.join(" ");
|
||||
// Escaped arg commands
|
||||
(_, true) => {
|
||||
cmds.push(format!("\"{}\"", cmd));
|
||||
(false, cmds)
|
||||
}
|
||||
// Regular commands
|
||||
(_, false) => {
|
||||
cmds.push(cmd.to_string());
|
||||
(false, cmds)
|
||||
}
|
||||
}
|
||||
})
|
||||
.1
|
||||
.join(" ");
|
||||
|
||||
let emails = ImapConnector::new(&config.imap)?
|
||||
.read_emails(&mbox, &query)?
|
||||
.to_table();
|
||||
let msgs = imap_conn.read_emails(&mbox, &query)?;
|
||||
println!("{}", msgs.to_table());
|
||||
|
||||
println!("{}", emails);
|
||||
}
|
||||
imap_conn.close();
|
||||
}
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("read") {
|
||||
let config = Config::new_from_file()?;
|
||||
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||
|
||||
let mbox = matches.value_of("mailbox").unwrap();
|
||||
let uid = matches.value_of("uid").unwrap();
|
||||
let mime = format!("text/{}", matches.value_of("mime-type").unwrap());
|
||||
let body = ImapConnector::new(&config.imap)?.read_email_body(&mbox, &uid, &mime)?;
|
||||
|
||||
let body = imap_conn.read_email_body(&mbox, &uid, &mime)?;
|
||||
println!("{}", body);
|
||||
|
||||
imap_conn.close();
|
||||
}
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("attachments") {
|
||||
let config = Config::new_from_file()?;
|
||||
let mbox = matches.value_of("mailbox").unwrap();
|
||||
let uid = matches.value_of("uid").unwrap();
|
||||
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||
|
||||
let msg = imap_conn.read_msg(&mbox, &uid)?;
|
||||
let msg = Msg::from(&msg)?;
|
||||
let mbox = matches.value_of("mailbox").unwrap();
|
||||
let uid = matches.value_of("uid").unwrap();
|
||||
|
||||
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?.as_slice());
|
||||
let parts = msg.extract_parts()?;
|
||||
|
||||
if parts.is_empty() {
|
||||
println!("No attachment found for message {}", uid);
|
||||
} else {
|
||||
println!("{} attachment(s) found for message {}", parts.len(), uid);
|
||||
msg.extract_parts()?.iter().for_each(|(filename, bytes)| {
|
||||
parts.iter().for_each(|(filename, bytes)| {
|
||||
let filepath = config.downloads_filepath(&filename);
|
||||
println!("Downloading {} …", filename);
|
||||
fs::write(filepath, bytes).unwrap()
|
||||
});
|
||||
println!("Done!");
|
||||
}
|
||||
|
||||
imap_conn.close();
|
||||
}
|
||||
|
||||
if let Some(_) = matches.subcommand_matches("write") {
|
||||
let config = Config::new_from_file()?;
|
||||
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||
|
||||
let tpl = Msg::build_new_tpl(&config)?;
|
||||
let content = input::open_editor_with_tpl(&tpl.as_bytes())?;
|
||||
let msg = Msg::from(content.as_bytes())?;
|
||||
let msg = Msg::from(content.as_bytes());
|
||||
|
||||
input::ask_for_confirmation("Send the message?")?;
|
||||
|
||||
|
@ -238,17 +307,18 @@ fn run() -> Result<()> {
|
|||
smtp::send(&config.smtp, &msg.to_sendable_msg()?)?;
|
||||
imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
||||
println!("Done!");
|
||||
|
||||
imap_conn.close();
|
||||
}
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("reply") {
|
||||
let config = Config::new_from_file()?;
|
||||
let mbox = matches.value_of("mailbox").unwrap();
|
||||
let uid = matches.value_of("uid").unwrap();
|
||||
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||
|
||||
let msg = imap_conn.read_msg(&mbox, &uid)?;
|
||||
let msg = Msg::from(&msg)?;
|
||||
let mbox = matches.value_of("mailbox").unwrap();
|
||||
let uid = matches.value_of("uid").unwrap();
|
||||
|
||||
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?.as_slice());
|
||||
let tpl = if matches.is_present("reply-all") {
|
||||
msg.build_reply_all_tpl(&config)?
|
||||
} else {
|
||||
|
@ -256,7 +326,7 @@ fn run() -> Result<()> {
|
|||
};
|
||||
|
||||
let content = input::open_editor_with_tpl(&tpl.as_bytes())?;
|
||||
let msg = Msg::from(content.as_bytes())?;
|
||||
let msg = Msg::from(content.as_bytes());
|
||||
|
||||
input::ask_for_confirmation("Send the message?")?;
|
||||
|
||||
|
@ -264,20 +334,21 @@ fn run() -> Result<()> {
|
|||
smtp::send(&config.smtp, &msg.to_sendable_msg()?)?;
|
||||
imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
||||
println!("Done!");
|
||||
|
||||
imap_conn.close();
|
||||
}
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("forward") {
|
||||
let config = Config::new_from_file()?;
|
||||
let mbox = matches.value_of("mailbox").unwrap();
|
||||
let uid = matches.value_of("uid").unwrap();
|
||||
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||
|
||||
let msg = imap_conn.read_msg(&mbox, &uid)?;
|
||||
let msg = Msg::from(&msg)?;
|
||||
let mbox = matches.value_of("mailbox").unwrap();
|
||||
let uid = matches.value_of("uid").unwrap();
|
||||
|
||||
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?.as_slice());
|
||||
let tpl = msg.build_forward_tpl(&config)?;
|
||||
let content = input::open_editor_with_tpl(&tpl.as_bytes())?;
|
||||
let msg = Msg::from(content.as_bytes())?;
|
||||
let msg = Msg::from(content.as_bytes());
|
||||
|
||||
input::ask_for_confirmation("Send the message?")?;
|
||||
|
||||
|
@ -285,6 +356,8 @@ fn run() -> Result<()> {
|
|||
smtp::send(&config.smtp, &msg.to_sendable_msg()?)?;
|
||||
imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
||||
println!("Done!");
|
||||
|
||||
imap_conn.close();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -83,13 +83,13 @@ impl DisplayCell for Attributes<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Mailbox<'a> {
|
||||
pub struct Mbox<'a> {
|
||||
pub delim: Delim,
|
||||
pub name: Name,
|
||||
pub attributes: Attributes<'a>,
|
||||
}
|
||||
|
||||
impl Mailbox<'_> {
|
||||
impl Mbox<'_> {
|
||||
pub fn from_name(name: &imap::types::Name) -> Self {
|
||||
Self {
|
||||
delim: Delim::from_name(name),
|
||||
|
@ -99,7 +99,7 @@ impl Mailbox<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> DisplayRow for Mailbox<'a> {
|
||||
impl<'a> DisplayRow for Mbox<'a> {
|
||||
fn to_row(&self) -> Vec<table::Cell> {
|
||||
vec![
|
||||
self.delim.to_cell(),
|
||||
|
@ -109,12 +109,12 @@ impl<'a> DisplayRow for Mailbox<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> DisplayTable<'a, Mailbox<'a>> for Vec<Mailbox<'a>> {
|
||||
impl<'a> DisplayTable<'a, Mbox<'a>> for Vec<Mbox<'a>> {
|
||||
fn cols() -> &'a [&'a str] {
|
||||
&["delim", "name", "attributes"]
|
||||
}
|
||||
|
||||
fn rows(&self) -> &Vec<Mailbox<'a>> {
|
||||
fn rows(&self) -> &Vec<Mbox<'a>> {
|
||||
self
|
||||
}
|
||||
}
|
104
src/msg.rs
104
src/msg.rs
|
@ -1,7 +1,8 @@
|
|||
use lettre;
|
||||
use mailparse::{self, MailHeaderMap};
|
||||
use std::{fmt, ops, result};
|
||||
use std::{fmt, result};
|
||||
|
||||
use crate::table::{self, DisplayRow, DisplayTable};
|
||||
use crate::Config;
|
||||
|
||||
// Error wrapper
|
||||
|
@ -9,8 +10,7 @@ use crate::Config;
|
|||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
ParseMsgError(mailparse::MailParseError),
|
||||
BuildEmailError(lettre::error::Error),
|
||||
TryError,
|
||||
BuildSendableMsgError(lettre::error::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
|
@ -18,8 +18,7 @@ impl fmt::Display for Error {
|
|||
write!(f, "(msg): ")?;
|
||||
match self {
|
||||
Error::ParseMsgError(err) => err.fmt(f),
|
||||
Error::BuildEmailError(err) => err.fmt(f),
|
||||
Error::TryError => write!(f, "cannot parse"),
|
||||
Error::BuildSendableMsgError(err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +31,7 @@ impl From<mailparse::MailParseError> for Error {
|
|||
|
||||
impl From<lettre::error::Error> for Error {
|
||||
fn from(err: lettre::error::Error) -> Error {
|
||||
Error::BuildEmailError(err)
|
||||
Error::BuildSendableMsgError(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,28 +39,45 @@ impl From<lettre::error::Error> for Error {
|
|||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Wrapper around mailparse::ParsedMail and lettre::Message
|
||||
// Msg
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Msg<'a>(mailparse::ParsedMail<'a>);
|
||||
pub struct Msg {
|
||||
pub uid: u32,
|
||||
pub flags: Vec<String>,
|
||||
raw: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<'a> ops::Deref for Msg<'a> {
|
||||
type Target = mailparse::ParsedMail<'a>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
impl From<&[u8]> for Msg {
|
||||
fn from(item: &[u8]) -> Self {
|
||||
Self {
|
||||
uid: 0,
|
||||
flags: vec![],
|
||||
raw: item.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Msg<'a> {
|
||||
pub fn from(bytes: &'a [u8]) -> Result<Self> {
|
||||
Ok(Self(mailparse::parse_mail(bytes)?))
|
||||
impl From<&imap::types::Fetch> for Msg {
|
||||
fn from(fetch: &imap::types::Fetch) -> Self {
|
||||
Self {
|
||||
uid: fetch.uid.unwrap_or_default(),
|
||||
flags: vec![],
|
||||
raw: fetch.body().unwrap_or_default().to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Msg {
|
||||
pub fn parse(&'a self) -> Result<mailparse::ParsedMail<'a>> {
|
||||
Ok(mailparse::parse_mail(&self.raw)?)
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Result<Vec<u8>> {
|
||||
let headers = self.0.get_headers().get_raw_bytes().to_vec();
|
||||
let parsed = self.parse()?;
|
||||
let headers = parsed.get_headers().get_raw_bytes().to_vec();
|
||||
let sep = "\r\n".as_bytes().to_vec();
|
||||
let body = self.0.get_body()?.as_bytes().to_vec();
|
||||
let body = parsed.get_body()?.as_bytes().to_vec();
|
||||
|
||||
Ok(vec![headers, sep, body].concat())
|
||||
}
|
||||
|
@ -70,8 +86,8 @@ impl<'a> Msg<'a> {
|
|||
use lettre::message::header::{ContentTransferEncoding, ContentType};
|
||||
use lettre::message::{Message, SinglePart};
|
||||
|
||||
let msg = self
|
||||
.0
|
||||
let parsed = self.parse()?;
|
||||
let msg = parsed
|
||||
.headers
|
||||
.iter()
|
||||
.fold(Message::builder(), |msg, h| {
|
||||
|
@ -111,7 +127,7 @@ impl<'a> Msg<'a> {
|
|||
SinglePart::builder()
|
||||
.header(ContentType("text/plain; charset=utf-8".parse().unwrap()))
|
||||
.header(ContentTransferEncoding::Base64)
|
||||
.body(self.0.get_body_raw()?),
|
||||
.body(parsed.get_body_raw()?),
|
||||
)?;
|
||||
|
||||
Ok(msg)
|
||||
|
@ -147,7 +163,7 @@ impl<'a> Msg<'a> {
|
|||
|
||||
pub fn extract_parts(&self) -> Result<Vec<(String, Vec<u8>)>> {
|
||||
let mut parts = vec![];
|
||||
Self::extract_parts_into(&self.0, &mut parts);
|
||||
Self::extract_parts_into(&self.parse()?, &mut parts);
|
||||
Ok(parts)
|
||||
}
|
||||
|
||||
|
@ -167,7 +183,7 @@ impl<'a> Msg<'a> {
|
|||
}
|
||||
|
||||
pub fn build_reply_tpl(&self, config: &Config) -> Result<String> {
|
||||
let msg = &self.0;
|
||||
let msg = &self.parse()?;
|
||||
let headers = msg.get_headers();
|
||||
let mut tpl = vec![];
|
||||
|
||||
|
@ -207,7 +223,7 @@ impl<'a> Msg<'a> {
|
|||
}
|
||||
|
||||
pub fn build_reply_all_tpl(&self, config: &Config) -> Result<String> {
|
||||
let msg = &self.0;
|
||||
let msg = &self.parse()?;
|
||||
let headers = msg.get_headers();
|
||||
let mut tpl = vec![];
|
||||
|
||||
|
@ -289,7 +305,7 @@ impl<'a> Msg<'a> {
|
|||
}
|
||||
|
||||
pub fn build_forward_tpl(&self, config: &Config) -> Result<String> {
|
||||
let msg = &self.0;
|
||||
let msg = &self.parse()?;
|
||||
let headers = msg.get_headers();
|
||||
let mut tpl = vec![];
|
||||
|
||||
|
@ -313,3 +329,41 @@ impl<'a> Msg<'a> {
|
|||
Ok(tpl.join("\r\n"))
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayRow for Msg {
|
||||
fn to_row(&self) -> Vec<table::Cell> {
|
||||
match self.parse() {
|
||||
Err(_) => vec![],
|
||||
Ok(parsed) => {
|
||||
let headers = parsed.get_headers();
|
||||
|
||||
let uid = &self.uid.to_string();
|
||||
let flags = String::new(); // TODO: render flags
|
||||
let sender = headers
|
||||
.get_first_value("reply-to")
|
||||
.or(headers.get_first_value("from"))
|
||||
.unwrap_or_default();
|
||||
let subject = headers.get_first_value("subject").unwrap_or_default();
|
||||
let date = headers.get_first_value("date").unwrap_or_default();
|
||||
|
||||
vec![
|
||||
table::Cell::new(&[table::RED], &uid),
|
||||
table::Cell::new(&[table::WHITE], &flags),
|
||||
table::Cell::new(&[table::BLUE], &sender),
|
||||
table::Cell::new(&[table::GREEN], &subject),
|
||||
table::Cell::new(&[table::YELLOW], &date),
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DisplayTable<'a, Msg> for Vec<Msg> {
|
||||
fn cols() -> &'a [&'a str] {
|
||||
&["uid", "flags", "sender", "subject", "date"]
|
||||
}
|
||||
|
||||
fn rows(&self) -> &Vec<Msg> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ pub trait DisplayRow {
|
|||
fn to_row(&self) -> Vec<Cell>;
|
||||
}
|
||||
|
||||
pub trait DisplayTable<'a, T: DisplayRow> {
|
||||
pub trait DisplayTable<'a, T: DisplayRow + 'a> {
|
||||
fn cols() -> &'a [&'a str];
|
||||
fn rows(&self) -> &Vec<T>;
|
||||
|
||||
|
|
Loading…
Reference in a new issue