diff options
author | Vitaly Ovchinnikov <v@postbox.nz> | 2023-08-04 23:33:07 +0300 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2023-08-05 00:04:35 +0200 |
commit | 1144611a1626d8f5d7ffe02d76ea58a97a67aff4 (patch) | |
tree | 70fea6988636554d801569c11d779da34e2aa67e | |
parent | c801f1582cf6d5d3e367c5e2931381559746cccf (diff) | |
download | aerc-1144611a1626d8f5d7ffe02d76ea58a97a67aff4.tar.gz |
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.md | 1 | ||||
-rw-r--r-- | commands/compose/attach.go | 78 | ||||
-rw-r--r-- | doc/aerc.1.scd | 5 | ||||
-rw-r--r-- | lib/attachment.go | 40 |
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) +} |