Received: from sender4-of-o54.zoho.com (sender4-of-o54.zoho.com [136.143.188.54]) by mail.sr.ht (Postfix) with ESMTPS id 1A66240732 for <~sircmpwn/aerc@lists.sr.ht>; Sun, 3 Nov 2019 12:51:25 +0000 (UTC) Authentication-Results: mail.sr.ht; dkim=pass (1024-bit key) header.d=vathsan.com header.i=sri@vathsan.com header.b=aw57GxI7 ARC-Seal: i=1; a=rsa-sha256; t=1572785482; cv=none; d=zohomail.com; s=zohoarc; b=PUOzfUpFLHitfHR4scPYsB+oTGJAlqRu04Y9T0BQYHJcNJ5LFy9dGJuKfTFqAo8n5Up4vaTHpMffi3SJ7MqyvukSU+PbyXr6ePGS1nBABgXZ8CBZ7OeyItGudWAnriWuYKBBUzhWZpsnH8RqDEA2wvrqds4Py9aamGyyCaKuAYM= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1572785482; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:MIME-Version:Message-ID:Subject:To; bh=BDLV1NdgETUWeoQ0qgEZJZylBi4tMNr/RGz1TNfd3HM=; b=BeR005YfZdM45JDBhMQna1hDkRAxI3UneGVn3wDpAdtRfKlRPxU0nJr7OZuAcSmbRBaseVFdDr7PYI9Y+WJl4aWxGg8sC+2A22DgylBjC8IX6Td1I2RDamAN3G+Jz0jSdF3rVlgqFQT+tMMX3nK7ImZq2l5ZgedsodQqTM1S9Cg= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=vathsan.com; spf=pass smtp.mailfrom=sri@vathsan.com; dmarc=pass header.from= header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1572785482; s=zoho; d=vathsan.com; i=sri@vathsan.com; h=From:To:Cc:Message-ID:Subject:Date:MIME-Version:Content-Transfer-Encoding:Content-Type; l=33050; bh=BDLV1NdgETUWeoQ0qgEZJZylBi4tMNr/RGz1TNfd3HM=; b=aw57GxI777Sb9ZqeCO+ocGqUYOPDkr12TCSVKDerJvV9rb7QHtA9jH7Os+PXgYxG ghPv5h1UDsHTFOEO7vQ7DRFJP1Dn1VkYrNnl64mJwMmtLAVvv9alzpllo0yHvbg4+xZ Vhjeoe2knhBQOTlfGJ1E77MmzKcfn8A6ra0uDUn0= Received: from localhost (212-51-138-70.fiber7.init7.net [212.51.138.70]) by mx.zohomail.com with SMTPS id 1572785480425920.5673047980583; Sun, 3 Nov 2019 04:51:20 -0800 (PST) From: Srivathsan Murali To: ~sircmpwn/aerc@lists.sr.ht Cc: Srivathsan Murali Message-ID: <20191103125114.106623-1-sri@vathsan.com> Subject: [PATCH v4] Add Templates with Parsing Date: Sun, 3 Nov 2019 13:51:14 +0100 X-Mailer: git-send-email 2.23.0 MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ZohoMailClient: External Content-Type: text/plain; charset=utf8 + 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. --- Made template-dirs a colon-separated list of dirs as requested. Also added a aerc-templates manual to help with template creation. This patch is ready from my side. Cheers Makefile | 7 +- commands/account/compose.go | 24 ++++-- commands/msg/forward.go | 157 ++++++++++++++++++----------------- 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, 511 insertions(+), 144 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 c2e6d1ba4a97..b28746c77fb4 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,8 @@ DOCS :=3D \ =09aerc-sendmail.5 \ =09aerc-notmuch.5 \ =09aerc-smtp.5 \ -=09aerc-tutorial.7 +=09aerc-tutorial.7\ +=09aerc-templates.7 =20 .1.scd.1: =09scdoc < $< > $@ @@ -58,7 +59,7 @@ clean: =20 install: all =09mkdir -p $(BINDIR) $(MANDIR)/man1 $(MANDIR)/man5 $(MANDIR)/man7 \ -=09=09$(SHAREDIR) $(SHAREDIR)/filters +=09=09$(SHAREDIR) $(SHAREDIR)/filters $(SHAREDIR)/templates =09install -m755 aerc $(BINDIR)/aerc =09install -m644 aerc.1 $(MANDIR)/man1/aerc.1 =09install -m644 aerc-search.1 $(MANDIR)/man1/aerc-search.1 @@ -75,6 +76,8 @@ install: all =09install -m755 filters/hldiff $(SHAREDIR)/filters/hldiff =09install -m755 filters/html $(SHAREDIR)/filters/html =09install -m755 filters/plaintext $(SHAREDIR)/filters/plaintext +=09install -m644 templates/quoted_reply $(SHAREDIR)/templates/quoted_reply +=09install -m644 templates/forward_as_body $(SHAREDIR)/templates/forward_a= s_body =20 RMDIR_IF_EMPTY:=3Dsh -c '\ if test -d $$0 && ! ls -1qA $$0 | grep -q . ; then \ diff --git a/commands/account/compose.go b/commands/account/compose.go index 039eb92363c6..24e460b318b1 100644 --- a/commands/account/compose.go +++ b/commands/account/compose.go @@ -24,13 +24,17 @@ func (Compose) Complete(aerc *widgets.Aerc, args []stri= ng) []string { } =20 func (Compose) Execute(aerc *widgets.Aerc, args []string) error { -=09body, err :=3D buildBody(args) +=09body, template, err :=3D buildBody(args) =09if err !=3D nil { =09=09return err =09} =09acct :=3D aerc.SelectedAccount() -=09composer :=3D widgets.NewComposer(aerc, -=09=09aerc.Config(), acct.AccountConfig(), acct.Worker(), nil) + +=09composer, err :=3D widgets.NewComposer(aerc, +=09=09aerc.Config(), acct.AccountConfig(), acct.Worker(), template, nil) +=09if err !=3D nil { +=09=09return err +=09} =09tab :=3D aerc.NewTab(composer, "New email") =09composer.OnHeaderChange("Subject", func(subject string) { =09=09if subject =3D=3D "" { @@ -44,11 +48,11 @@ func (Compose) Execute(aerc *widgets.Aerc, args []strin= g) error { =09return nil } =20 -func buildBody(args []string) (string, error) { -=09var body, headers string -=09opts, optind, err :=3D getopt.Getopts(args, "H:") +func buildBody(args []string) (string, string, error) { +=09var body, template, headers string +=09opts, optind, err :=3D getopt.Getopts(args, "H:T:") =09if err !=3D nil { -=09=09return "", err +=09=09return "", "", err =09} =09for _, opt :=3D range opts { =09=09switch opt.Option { @@ -60,11 +64,13 @@ func buildBody(args []string) (string, error) { =09=09=09} else { =09=09=09=09headers +=3D opt.Value + ":\n" =09=09=09} +=09=09case 'T': +=09=09=09template =3D opt.Value =09=09} =09} =09posargs :=3D args[optind:] =09if len(posargs) > 1 { -=09=09return "", errors.New("Usage: compose [-H] [body]") +=09=09return "", template, errors.New("Usage: compose [-H] [body]") =09} =09if len(posargs) =3D=3D 1 { =09=09body =3D posargs[0] @@ -76,5 +82,5 @@ func buildBody(args []string) (string, error) { =09=09=09body =3D headers + "\n\n" =09=09} =09} -=09return body, nil +=09return body, template, nil } diff --git a/commands/msg/forward.go b/commands/msg/forward.go index 494072d07d04..b4d29137a348 100644 --- a/commands/msg/forward.go +++ b/commands/msg/forward.go @@ -1,20 +1,21 @@ package msg =20 import ( -=09"bufio" +=09"bytes" =09"errors" =09"fmt" -=09"git.sr.ht/~sircmpwn/aerc/lib" -=09"git.sr.ht/~sircmpwn/aerc/models" -=09"git.sr.ht/~sircmpwn/aerc/widgets" -=09"git.sr.ht/~sircmpwn/getopt" -=09"github.com/emersion/go-message" -=09"github.com/emersion/go-message/mail" =09"io" =09"io/ioutil" =09"os" =09"path" =09"strings" + +=09"github.com/emersion/go-message" +=09"github.com/emersion/go-message/mail" + +=09"git.sr.ht/~sircmpwn/aerc/models" +=09"git.sr.ht/~sircmpwn/aerc/widgets" +=09"git.sr.ht/~sircmpwn/getopt" ) =20 type forward struct{} @@ -32,15 +33,18 @@ func (forward) Complete(aerc *widgets.Aerc, args []stri= ng) []string { } =20 func (forward) Execute(aerc *widgets.Aerc, args []string) error { -=09opts, optind, err :=3D getopt.Getopts(args, "A") +=09opts, optind, err :=3D getopt.Getopts(args, "AT:") =09if err !=3D nil { =09=09return err =09} =09attach :=3D false +=09template :=3D "" =09for _, opt :=3D range opts { =09=09switch opt.Option { =09=09case 'A': =09=09=09attach =3D true +=09=09case 'T': +=09=09=09template =3D opt.Value =09=09} =09} =20 @@ -69,10 +73,20 @@ func (forward) Execute(aerc *widgets.Aerc, args []strin= g) error { =09=09"To": to, =09=09"Subject": subject, =09} -=09composer :=3D widgets.NewComposer(aerc, aerc.Config(), acct.AccountConf= ig(), -=09=09acct.Worker(), defaults) =20 -=09addTab :=3D func() { +=09addTab :=3D func() (*widgets.Composer, error) { +=09=09if template !=3D "" { +=09=09=09defaults["OriginalFrom"] =3D models.FormatAddresses(msg.Envelope.= From) +=09=09=09defaults["OriginalDate"] =3D msg.Envelope.Date.Format("Mon Jan 2,= 2006 at 3:04 PM") +=09=09} + +=09=09composer, err :=3D widgets.NewComposer(aerc, aerc.Config(), acct.Acc= ountConfig(), +=09=09acct.Worker(), template, defaults) +=09=09if err !=3D nil { +=09=09=09aerc.PushError("Error: "+err.Error()) +=09=09=09return nil, err +=09=09} + =09=09tab :=3D aerc.NewTab(composer, subject) =09=09if to =3D=3D "" { =09=09=09composer.FocusRecipient() @@ -87,83 +101,68 @@ func (forward) Execute(aerc *widgets.Aerc, args []stri= ng) error { =09=09=09} =09=09=09tab.Content.Invalidate() =09=09}) +=09=09return composer, nil =09} =20 =09if attach { -=09=09forwardAttach(store, composer, msg, addTab) -=09} else { -=09=09forwardBodyPart(store, composer, msg, addTab) -=09} -=09return nil -} - -func forwardAttach(store *lib.MessageStore, composer *widgets.Composer, -=09msg *models.MessageInfo, addTab func()) { - -=09store.FetchFull([]uint32{msg.Uid}, func(reader io.Reader) { =09=09tmpDir, err :=3D ioutil.TempDir("", "aerc-tmp-attachment") =09=09if err !=3D nil { -=09=09=09// TODO: Do something with the error -=09=09=09addTab() -=09=09=09return +=09=09=09return err =09=09} =09=09tmpFileName :=3D path.Join(tmpDir, -=09=09=09strings.ReplaceAll(fmt.Sprintf("%s.eml", msg.Envelope.Subject), "= /", "-")) -=09=09tmpFile, err :=3D os.Create(tmpFileName) -=09=09if err !=3D nil { -=09=09=09println(err) -=09=09=09// TODO: Do something with the error -=09=09=09addTab() -=09=09=09return -=09=09} +=09=09strings.ReplaceAll(fmt.Sprintf("%s.eml", msg.Envelope.Subject), "/",= "-")) +=09=09store.FetchFull([]uint32{msg.Uid}, func(reader io.Reader) { +=09=09=09tmpFile, err :=3D os.Create(tmpFileName) +=09=09=09if err !=3D nil { +=09=09=09=09println(err) +=09=09=09=09// TODO: Do something with the error +=09=09=09=09addTab() +=09=09=09=09return +=09=09=09} =20 -=09=09defer tmpFile.Close() -=09=09io.Copy(tmpFile, reader) -=09=09composer.AddAttachment(tmpFileName) -=09=09composer.OnClose(func(composer *widgets.Composer) { -=09=09=09os.RemoveAll(tmpDir) +=09=09=09defer tmpFile.Close() +=09=09=09io.Copy(tmpFile, reader) +=09=09=09composer, err :=3D addTab() +=09=09=09if err !=3D nil { +=09=09=09=09return +=09=09=09} +=09=09=09composer.AddAttachment(tmpFileName) +=09=09=09composer.OnClose(func(composer *widgets.Composer) { +=09=09=09=09os.RemoveAll(tmpDir) +=09=09=09}) =09=09}) -=09=09addTab() -=09}) -} - -func forwardBodyPart(store *lib.MessageStore, composer *widgets.Composer, -=09msg *models.MessageInfo, addTab func()) { -=09// TODO: something more intelligent than fetching the 1st part -=09// TODO: add attachments! -=09store.FetchBodyPart(msg.Uid, []int{1}, func(reader io.Reader) { -=09=09header :=3D message.Header{} -=09=09header.SetText( -=09=09=09"Content-Transfer-Encoding", msg.BodyStructure.Encoding) -=09=09header.SetContentType( -=09=09=09msg.BodyStructure.MIMEType, msg.BodyStructure.Params) -=09=09header.SetText("Content-Description", msg.BodyStructure.Description) -=09=09entity, err :=3D message.New(header, reader) -=09=09if err !=3D nil { -=09=09=09// TODO: Do something with the error -=09=09=09addTab() -=09=09=09return -=09=09} -=09=09mreader :=3D mail.NewReader(entity) -=09=09part, err :=3D mreader.NextPart() -=09=09if err !=3D nil { -=09=09=09// TODO: Do something with the error -=09=09=09addTab() -=09=09=09return +=09} else { +=09=09if template =3D=3D "" { +=09=09=09template =3D aerc.Config().Templates.Forwards =09=09} =20 -=09=09pipeout, pipein :=3D io.Pipe() -=09=09scanner :=3D bufio.NewScanner(part.Body) -=09=09go composer.PrependContents(pipeout) -=09=09// TODO: Let user customize the date format used here -=09=09io.WriteString(pipein, fmt.Sprintf("Forwarded message from %s on %s:= \n\n", -=09=09=09msg.Envelope.From[0].Name, -=09=09=09msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM"))) -=09=09for scanner.Scan() { -=09=09=09io.WriteString(pipein, fmt.Sprintf("%s\n", scanner.Text())) -=09=09} -=09=09pipein.Close() -=09=09pipeout.Close() -=09=09addTab() -=09}) +=09=09// TODO: something more intelligent than fetching the 1st part +=09=09// TODO: add attachments! +=09=09store.FetchBodyPart(msg.Uid, []int{1}, func(reader io.Reader) { +=09=09=09header :=3D message.Header{} +=09=09=09header.SetText( +=09=09=09=09"Content-Transfer-Encoding", msg.BodyStructure.Encoding) +=09=09=09header.SetContentType( +=09=09=09=09msg.BodyStructure.MIMEType, msg.BodyStructure.Params) +=09=09=09=09header.SetText("Content-Description", msg.BodyStructure.Descri= ption) +=09=09=09=09entity, err :=3D message.New(header, reader) +=09=09=09=09if err !=3D nil { +=09=09=09=09=09// TODO: Do something with the error +=09=09=09=09=09addTab() +=09=09=09=09=09return +=09=09=09=09} +=09=09=09=09mreader :=3D mail.NewReader(entity) +=09=09=09=09part, err :=3D mreader.NextPart() +=09=09=09=09if err !=3D nil { +=09=09=09=09=09// TODO: Do something with the error +=09=09=09=09=09addTab() +=09=09=09=09=09return +=09=09=09=09} +=09=09=09=09buf :=3D new(bytes.Buffer) +=09=09=09=09buf.ReadFrom(part.Body) +=09=09=09=09defaults["Original"] =3D buf.String() +=09=09=09=09addTab() +=09=09=09}) +=09} +=09return nil } diff --git a/commands/msg/reply.go b/commands/msg/reply.go index 9ef7a3b82206..e65b504faf39 100644 --- a/commands/msg/reply.go +++ b/commands/msg/reply.go @@ -1,7 +1,7 @@ package msg =20 import ( -=09"bufio" +=09"bytes" =09"errors" =09"fmt" =09"io" @@ -32,16 +32,17 @@ func (reply) Complete(aerc *widgets.Aerc, args []string= ) []string { } =20 func (reply) Execute(aerc *widgets.Aerc, args []string) error { -=09opts, optind, err :=3D getopt.Getopts(args, "aq") +=09opts, optind, err :=3D getopt.Getopts(args, "aqT:") =09if err !=3D nil { =09=09return err =09} =09if optind !=3D len(args) { -=09=09return errors.New("Usage: reply [-aq]") +=09=09return errors.New("Usage: reply [-aq -T