mail/view: add pipe-attachment command
You can pipe individual attachments to binaries with the following command: pipe-attachment INDEX binary ARGS Example usage with the less(1) pager: pipe-attachment 0 less If the binary does not wait for your input before exiting, you will probably not see its output since you will return back to the user interface immediately. You can write a wrapper script that pipes your binary's output to less or less -r if you want to preserve the ANSI escape codes in the pager's output. Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
This commit is contained in:
parent
fcab855fda
commit
5e77821f78
4 changed files with 117 additions and 8 deletions
|
@ -214,14 +214,30 @@ See
|
|||
for the location of the mailcap files and
|
||||
.Xr mailcap 5
|
||||
for their syntax.
|
||||
You can save individual attachments with the
|
||||
.Command save-attachment Ar INDEX Ar path-to-file
|
||||
command.
|
||||
You can save individual attachments with the following command:
|
||||
.Command save\-attachment Ar INDEX Ar path\-to\-file
|
||||
.Ar INDEX
|
||||
is the attachment's index in the listing.
|
||||
If the path provided is a directory, the attachment is saved with its filename set to the filename in the attachment, if any.
|
||||
If the 0th index is provided, the entire message is saved.
|
||||
If the path provided is a directory, the message is saved as an eml file with its filename set to the messages message-id.
|
||||
.Bl -tag -compact -width 8n
|
||||
.It If the path provided is a directory, the attachment is saved with its filename set to the filename in the attachment, if any.
|
||||
.It If the 0th index is provided, the entire message is saved.
|
||||
.It If the path provided is a directory, the message is saved as an eml file with its filename set to the messages message\-id.
|
||||
.El
|
||||
.Pp
|
||||
You can pipe individual attachments to binaries with the following command:
|
||||
.Command pipe\-attachment Ar INDEX Ar binary Ar ARGS
|
||||
Example usage with the
|
||||
.Xr less 1
|
||||
pager:
|
||||
.D1 pipe\-attachment 0 less
|
||||
If the binary does not wait for your input before exiting, you will probably
|
||||
not see its output since you will return back to the user interface
|
||||
immediately.
|
||||
You can write a wrapper script that pipes your binary's output to
|
||||
.Dl less
|
||||
or
|
||||
.Dl less \-r
|
||||
if you want to preserve the ANSI escape codes in the pager's output.
|
||||
.Sh SEARCH
|
||||
Each e\-mail storage backend has a default search method assigned.
|
||||
.Em IMAP
|
||||
|
@ -582,7 +598,7 @@ open list archive with
|
|||
.Bl -tag -width 36n
|
||||
.It Cm mailto Ar MAILTO_ADDRESS
|
||||
Opens a composer tab with initial values parsed from the
|
||||
.Li mailto:
|
||||
.Li mailto :
|
||||
address.
|
||||
.It Cm add-attachment Ar PATH
|
||||
in composer, add
|
||||
|
@ -603,7 +619,7 @@ Launch command
|
|||
\&.
|
||||
The command should print file paths in stdout, separated by NUL bytes.
|
||||
Example usage with
|
||||
.Xr fzf Ns
|
||||
.Xr fzf 1 Ns
|
||||
:
|
||||
.D1 add-attachment-file-picker < fzf --print0
|
||||
.It Cm remove-attachment Ar INDEX
|
||||
|
|
|
@ -102,6 +102,7 @@ pub enum ViewAction {
|
|||
Pipe(String, Vec<String>),
|
||||
Filter(Option<String>),
|
||||
SaveAttachment(usize, String),
|
||||
PipeAttachment(usize, String, Vec<String>),
|
||||
ExportMail(String),
|
||||
AddAddressesToContacts,
|
||||
}
|
||||
|
|
|
@ -139,6 +139,7 @@ pub fn view(input: &[u8]) -> IResult<&[u8], Result<Action, CommandError>> {
|
|||
filter,
|
||||
pipe,
|
||||
save_attachment,
|
||||
pipe_attachment,
|
||||
export_mail,
|
||||
add_addresses_to_contacts,
|
||||
))(input)
|
||||
|
@ -895,6 +896,35 @@ pub fn save_attachment(input: &[u8]) -> IResult<&[u8], Result<Action, CommandErr
|
|||
let (input, _) = eof(input)?;
|
||||
Ok((input, Ok(View(SaveAttachment(idx, path.to_string())))))
|
||||
}
|
||||
pub fn pipe_attachment<'a>(input: &'a [u8]) -> IResult<&'a [u8], Result<Action, CommandError>> {
|
||||
let mut check = arg_init! { min_arg:2, max_arg:{u8::MAX}, pipe_attachment};
|
||||
let (input, _) = tag("pipe-attachment")(input.trim())?;
|
||||
arg_chk!(start check, input);
|
||||
let (input, _) = is_a(" ")(input)?;
|
||||
arg_chk!(inc check, input);
|
||||
let (input, idx) = map_res(quoted_argument, usize::from_str)(input)?;
|
||||
let (input, _) = is_a(" ")(input)?;
|
||||
arg_chk!(inc check, input);
|
||||
let (input, bin) = quoted_argument(input)?;
|
||||
arg_chk!(inc check, input);
|
||||
let (input, args) = alt((
|
||||
|input: &'a [u8]| -> IResult<&'a [u8], Vec<String>> {
|
||||
let (input, _) = is_a(" ")(input)?;
|
||||
let (input, args) = separated_list1(is_a(" "), quoted_argument)(input)?;
|
||||
let (input, _) = eof(input)?;
|
||||
Ok((
|
||||
input,
|
||||
args.into_iter().map(String::from).collect::<Vec<String>>(),
|
||||
))
|
||||
},
|
||||
|input: &'a [u8]| -> IResult<&'a [u8], Vec<String>> {
|
||||
let (input, _) = eof(input)?;
|
||||
Ok((input, Vec::with_capacity(0)))
|
||||
},
|
||||
))(input)?;
|
||||
arg_chk!(finish check, input);
|
||||
Ok((input, Ok(View(PipeAttachment(idx, bin.to_string(), args)))))
|
||||
}
|
||||
pub fn export_mail(input: &[u8]) -> IResult<&[u8], Result<Action, CommandError>> {
|
||||
let mut check = arg_init! { min_arg:1, max_arg: 1, export_mail};
|
||||
let (input, _) = tag("export-mail")(input.trim())?;
|
||||
|
|
|
@ -1521,6 +1521,68 @@ impl Component for EnvelopeView {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
UIEvent::Action(View(ViewAction::PipeAttachment(a_i, ref bin, ref args))) => {
|
||||
use std::borrow::Cow;
|
||||
|
||||
let bytes =
|
||||
if let Some(u) = self.open_attachment(a_i, context) {
|
||||
Cow::Owned(u.decode(Default::default()))
|
||||
} else if a_i == 0 {
|
||||
Cow::Borrowed(&self.mail.bytes)
|
||||
} else {
|
||||
context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!("Attachment `{}` not found.", a_i)),
|
||||
));
|
||||
return true;
|
||||
};
|
||||
// Kill input thread so that spawned command can be sole receiver of stdin
|
||||
{
|
||||
context.input_kill();
|
||||
}
|
||||
let pipe_command = format!("{} {}", bin, args.as_slice().join(" "));
|
||||
log::trace!("Executing: {}", &pipe_command);
|
||||
match Command::new(bin)
|
||||
.args(args)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()
|
||||
.map_err(Error::from)
|
||||
.and_then(|mut child| {
|
||||
let Some(mut stdin) = child.stdin.take() else {
|
||||
let _ = child.wait();
|
||||
return Err(Error::new(format!(
|
||||
"Could not open standard input of {bin}"
|
||||
))
|
||||
.set_kind(ErrorKind::External));
|
||||
};
|
||||
stdin.write_all(&bytes).chain_err_summary(|| {
|
||||
format!("Could not write to standard input of {bin}")
|
||||
})?;
|
||||
|
||||
Ok(child)
|
||||
}) {
|
||||
Ok(mut child) => {
|
||||
let _ = child.wait();
|
||||
}
|
||||
Err(err) => {
|
||||
context.replies.push_back(UIEvent::Notification {
|
||||
title: Some(
|
||||
format!("Failed to execute {}: {}", pipe_command, err).into(),
|
||||
),
|
||||
source: None,
|
||||
body: err.to_string().into(),
|
||||
kind: Some(NotificationType::Error(melib::error::ErrorKind::External)),
|
||||
});
|
||||
context.replies.push_back(UIEvent::Fork(ForkType::Finished));
|
||||
context.restore_input();
|
||||
self.set_dirty(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
context.replies.push_back(UIEvent::Fork(ForkType::Finished));
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Shortcuts::ENVELOPE_VIEW]["open_attachment"])
|
||||
&& !self.cmd_buf.is_empty() =>
|
||||
|
|
Loading…
Add table
Reference in a new issue