ui: make html filter optional in config

closes #92
embed
Manos Pitsidianakis 2019-04-06 00:30:06 +03:00
parent 92f42ad8fa
commit cfb7dcec64
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
6 changed files with 272 additions and 109 deletions

View File

@ -412,9 +412,9 @@ fn decode_rfc822(_raw: &[u8]) -> Attachment {
*/
}
type Filter = Box<Fn(&Attachment, &mut Vec<u8>) -> ()>;
type Filter<'a> = Box<FnMut(&'a Attachment, &mut Vec<u8>) -> () + 'a>;
fn decode_rec_helper(a: &Attachment, filter: &Option<Filter>) -> Vec<u8> {
fn decode_rec_helper<'a>(a: &'a Attachment, filter: &mut Option<Filter<'a>>) -> Vec<u8> {
let mut ret = match a.content_type {
ContentType::Unsupported { .. } => Vec::new(),
ContentType::Text { .. } => decode_helper(a, filter),
@ -449,11 +449,11 @@ fn decode_rec_helper(a: &Attachment, filter: &Option<Filter>) -> Vec<u8> {
ret
}
pub fn decode_rec(a: &Attachment, filter: Option<Filter>) -> Vec<u8> {
decode_rec_helper(a, &filter)
pub fn decode_rec<'a>(a: &'a Attachment, mut filter: Option<Filter<'a>>) -> Vec<u8> {
decode_rec_helper(a, &mut filter)
}
fn decode_helper(a: &Attachment, filter: &Option<Filter>) -> Vec<u8> {
fn decode_helper<'a>(a: &'a Attachment, filter: &mut Option<Filter<'a>>) -> Vec<u8> {
let charset = match a.content_type {
ContentType::Text { charset: c, .. } => c,
_ => Default::default(),
@ -488,6 +488,6 @@ fn decode_helper(a: &Attachment, filter: &Option<Filter>) -> Vec<u8> {
ret
}
pub fn decode(a: &Attachment, filter: Option<Filter>) -> Vec<u8> {
decode_helper(a, &filter)
pub fn decode<'a>(a: &'a Attachment, mut filter: Option<Filter<'a>>) -> Vec<u8> {
decode_helper(a, &mut filter)
}

View File

@ -116,31 +116,52 @@ impl MailView {
}
/// Returns the string to be displayed in the Viewer
fn attachment_to_text(&self, body: &Attachment) -> String {
fn attachment_to_text<'closure, 's: 'closure, 'context: 's>(
&'s self,
body: &'context Attachment,
context: &'context mut Context,
) -> String {
let finder = LinkFinder::new();
let body_text = String::from_utf8_lossy(&decode_rec(
&body,
Some(Box::new(|a: &Attachment, v: &mut Vec<u8>| {
body,
Some(Box::new(move |a: &'closure Attachment, v: &mut Vec<u8>| {
if a.content_type().is_text_html() {
use std::io::Write;
use std::process::{Command, Stdio};
let settings = context.accounts[self.coordinates.0].runtime_settings.conf();
if let Some(filter_invocation) = settings.html_filter() {
let parts = split_command!(filter_invocation);
let (cmd, args) = (parts[0], &parts[1..]);
let command_obj = Command::new(cmd)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn();
if command_obj.is_err() {
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::Notification(
Some(format!(
"Failed to start html filter process: {}",
filter_invocation,
)),
String::new(),
),
});
return;
}
let mut html_filter = Command::new("w3m")
.args(&["-I", "utf-8", "-T", "text/html"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed to start html filter process");
html_filter
.stdin
.as_mut()
.unwrap()
.write_all(&v)
.expect("Failed to write to w3m stdin");
*v = b"Text piped through `w3m`. Press `v` to open in web browser. \n\n"
.to_vec();
v.extend(html_filter.wait_with_output().unwrap().stdout);
let mut html_filter = command_obj.unwrap();
html_filter
.stdin
.as_mut()
.unwrap()
.write_all(&v)
.expect("Failed to write to stdin");
*v = format!("Text piped through `{}`. Press `v` to open in web browser. \n\n",
filter_invocation).into_bytes();
v.extend(html_filter.wait_with_output().unwrap().stdout);
}
}
})),
))
@ -326,35 +347,42 @@ impl Component for MailView {
};
if self.dirty {
let mailbox_idx = self.coordinates; // coordinates are mailbox idxs
let mailbox = &context.accounts[mailbox_idx.0][mailbox_idx.1]
.as_ref()
.unwrap();
let envelope: &Envelope = &mailbox.collection[&mailbox_idx.2];
let op = context.accounts[mailbox_idx.0]
.backend
.operation(envelope.hash(), mailbox.folder.hash());
let body = envelope.body(op);
let body = {
let mailbox_idx = self.coordinates; // coordinates are mailbox idxs
let mailbox = &context.accounts[mailbox_idx.0][mailbox_idx.1]
.as_ref()
.unwrap();
let envelope: &Envelope = &mailbox.collection[&mailbox_idx.2];
let op = context.accounts[mailbox_idx.0]
.backend
.operation(envelope.hash(), mailbox.folder.hash());
envelope.body(op)
};
match self.mode {
ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => {
self.pager = None;
self.subview = Some(Box::new(HtmlView::new(decode(
&body.attachments()[aidx],
None,
))));
let attachment = &body.attachments()[aidx];
self.subview = Some(Box::new(HtmlView::new(
decode(&attachment, None),
context,
self.coordinates.0,
)));
self.mode = ViewMode::Subview;
}
ViewMode::Normal if body.is_html() => {
self.subview = Some(Box::new(HtmlView::new(decode(&body, None))));
self.subview = Some(Box::new(HtmlView::new(
decode(&body, None),
context,
self.coordinates.0,
)));
self.pager = None;
self.mode = ViewMode::Subview;
}
ViewMode::Subview | ViewMode::ContactSelector(_) => {}
_ => {
let text = {
self.attachment_to_text(&body)
self.attachment_to_text(&body, context)
/*
let text = self.attachment_to_text(&body);
// URL indexes must be colored (ugh..)
MailView::plain_text_to_buf(&text, self.mode == ViewMode::Url)
*/
@ -530,8 +558,12 @@ impl Component for MailView {
self.mode = ViewMode::Subview;
match EnvelopeWrapper::new(u.bytes().to_vec()) {
Ok(wrapper) => {
self.subview =
Some(Box::new(EnvelopeView::new(wrapper, None, None)));
self.subview = Some(Box::new(EnvelopeView::new(
wrapper,
None,
None,
self.coordinates.0,
)));
}
Err(e) => {
context.replies.push_back(UIEvent {

View File

@ -53,6 +53,7 @@ pub struct EnvelopeView {
mode: ViewMode,
wrapper: EnvelopeWrapper,
account_pos: usize,
cmd_buf: String,
}
@ -68,6 +69,7 @@ impl EnvelopeView {
wrapper: EnvelopeWrapper,
pager: Option<Pager>,
subview: Option<Box<Component>>,
account_pos: usize,
) -> Self {
EnvelopeView {
pager,
@ -75,13 +77,17 @@ impl EnvelopeView {
dirty: true,
mode: ViewMode::Normal,
wrapper,
account_pos,
cmd_buf: String::with_capacity(4),
}
}
/// Returns the string to be displayed in the Viewer
fn attachment_to_text(&self, body: &Attachment) -> String {
fn attachment_to_text<'closure, 's: 'closure, 'context: 's>(
&'s self,
body: &'context Attachment,
context: &'context mut Context,
) -> String {
let finder = LinkFinder::new();
let body_text = String::from_utf8_lossy(&decode_rec(
&body,
@ -89,23 +95,40 @@ impl EnvelopeView {
if a.content_type().is_text_html() {
use std::io::Write;
use std::process::{Command, Stdio};
let settings = context.accounts[self.account_pos].runtime_settings.conf();
if let Some(filter_invocation) = settings.html_filter() {
let parts = split_command!(filter_invocation);
let (cmd, args) = (parts[0], &parts[1..]);
let command_obj = Command::new(cmd)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn();
if command_obj.is_err() {
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::Notification(
Some(format!(
"Failed to start html filter process: {}",
filter_invocation,
)),
String::new(),
),
});
return;
}
let mut html_filter = Command::new("w3m")
.args(&["-I", "utf-8", "-T", "text/html"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed to start html filter process");
html_filter
.stdin
.as_mut()
.unwrap()
.write_all(&v)
.expect("Failed to write to w3m stdin");
*v = b"Text piped through `w3m`. Press `v` to open in web browser. \n\n"
.to_vec();
v.extend(html_filter.wait_with_output().unwrap().stdout);
let mut html_filter = command_obj.unwrap();
html_filter
.stdin
.as_mut()
.unwrap()
.write_all(&v)
.expect("Failed to write to stdin");
*v = format!("Text piped through `{}`. Press `v` to open in web browser. \n\n",
filter_invocation).into_bytes();
v.extend(html_filter.wait_with_output().unwrap().stdout);
}
}
})),
))
@ -288,18 +311,24 @@ impl Component for EnvelopeView {
let body = self.wrapper.body_bytes(self.wrapper.buffer());
match self.mode {
ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => {
self.subview = Some(Box::new(HtmlView::new(decode(
&body.attachments()[aidx],
None,
))));
let attachment = &body.attachments()[aidx];
self.subview = Some(Box::new(HtmlView::new(
decode(&attachment, None),
context,
self.account_pos,
)));
}
ViewMode::Normal if body.is_html() => {
self.subview = Some(Box::new(HtmlView::new(decode(&body, None))));
self.subview = Some(Box::new(HtmlView::new(
decode(&body, None),
context,
self.account_pos,
)));
self.mode = ViewMode::Subview;
}
_ => {
let text = {
self.attachment_to_text(&body)
self.attachment_to_text(&body, context)
/*
let text = self.attachment_to_text(&body);
// URL indexes must be colored (ugh..)

View File

@ -30,27 +30,94 @@ pub struct HtmlView {
}
impl HtmlView {
pub fn new(bytes: Vec<u8>) -> Self {
let mut html_filter = Command::new("w3m")
.args(&["-I", "utf-8", "-T", "text/html"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed to start html filter process");
html_filter
.stdin
.as_mut()
.unwrap()
.write_all(&bytes)
.expect("Failed to write to w3m stdin");
let mut display_text =
String::from("Text piped through `w3m`. Press `v` to open in web browser. \n\n");
display_text.push_str(&String::from_utf8_lossy(
&html_filter.wait_with_output().unwrap().stdout,
));
pub fn new(bytes: Vec<u8>, context: &mut Context, account_pos: usize) -> Self {
let settings = context.accounts[account_pos].runtime_settings.conf();
if let Some(filter_invocation) = settings.html_filter() {
let parts = split_command!(filter_invocation);
let (cmd, args) = (parts[0], &parts[1..]);
let command_obj = Command::new(cmd)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn();
if command_obj.is_err() {
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::Notification(
Some(format!(
"Failed to start html filter process: {}",
filter_invocation
)),
String::new(),
),
});
let pager = Pager::from_string(
String::from_utf8_lossy(&bytes).to_string(),
None,
None,
None,
);
HtmlView { pager, bytes }
} else {
let mut html_filter = command_obj.unwrap();
html_filter
.stdin
.as_mut()
.unwrap()
.write_all(&bytes)
.expect("Failed to write to html filter stdin");
let mut display_text = format!(
"Text piped through `{}`. Press `v` to open in web browser. \n\n",
filter_invocation
);
display_text.push_str(&String::from_utf8_lossy(
&html_filter.wait_with_output().unwrap().stdout,
));
let pager = Pager::from_string(display_text, None, None, None);
HtmlView { pager, bytes }
let pager = Pager::from_string(display_text, None, None, None);
HtmlView { pager, bytes }
}
} else {
if let Ok(mut html_filter) = Command::new("w3m")
.args(&["-I", "utf-8", "-T", "text/html"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
{
html_filter
.stdin
.as_mut()
.unwrap()
.write_all(&bytes)
.expect("Failed to write to html filter stdin");
let mut display_text = String::from(
"Text piped through `w3m`. Press `v` to open in web browser. \n\n",
);
display_text.push_str(&String::from_utf8_lossy(
&html_filter.wait_with_output().unwrap().stdout,
));
let pager = Pager::from_string(display_text, None, None, None);
HtmlView { pager, bytes }
} else {
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::Notification(
Some(format!(
"Failed to find any application to use as html filter"
)),
String::new(),
),
});
let pager = Pager::from_string(
String::from_utf8_lossy(&bytes).to_string(),
None,
None,
None,
);
HtmlView { pager, bytes }
}
}
}
}

View File

@ -33,6 +33,7 @@ pub use self::accounts::Account;
use self::config::{Config, File, FileFormat};
pub use self::shortcuts::*;
use self::default_vals::*;
use self::notifications::NotificationsSettings;
use melib::conf::AccountSettings;
use melib::error::*;
@ -43,8 +44,11 @@ use std::collections::HashMap;
use std::env;
use std::path::PathBuf;
fn true_val() -> bool {
true
#[macro_export]
macro_rules! split_command {
($cmd:expr) => {{
$cmd.split_whitespace().collect::<Vec<&str>>()
}};
}
#[derive(Debug, Clone, Default, Deserialize)]
@ -68,9 +72,17 @@ pub struct FileAccount {
sent_folder: String,
draft_folder: String,
identity: String,
#[serde(default = "none")]
display_name: Option<String>,
#[serde(deserialize_with = "index_from_str")]
index: IndexStyle,
/// A command to pipe html output before displaying it in a pager
/// Default: None
#[serde(default = "none", deserialize_with = "non_empty_string")]
html_filter: Option<String>,
folders: Option<HashMap<String, FolderConf>>,
}
@ -108,6 +120,9 @@ impl FileAccount {
pub fn index(&self) -> IndexStyle {
self.index
}
pub fn html_filter(&self) -> Option<&str> {
self.html_filter.as_ref().map(|f| f.as_str())
}
}
#[derive(Debug, Clone, Default, Deserialize)]
@ -218,3 +233,41 @@ where
_ => Err(de::Error::custom("invalid `index` value")),
}
}
fn non_empty_string<'de, D>(deserializer: D) -> std::result::Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let s = <String>::deserialize(deserializer)?;
if s.is_empty() {
Ok(None)
} else {
Ok(Some(s))
}
}
/*
* Deserialize default functions
*/
mod default_vals {
pub(in conf) fn false_val() -> bool {
true
}
pub(in conf) fn true_val() -> bool {
true
}
pub(in conf) fn zero_val() -> usize {
0
}
pub(in conf) fn eighty_percent() -> usize {
80
}
pub(in conf) fn none() -> Option<String> {
None
}
}

View File

@ -19,25 +19,7 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
fn false_val() -> bool {
true
}
fn true_val() -> bool {
true
}
fn zero_val() -> usize {
0
}
fn eighty_percent() -> usize {
80
}
fn none() -> Option<String> {
None
}
use super::default_vals::*;
/// Settings for the pager function.
#[derive(Debug, Deserialize, Clone, Default)]
pub struct PagerSettings {