aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVitaly Ovchinnikov <v@postbox.nz>2023-08-04 23:33:07 +0300
committerRobin Jarry <robin@jarry.cc>2023-08-05 00:04:35 +0200
commit1144611a1626d8f5d7ffe02d76ea58a97a67aff4 (patch)
tree70fea6988636554d801569c11d779da34e2aa67e
parentc801f1582cf6d5d3e367c5e2931381559746cccf (diff)
downloadaerc-1144611a1626d8f5d7ffe02d76ea58a97a67aff4.tar.gz
aerc-1144611a1626d8f5d7ffe02d76ea58a97a67aff4.zip
attach: add an option to pipe the attachments in
Add the -r option to :attach so that the attachments can be piped in from a command. Example: :attach -r image.jpg read-jpeg-from-clipboard.sh It takes two parameters: the attachment name (to be used in the email and to get the MIME type from) and the command to execute and read the output. Signed-off-by: Vitaly Ovchinnikov <v@postbox.nz> Acked-by: Robin Jarry <robin@jarry.cc> Reviewed-by: Koni Marti <koni.marti@gmail.com>
-rw-r--r--CHANGELOG.md1
-rw-r--r--commands/compose/attach.go78
-rw-r--r--doc/aerc.1.scd5
-rw-r--r--lib/attachment.go40
4 files changed, 100 insertions, 24 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3c7c3764..8e84982e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
in `aerc.conf` or with the `-e` flag for all compose related commands (e.g.
`:compose`, `:forward`, `:recall`, etc.).
- Remove headers from the compose window with `:header -d <name>`.
+- Add option `-r` to `:attach` to pipe the attachments in.
### Fixed
diff --git a/commands/compose/attach.go b/commands/compose/attach.go
index cf11af44..2eb8a98d 100644
--- a/commands/compose/attach.go
+++ b/commands/compose/attach.go
@@ -2,7 +2,7 @@ package compose
import (
"bufio"
- "errors"
+ "bytes"
"fmt"
"io"
"os"
@@ -12,10 +12,14 @@ import (
"git.sr.ht/~rjarry/aerc/commands"
"git.sr.ht/~rjarry/aerc/config"
+ "git.sr.ht/~rjarry/aerc/lib"
"git.sr.ht/~rjarry/aerc/lib/ui"
"git.sr.ht/~rjarry/aerc/log"
"git.sr.ht/~rjarry/aerc/widgets"
"github.com/mitchellh/go-homedir"
+ "github.com/pkg/errors"
+
+ "git.sr.ht/~sircmpwn/getopt"
)
type Attach struct{}
@@ -34,15 +38,48 @@ func (Attach) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (a Attach) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) == 1 {
- return fmt.Errorf("Usage: :attach <path>")
+ var (
+ menu bool
+ read bool
+ )
+
+ opts, optind, err := getopt.Getopts(args, "mr")
+ if err != nil {
+ return err
}
- if args[1] == "-m" {
- return a.openMenu(aerc, args[2:])
+ for _, opt := range opts {
+ switch opt.Option {
+ case 'm':
+ if read {
+ return errors.New("-m and -r are mutually exclusive")
+ }
+ menu = true
+ case 'r':
+ if menu {
+ return errors.New("-m and -r are mutually exclusive")
+ }
+ read = true
+ }
}
- return a.addPath(aerc, strings.Join(args[1:], " "))
+ args = args[optind:]
+
+ if menu {
+ return a.openMenu(aerc, args)
+ }
+
+ if read {
+ if len(args) < 2 {
+ return fmt.Errorf("Usage: :attach -r <name> <cmd> [args...]")
+ }
+ return a.readCommand(aerc, args[0], args[1:])
+ }
+
+ if len(args) == 0 {
+ return fmt.Errorf("Usage: :attach <path>")
+ }
+ return a.addPath(aerc, strings.Join(args, " "))
}
func (a Attach) addPath(aerc *widgets.Aerc, path string) error {
@@ -177,3 +214,32 @@ func (a Attach) openMenu(aerc *widgets.Aerc, args []string) error {
return nil
}
+
+func (a Attach) readCommand(aerc *widgets.Aerc, name string, args []string) error {
+ args = append([]string{"-c"}, args...)
+ cmd := exec.Command("sh", args...)
+
+ data, err := cmd.Output()
+ if err != nil {
+ return errors.Wrap(err, "Output")
+ }
+
+ reader := bufio.NewReader(bytes.NewReader(data))
+
+ mimeType, mimeParams, err := lib.FindMimeType(name, reader)
+ if err != nil {
+ return errors.Wrap(err, "FindMimeType")
+ }
+
+ mimeParams["name"] = name
+
+ composer, _ := aerc.SelectedTabContent().(*widgets.Composer)
+ err = composer.AddPartAttachment(name, mimeType, mimeParams, reader)
+ if err != nil {
+ return errors.Wrap(err, "AddPartAttachment")
+ }
+
+ aerc.PushSuccess(fmt.Sprintf("Attached %s", name))
+
+ return nil
+}
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index 5b1d36f3..36b2f6bd 100644
--- a/doc/aerc.1.scd
+++ b/doc/aerc.1.scd
@@ -571,6 +571,7 @@ message list, the message in the message viewer, etc).
*:attach* _<path>_++
*:attach* *-m* [_<arg>_]
+*:attach* *-r* <name> <cmd>
Attaches the file at the given path to the email. The path can contain
globbing syntax described at https://godocs.io/path/filepath#Match.
@@ -578,6 +579,10 @@ message list, the message in the message viewer, etc).
Runs the *file-picker-cmd* to select files to be attached.
Requires an argument when *file-picker-cmd* contains the _%s_ verb.
+ *-r* <name> <cmd>
+ Runs the <cmd>, reads its output and attaches it as <name>. The
+ attachment MIME type is derived from the <name>'s extension.
+
*:attach-key*
Attaches the public key for the configured account to the email.
diff --git a/lib/attachment.go b/lib/attachment.go
index f6118780..a8a8103d 100644
--- a/lib/attachment.go
+++ b/lib/attachment.go
@@ -66,24 +66,7 @@ func (fa *FileAttachment) WriteTo(w *mail.Writer) error {
reader := bufio.NewReader(f)
- // if we have an extension, prefer that instead of trying to sniff the header.
- // That's generally more accurate than sniffing as lots of things are zip files
- // under the hood, e.g. most office file types
- ext := filepath.Ext(fa.path)
- var mimeString string
- if mimeString = mime.TypeByExtension(ext); mimeString == "" {
- // Sniff the mime type since it's not in the database
- // http.DetectContentType only cares about the first 512 bytes
- head, err := reader.Peek(512)
- if err != nil && err != io.EOF {
- return errors.Wrap(err, "Peek")
- }
- mimeString = http.DetectContentType(head)
- }
-
- // mimeString can contain type and params (like text encoding),
- // so we need to break them apart before passing them to the headers
- mimeType, params, err := mime.ParseMediaType(mimeString)
+ mimeType, params, err := FindMimeType(fa.path, reader)
if err != nil {
return errors.Wrap(err, "ParseMediaType")
}
@@ -159,3 +142,24 @@ func SetUtf8Charset(origParams map[string]string) map[string]string {
}
return params
}
+
+func FindMimeType(filename string, reader *bufio.Reader) (string, map[string]string, error) {
+ // if we have an extension, prefer that instead of trying to sniff the header.
+ // That's generally more accurate than sniffing as lots of things are zip files
+ // under the hood, e.g. most office file types
+ ext := filepath.Ext(filename)
+ var mimeString string
+ if mimeString = mime.TypeByExtension(ext); mimeString == "" {
+ // Sniff the mime type since it's not in the database
+ // http.DetectContentType only cares about the first 512 bytes
+ head, err := reader.Peek(512)
+ if err != nil && err != io.EOF {
+ return "", map[string]string{}, errors.Wrap(err, "Peek")
+ }
+ mimeString = http.DetectContentType(head)
+ }
+
+ // mimeString can contain type and params (like text encoding),
+ // so we need to break them apart before passing them to the headers
+ return mime.ParseMediaType(mimeString)
+}