From 3ba69edab5f0c787424dac9649e43a7743da13ca Mon Sep 17 00:00:00 2001 From: Srivathsan Murali Date: Sun, 3 Nov 2019 13:51:14 +0100 Subject: [PATCH] Add Templates with Parsing + Changes NewComposer to return error. + Add lib to handle templates using "text/template". + Add -T option to following commands - compose. - reply - forward + Quoted replies using templates. + Forwards as body using templates + Default templates are installed similar to filters. + Templates Config in aerc.conf. - Required templates are parsed while loading config. + Add aerc-templates.7 manual for using template data. --- Makefile | 7 +- commands/account/compose.go | 24 ++++-- commands/msg/forward.go | 155 +++++++++++++++++----------------- commands/msg/reply.go | 73 ++++++++-------- commands/msg/unsubscribe.go | 6 +- config/aerc.conf.in | 22 ++++- config/config.go | 44 ++++++++-- doc/aerc-config.5.scd | 25 ++++++ doc/aerc-templates.7.scd | 89 ++++++++++++++++++++ lib/templates/template.go | 160 ++++++++++++++++++++++++++++++++++++ templates/forward_as_body | 2 + templates/quoted_reply | 2 + widgets/aerc.go | 7 +- widgets/compose.go | 37 ++++++++- 14 files changed, 510 insertions(+), 143 deletions(-) create mode 100644 doc/aerc-templates.7.scd create mode 100644 lib/templates/template.go create mode 100644 templates/forward_as_body create mode 100644 templates/quoted_reply diff --git a/Makefile b/Makefile index c2e6d1b..0592c56 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,8 @@ DOCS := \ aerc-sendmail.5 \ aerc-notmuch.5 \ aerc-smtp.5 \ - aerc-tutorial.7 + aerc-tutorial.7 \ + aerc-templates.7 .1.scd.1: scdoc < $< > $@ @@ -58,7 +59,7 @@ clean: install: all mkdir -p $(BINDIR) $(MANDIR)/man1 $(MANDIR)/man5 $(MANDIR)/man7 \ - $(SHAREDIR) $(SHAREDIR)/filters + $(SHAREDIR) $(SHAREDIR)/filters $(SHAREDIR)/templates install -m755 aerc $(BINDIR)/aerc install -m644 aerc.1 $(MANDIR)/man1/aerc.1 install -m644 aerc-search.1 $(MANDIR)/man1/aerc-search.1 @@ -75,6 +76,8 @@ install: all install -m755 filters/hldiff $(SHAREDIR)/filters/hldiff install -m755 filters/html $(SHAREDIR)/filters/html install -m755 filters/plaintext $(SHAREDIR)/filters/plaintext + install -m644 templates/quoted_reply $(SHAREDIR)/templates/quoted_reply + install -m644 templates/forward_as_body $(SHAREDIR)/templates/forward_as_body RMDIR_IF_EMPTY:=sh -c '\ if test -d $$0 && ! ls -1qA $$0 | grep -q . ; then \ diff --git a/commands/account/compose.go b/commands/account/compose.go index 039eb92..24e460b 100644 --- a/commands/account/compose.go +++ b/commands/account/compose.go @@ -24,13 +24,17 @@ func (Compose) Complete(aerc *widgets.Aerc, args []string) []string { } func (Compose) Execute(aerc *widgets.Aerc, args []string) error { - body, err := buildBody(args) + body, template, err := buildBody(args) if err != nil { return err } acct := aerc.SelectedAccount() - composer := widgets.NewComposer(aerc, - aerc.Config(), acct.AccountConfig(), acct.Worker(), nil) + + composer, err := widgets.NewComposer(aerc, + aerc.Config(), acct.AccountConfig(), acct.Worker(), template, nil) + if err != nil { + return err + } tab := aerc.NewTab(composer, "New email") composer.OnHeaderChange("Subject", func(subject string) { if subject == "" { @@ -44,11 +48,11 @@ func (Compose) Execute(aerc *widgets.Aerc, args []string) error { return nil } -func buildBody(args []string) (string, error) { - var body, headers string - opts, optind, err := getopt.Getopts(args, "H:") +func buildBody(args []string) (string, string, error) { + var body, template, headers string + opts, optind, err := getopt.Getopts(args, "H:T:") if err != nil { - return "", err + return "", "", err } for _, opt := range opts { switch opt.Option { @@ -60,11 +64,13 @@ func buildBody(args []string) (string, error) { } else { headers += opt.Value + ":\n" } + case 'T': + template = opt.Value } } posargs := args[optind:] if len(posargs) > 1 { - return "", errors.New("Usage: compose [-H] [body]") + return "", template, errors.New("Usage: compose [-H] [body]") } if len(posargs) == 1 { body = posargs[0] @@ -76,5 +82,5 @@ func buildBody(args []string) (string, error) { body = headers + "\n\n" } } - return body, nil + return body, template, nil } diff --git a/commands/msg/forward.go b/commands/msg/forward.go index 494072d..7570177 100644 --- a/commands/msg/forward.go +++ b/commands/msg/forward.go @@ -1,20 +1,21 @@ package msg import ( - "bufio" + "bytes" "errors" "fmt" - "git.sr.ht/~sircmpwn/aerc/lib" - "git.sr.ht/~sircmpwn/aerc/models" - "git.sr.ht/~sircmpwn/aerc/widgets" - "git.sr.ht/~sircmpwn/getopt" - "github.com/emersion/go-message" - "github.com/emersion/go-message/mail" "io" "io/ioutil" "os" "path" "strings" + + "github.com/emersion/go-message" + "github.com/emersion/go-message/mail" + + "git.sr.ht/~sircmpwn/aerc/models" + "git.sr.ht/~sircmpwn/aerc/widgets" + "git.sr.ht/~sircmpwn/getopt" ) type forward struct{} @@ -32,15 +33,18 @@ func (forward) Complete(aerc *widgets.Aerc, args []string) []string { } func (forward) Execute(aerc *widgets.Aerc, args []string) error { - opts, optind, err := getopt.Getopts(args, "A") + opts, optind, err := getopt.Getopts(args, "AT:") if err != nil { return err } attach := false + template := "" for _, opt := range opts { switch opt.Option { case 'A': attach = true + case 'T': + template = opt.Value } } @@ -69,10 +73,20 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error { "To": to, "Subject": subject, } - composer := widgets.NewComposer(aerc, aerc.Config(), acct.AccountConfig(), - acct.Worker(), defaults) - addTab := func() { + addTab := func() (*widgets.Composer, error) { + if template != "" { + defaults["OriginalFrom"] = models.FormatAddresses(msg.Envelope.From) + defaults["OriginalDate"] = msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM") + } + + composer, err := widgets.NewComposer(aerc, aerc.Config(), acct.AccountConfig(), + acct.Worker(), template, defaults) + if err != nil { + aerc.PushError("Error: " + err.Error()) + return nil, err + } + tab := aerc.NewTab(composer, subject) if to == "" { composer.FocusRecipient() @@ -87,83 +101,68 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error { } tab.Content.Invalidate() }) + return composer, nil } if attach { - forwardAttach(store, composer, msg, addTab) - } else { - forwardBodyPart(store, composer, msg, addTab) - } - return nil -} - -func forwardAttach(store *lib.MessageStore, composer *widgets.Composer, - msg *models.MessageInfo, addTab func()) { - - store.FetchFull([]uint32{msg.Uid}, func(reader io.Reader) { tmpDir, err := ioutil.TempDir("", "aerc-tmp-attachment") if err != nil { - // TODO: Do something with the error - addTab() - return + return err } tmpFileName := path.Join(tmpDir, strings.ReplaceAll(fmt.Sprintf("%s.eml", msg.Envelope.Subject), "/", "-")) - tmpFile, err := os.Create(tmpFileName) - if err != nil { - println(err) - // TODO: Do something with the error - addTab() - return - } + store.FetchFull([]uint32{msg.Uid}, func(reader io.Reader) { + tmpFile, err := os.Create(tmpFileName) + if err != nil { + println(err) + // TODO: Do something with the error + addTab() + return + } - defer tmpFile.Close() - io.Copy(tmpFile, reader) - composer.AddAttachment(tmpFileName) - composer.OnClose(func(composer *widgets.Composer) { - os.RemoveAll(tmpDir) + defer tmpFile.Close() + io.Copy(tmpFile, reader) + composer, err := addTab() + if err != nil { + return + } + composer.AddAttachment(tmpFileName) + composer.OnClose(func(composer *widgets.Composer) { + os.RemoveAll(tmpDir) + }) }) - addTab() - }) -} - -func forwardBodyPart(store *lib.MessageStore, composer *widgets.Composer, - msg *models.MessageInfo, addTab func()) { - // TODO: something more intelligent than fetching the 1st part - // TODO: add attachments! - store.FetchBodyPart(msg.Uid, []int{1}, func(reader io.Reader) { - header := message.Header{} - header.SetText( - "Content-Transfer-Encoding", msg.BodyStructure.Encoding) - header.SetContentType( - msg.BodyStructure.MIMEType, msg.BodyStructure.Params) - header.SetText("Content-Description", msg.BodyStructure.Description) - entity, err := message.New(header, reader) - if err != nil { - // TODO: Do something with the error - addTab() - return - } - mreader := mail.NewReader(entity) - part, err := mreader.NextPart() - if err != nil { - // TODO: Do something with the error - addTab() - return + } else { + if template == "" { + template = aerc.Config().Templates.Forwards } - pipeout, pipein := io.Pipe() - scanner := bufio.NewScanner(part.Body) - go composer.PrependContents(pipeout) - // TODO: Let user customize the date format used here - io.WriteString(pipein, fmt.Sprintf("Forwarded message from %s on %s:\n\n", - msg.Envelope.From[0].Name, - msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM"))) - for scanner.Scan() { - io.WriteString(pipein, fmt.Sprintf("%s\n", scanner.Text())) - } - pipein.Close() - pipeout.Close() - addTab() - }) + // TODO: something more intelligent than fetching the 1st part + // TODO: add attachments! + store.FetchBodyPart(msg.Uid, []int{1}, func(reader io.Reader) { + header := message.Header{} + header.SetText( + "Content-Transfer-Encoding", msg.BodyStructure.Encoding) + header.SetContentType( + msg.BodyStructure.MIMEType, msg.BodyStructure.Params) + header.SetText("Content-Description", msg.BodyStructure.Description) + entity, err := message.New(header, reader) + if err != nil { + // TODO: Do something with the error + addTab() + return + } + mreader := mail.NewReader(entity) + part, err := mreader.NextPart() + if err != nil { + // TODO: Do something with the error + addTab() + return + } + buf := new(bytes.Buffer) + buf.ReadFrom(part.Body) + defaults["Original"] = buf.String() + addTab() + }) + } + return nil } diff --git a/commands/msg/reply.go b/commands/msg/reply.go index 9ef7a3b..b13e254 100644 --- a/commands/msg/reply.go +++ b/commands/msg/reply.go @@ -1,7 +1,7 @@ package msg import ( - "bufio" + "bytes" "errors" "fmt" "io" @@ -32,16 +32,17 @@ func (reply) Complete(aerc *widgets.Aerc, args []string) []string { } func (reply) Execute(aerc *widgets.Aerc, args []string) error { - opts, optind, err := getopt.Getopts(args, "aq") + opts, optind, err := getopt.Getopts(args, "aqT:") if err != nil { return err } if optind != len(args) { - return errors.New("Usage: reply [-aq]") + return errors.New("Usage: reply [-aq -T