~sircmpwn/aerc

aerc: fix wrapText and quote, add exec, .OriginalMIMEType var for templates v5 PROPOSED

Leszek Cimała: 6
 create OriginalMail struct
 remove Original* check
 add .OriginalMIMEType variable to reply template
 template: remove last \n to fix additional new lines after quote
 template: add exec and wrap
 template: man cosmetic changes

 17 files changed, 122 insertions(+), 49 deletions(-)
Fixed lines >80 columsn and pushed:

To git.sr.ht:~sircmpwn/aerc
   da6fb1a..a40959c  master -> master

Thank you! This is a much better approach.
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~sircmpwn/aerc/patches/9479/mbox | git am -3
Learn more about email & git

[PATCH aerc v5 1/6] create OriginalMail struct Export this patch

---
 commands/account/compose.go |  3 ++-
 commands/msg/forward.go     | 10 ++++++----
 commands/msg/reply.go       |  9 +++++----
 commands/msg/unsubscribe.go |  2 ++
 lib/templates/template.go   | 28 ++++++++++++++++------------
 models/models.go            |  7 +++++++
 widgets/aerc.go             |  3 ++-
 widgets/compose.go          |  5 +++--
 8 files changed, 43 insertions(+), 24 deletions(-)

diff --git a/commands/account/compose.go b/commands/account/compose.go
index 24e460b..a8ed679 100644
--- a/commands/account/compose.go
+++ b/commands/account/compose.go
@@ -5,6 +5,7 @@ import (
	"regexp"
	"strings"

	"git.sr.ht/~sircmpwn/aerc/models"
	"git.sr.ht/~sircmpwn/aerc/widgets"
	"git.sr.ht/~sircmpwn/getopt"
)
@@ -31,7 +32,7 @@ func (Compose) Execute(aerc *widgets.Aerc, args []string) error {
	acct := aerc.SelectedAccount()

	composer, err := widgets.NewComposer(aerc,
		aerc.Config(), acct.AccountConfig(), acct.Worker(), template, nil)
		aerc.Config(), acct.AccountConfig(), acct.Worker(), template, nil, models.OriginalMail{})
	if err != nil {
		return err
	}
diff --git a/commands/msg/forward.go b/commands/msg/forward.go
index 35d276e..c51949e 100644
--- a/commands/msg/forward.go
+++ b/commands/msg/forward.go
@@ -71,14 +71,16 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error {
		"Subject": subject,
	}

	original := models.OriginalMail{}

	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")
			original.From = models.FormatAddresses(msg.Envelope.From)
			original.Date = 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)
			acct.Worker(), template, defaults, original)
		if err != nil {
			aerc.PushError("Error: " + err.Error())
			return nil, err
@@ -138,7 +140,7 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error {
		store.FetchBodyPart(msg.Uid, msg.BodyStructure, []int{1}, func(reader io.Reader) {
			buf := new(bytes.Buffer)
			buf.ReadFrom(reader)
			defaults["Original"] = buf.String()
			original.Text = buf.String()
			addTab()
		})
	}
diff --git a/commands/msg/reply.go b/commands/msg/reply.go
index a7379d7..2964a83 100644
--- a/commands/msg/reply.go
+++ b/commands/msg/reply.go
@@ -116,15 +116,16 @@ func (reply) Execute(aerc *widgets.Aerc, args []string) error {
		"Subject":     subject,
		"In-Reply-To": msg.Envelope.MessageId,
	}
	original := models.OriginalMail{}

	addTab := func() error {
		if template != "" {
			defaults["OriginalFrom"] = models.FormatAddresses(msg.Envelope.From)
			defaults["OriginalDate"] = msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM")
			original.From = models.FormatAddresses(msg.Envelope.From)
			original.Date = 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)
			acct.AccountConfig(), acct.Worker(), template, defaults, original)
		if err != nil {
			aerc.PushError("Error: " + err.Error())
			return err
@@ -155,7 +156,7 @@ func (reply) Execute(aerc *widgets.Aerc, args []string) error {
		store.FetchBodyPart(msg.Uid, msg.BodyStructure, []int{1}, func(reader io.Reader) {
			buf := new(bytes.Buffer)
			buf.ReadFrom(reader)
			defaults["Original"] = buf.String()
			original.Text = buf.String()
			addTab()
		})
		return nil
diff --git a/commands/msg/unsubscribe.go b/commands/msg/unsubscribe.go
index 5ffec46..682b2b5 100644
--- a/commands/msg/unsubscribe.go
+++ b/commands/msg/unsubscribe.go
@@ -7,6 +7,7 @@ import (
	"strings"

	"git.sr.ht/~sircmpwn/aerc/lib"
	"git.sr.ht/~sircmpwn/aerc/models"
	"git.sr.ht/~sircmpwn/aerc/widgets"
)

@@ -94,6 +95,7 @@ func unsubscribeMailto(aerc *widgets.Aerc, u *url.URL) error {
		acct.Worker(),
		"",
		defaults,
		models.OriginalMail{},
	)
	if err != nil {
		return err
diff --git a/lib/templates/template.go b/lib/templates/template.go
index 21f7b35..8a345d9 100644
--- a/lib/templates/template.go
+++ b/lib/templates/template.go
@@ -10,6 +10,7 @@ import (
	"text/template"
	"time"

	"git.sr.ht/~sircmpwn/aerc/models"
	"github.com/mitchellh/go-homedir"
)

@@ -28,20 +29,23 @@ type TemplateData struct {

func TestTemplateData() TemplateData {
	defaults := map[string]string{
		"To":           "John Doe <john@example.com>",
		"Cc":           "Josh Doe <josh@example.com>",
		"From":         "Jane Smith <jane@example.com>",
		"Subject":      "This is only a test",
		"OriginalText": "This is only a test text",
		"OriginalFrom": "John Doe <john@example.com>",
		"OriginalDate": time.Now().Format("Mon Jan 2, 2006 at 3:04 PM"),
		"To":      "John Doe <john@example.com>",
		"Cc":      "Josh Doe <josh@example.com>",
		"From":    "Jane Smith <jane@example.com>",
		"Subject": "This is only a test",
	}

	return ParseTemplateData(defaults)
	original := models.OriginalMail{
		Date: time.Now().Format("Mon Jan 2, 2006 at 3:04 PM"),
		From: "John Doe <john@example.com>",
		Text: "This is only a test text",
	}

	return ParseTemplateData(defaults, original)
}

func ParseTemplateData(defaults map[string]string) TemplateData {
	originalDate, _ := time.Parse("Mon Jan 2, 2006 at 3:04 PM", defaults["OriginalDate"])
func ParseTemplateData(defaults map[string]string, original models.OriginalMail) TemplateData {
	originalDate, _ := time.Parse("Mon Jan 2, 2006 at 3:04 PM", original.Date)
	td := TemplateData{
		To:           parseAddressList(defaults["To"]),
		Cc:           parseAddressList(defaults["Cc"]),
@@ -49,8 +53,8 @@ func ParseTemplateData(defaults map[string]string) TemplateData {
		From:         parseAddressList(defaults["From"]),
		Date:         time.Now(),
		Subject:      defaults["Subject"],
		OriginalText: defaults["Original"],
		OriginalFrom: parseAddressList(defaults["OriginalFrom"]),
		OriginalText: original.Text,
		OriginalFrom: parseAddressList(original.From),
		OriginalDate: originalDate,
	}
	return td
diff --git a/models/models.go b/models/models.go
index 036a609..7c3c192 100644
--- a/models/models.go
+++ b/models/models.go
@@ -164,3 +164,10 @@ func FormatAddresses(addrs []*Address) string {
	}
	return val.String()
}

// OriginalMail is helper struct used for reply/forward
type OriginalMail struct {
	Date string
	From string
	Text string
}
diff --git a/widgets/aerc.go b/widgets/aerc.go
index da3f56f..a0e356a 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -14,6 +14,7 @@ import (
	"git.sr.ht/~sircmpwn/aerc/config"
	"git.sr.ht/~sircmpwn/aerc/lib"
	"git.sr.ht/~sircmpwn/aerc/lib/ui"
	"git.sr.ht/~sircmpwn/aerc/models"
)

type Aerc struct {
@@ -432,7 +433,7 @@ func (aerc *Aerc) Mailto(addr *url.URL) error {
		}
	}
	composer, err := NewComposer(aerc, aerc.Config(),
		acct.AccountConfig(), acct.Worker(), "", defaults)
		acct.AccountConfig(), acct.Worker(), "", defaults, models.OriginalMail{})
	if err != nil {
		return nil
	}
diff --git a/widgets/compose.go b/widgets/compose.go
index 091eb70..636dcd1 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -26,6 +26,7 @@ import (
	"git.sr.ht/~sircmpwn/aerc/config"
	"git.sr.ht/~sircmpwn/aerc/lib/templates"
	"git.sr.ht/~sircmpwn/aerc/lib/ui"
	"git.sr.ht/~sircmpwn/aerc/models"
	"git.sr.ht/~sircmpwn/aerc/worker/types"
)

@@ -59,7 +60,7 @@ type Composer struct {

func NewComposer(aerc *Aerc, conf *config.AercConfig,
	acct *config.AccountConfig, worker *types.Worker, template string,
	defaults map[string]string) (*Composer, error) {
	defaults map[string]string, original models.OriginalMail) (*Composer, error) {

	if defaults == nil {
		defaults = make(map[string]string)
@@ -68,7 +69,7 @@ func NewComposer(aerc *Aerc, conf *config.AercConfig,
		defaults["From"] = acct.From
	}

	templateData := templates.ParseTemplateData(defaults)
	templateData := templates.ParseTemplateData(defaults, original)
	cmpl := completer.New(conf.Compose.AddressBookCmd, func(err error) {
		aerc.PushError(fmt.Sprintf("could not complete header: %v", err))
		worker.Logger.Printf("could not complete header: %v", err)
-- 
2.24.1

[PATCH aerc v5 2/6] remove Original* check Export this patch

---
 widgets/compose.go | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/widgets/compose.go b/widgets/compose.go
index 636dcd1..9419579 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -488,8 +488,7 @@ func (c *Composer) PrepareHeader() (*mail.Header, []string, error) {
	// Merge in additional headers
	txthdr := mhdr.Header
	for key, value := range c.defaults {
		// skip all Original* defaults, they contain info about original message
		if !txthdr.Has(key) && value != "" && !strings.HasPrefix(key, "Original") {
		if !txthdr.Has(key) && value != "" {
			mhdr.SetText(key, value)
		}
	}
-- 
2.24.1

[PATCH aerc v5 3/6] add .OriginalMIMEType variable to reply template Export this patch

---
 commands/msg/reply.go     |  6 ++++++
 doc/aerc-templates.7.scd  |  6 ++++++
 lib/templates/template.go | 33 ++++++++++++++++++---------------
 models/models.go          |  7 ++++---
 4 files changed, 34 insertions(+), 18 deletions(-)

diff --git a/commands/msg/reply.go b/commands/msg/reply.go
index 2964a83..6b5a698 100644
--- a/commands/msg/reply.go
+++ b/commands/msg/reply.go
@@ -157,6 +157,12 @@ func (reply) Execute(aerc *widgets.Aerc, args []string) error {
			buf := new(bytes.Buffer)
			buf.ReadFrom(reader)
			original.Text = buf.String()
			if len(msg.BodyStructure.Parts) == 0 {
				original.MIMEType = fmt.Sprintf("%s/%s", msg.BodyStructure.MIMEType, msg.BodyStructure.MIMESubType)
			} else {
				// TODO: still will be "multipart/mixed" for mixed mails with attachments, fix this after aerc could handle responding to such mails
				original.MIMEType = fmt.Sprintf("%s/%s", msg.BodyStructure.Parts[0].MIMEType, msg.BodyStructure.Parts[0].MIMESubType)
			}
			addTab()
		})
		return nil
diff --git a/doc/aerc-templates.7.scd b/doc/aerc-templates.7.scd
index 3c8b123..9382f2e 100644
--- a/doc/aerc-templates.7.scd
+++ b/doc/aerc-templates.7.scd
@@ -60,6 +60,12 @@ available always.
	Example:
	{{.Subject}}

*MIME Type*
	MIME Type is available for quoted reply.

	- OriginalMIMEType: MIME type info of quoted mail part. Usually
	  "text/plain" or "text/html".

*Original Message*
	When using quoted reply or forward, the original message is available.
	It can be used using two functions that are available to templates.
diff --git a/lib/templates/template.go b/lib/templates/template.go
index 8a345d9..6eae5a2 100644
--- a/lib/templates/template.go
+++ b/lib/templates/template.go
@@ -22,9 +22,10 @@ type TemplateData struct {
	Date    time.Time
	Subject string
	// Only available when replying with a quote
	OriginalText string
	OriginalFrom []*mail.Address
	OriginalDate time.Time
	OriginalText     string
	OriginalFrom     []*mail.Address
	OriginalDate     time.Time
	OriginalMIMEType string
}

func TestTemplateData() TemplateData {
@@ -36,9 +37,10 @@ func TestTemplateData() TemplateData {
	}

	original := models.OriginalMail{
		Date: time.Now().Format("Mon Jan 2, 2006 at 3:04 PM"),
		From: "John Doe <john@example.com>",
		Text: "This is only a test text",
		Date:     time.Now().Format("Mon Jan 2, 2006 at 3:04 PM"),
		From:     "John Doe <john@example.com>",
		Text:     "This is only a test text",
		MIMEType: "text/plain",
	}

	return ParseTemplateData(defaults, original)
@@ -47,15 +49,16 @@ func TestTemplateData() TemplateData {
func ParseTemplateData(defaults map[string]string, original models.OriginalMail) TemplateData {
	originalDate, _ := time.Parse("Mon Jan 2, 2006 at 3:04 PM", original.Date)
	td := TemplateData{
		To:           parseAddressList(defaults["To"]),
		Cc:           parseAddressList(defaults["Cc"]),
		Bcc:          parseAddressList(defaults["Bcc"]),
		From:         parseAddressList(defaults["From"]),
		Date:         time.Now(),
		Subject:      defaults["Subject"],
		OriginalText: original.Text,
		OriginalFrom: parseAddressList(original.From),
		OriginalDate: originalDate,
		To:               parseAddressList(defaults["To"]),
		Cc:               parseAddressList(defaults["Cc"]),
		Bcc:              parseAddressList(defaults["Bcc"]),
		From:             parseAddressList(defaults["From"]),
		Date:             time.Now(),
		Subject:          defaults["Subject"],
		OriginalText:     original.Text,
		OriginalFrom:     parseAddressList(original.From),
		OriginalDate:     originalDate,
		OriginalMIMEType: original.MIMEType,
	}
	return td
}
diff --git a/models/models.go b/models/models.go
index 7c3c192..8d254a1 100644
--- a/models/models.go
+++ b/models/models.go
@@ -167,7 +167,8 @@ func FormatAddresses(addrs []*Address) string {

// OriginalMail is helper struct used for reply/forward
type OriginalMail struct {
	Date string
	From string
	Text string
	Date     string
	From     string
	Text     string
	MIMEType string
}
-- 
2.24.1

[PATCH aerc v5 4/6] template: remove last \n to fix additional new lines after quote Export this patch

---
 lib/templates/template.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lib/templates/template.go b/lib/templates/template.go
index 6eae5a2..5402472 100644
--- a/lib/templates/template.go
+++ b/lib/templates/template.go
@@ -97,6 +97,7 @@ func wrapLine(text string, lineWidth int) string {

func wrapText(text string, lineWidth int) string {
	text = strings.ReplaceAll(text, "\r\n", "\n")
	text = strings.TrimRight(text, "\n")
	lines := strings.Split(text, "\n")
	var wrapped strings.Builder

@@ -118,6 +119,7 @@ func wrapText(text string, lineWidth int) string {
// quote prepends "> " in front of every line in text
func quote(text string) string {
	text = strings.ReplaceAll(text, "\r\n", "\n")
	text = strings.TrimRight(text, "\n")
	lines := strings.Split(text, "\n")
	var quoted strings.Builder
	for _, line := range lines {
-- 
2.24.1

[PATCH aerc v5 5/6] template: add exec and wrap Export this patch

---
 doc/aerc-templates.7.scd  | 21 +++++++++++++++++----
 lib/templates/template.go | 23 +++++++++++++++++++++++
 2 files changed, 40 insertions(+), 4 deletions(-)

diff --git a/doc/aerc-templates.7.scd b/doc/aerc-templates.7.scd
index 9382f2e..adcc85c 100644
--- a/doc/aerc-templates.7.scd
+++ b/doc/aerc-templates.7.scd
@@ -72,20 +72,33 @@ available always.

	Example:

	_wrapText_ function can be used to wrap the original text to a number
	_wrap_ function can be used to wrap the original text to a number
	of characters per line.
	```
	{{wrapText .OriginalText 72}}
	{{wrap 72 .OriginalText}}
	```

	_quote_ function prepends each line with "> ".
	```
	{{quote .OriginalText}}
	```
	_exec_ function execute external command to process message.
	```
	{{exec `/usr/local/share/aerc/filters/html`}}
	```

	All of the above can be chained together if needed, for example.
	```
	{{exec `/usr/local/share/aerc/filters/html` .OriginalText | wrap 72 | quote}}
	```

	All of the above can be chained together if needed, for example
	Automatic HTML parsing can be achieved.
	```
	{{wrapText .OriginalText 72 | quote}}
	{{if eq .OriginalMIMEType "text/html"}}
	{{exec `/usr/local/share/aerc/filters/html` .OriginalText | wrap 72 | quote}}
	{{else}}
	{{wrap 72 .OriginalText | quote}}
	{{end}}
	```

# SEE ALSO
diff --git a/lib/templates/template.go b/lib/templates/template.go
index 5402472..f2765e8 100644
--- a/lib/templates/template.go
+++ b/lib/templates/template.go
@@ -5,6 +5,7 @@ import (
	"errors"
	"net/mail"
	"os"
	"os/exec"
	"path"
	"strings"
	"text/template"
@@ -72,6 +73,11 @@ func parseAddressList(list string) []*mail.Address {
	return addrs
}

// wrap allows to chain wrapText
func wrap(lineWidth int, text string) string {
	return wrapText(text, lineWidth)
}

func wrapLine(text string, lineWidth int) string {
	words := strings.Fields(text)
	if len(words) == 0 {
@@ -135,10 +141,27 @@ func quote(text string) string {
	return quoted.String()
}

// cmd allow to parse reply by shell command
// text have to be passed by cmd param
// if there is error, original string is returned
func cmd(cmd, text string) string {
	var out bytes.Buffer
	c := exec.Command("sh", "-c", cmd)
	c.Stdin = strings.NewReader(text)
	c.Stdout = &out
	err := c.Run()
	if err != nil {
		return text
	}
	return out.String()
}

var templateFuncs = template.FuncMap{
	"quote":      quote,
	"wrapText":   wrapText,
	"wrap":       wrap,
	"dateFormat": time.Time.Format,
	"exec":       cmd,
}

func findTemplate(templateName string, templateDirs []string) (string, error) {
-- 
2.24.1

[PATCH aerc v5 6/6] template: man cosmetic changes Export this patch

---
 doc/aerc-templates.7.scd | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/doc/aerc-templates.7.scd b/doc/aerc-templates.7.scd
index adcc85c..a681fb2 100644
--- a/doc/aerc-templates.7.scd
+++ b/doc/aerc-templates.7.scd
@@ -32,7 +32,7 @@ available always.
	{{(index .From 0).Name}}
	```

	Get the email address of the first sender
	Get the email address of the first sender.
	```
	{{(index .From 0).Address}}
	```
@@ -82,6 +82,7 @@ available always.
	```
	{{quote .OriginalText}}
	```

	_exec_ function execute external command to process message.
	```
	{{exec `/usr/local/share/aerc/filters/html`}}
-- 
2.24.1
Fixed lines >80 columsn and pushed:

To git.sr.ht:~sircmpwn/aerc
   da6fb1a..a40959c  master -> master

Thank you! This is a much better approach.
View this thread in the archives