aerc/lib/templates/template.go

220 lines
5.1 KiB
Go
Raw Normal View History

package templates
import (
"bytes"
"errors"
"io"
"net/mail"
"os"
2020-01-08 21:44:18 +01:00
"os/exec"
"path"
"strings"
"text/template"
"time"
2020-01-08 21:44:14 +01:00
"git.sr.ht/~sircmpwn/aerc/models"
"github.com/mitchellh/go-homedir"
)
type TemplateData struct {
To []*mail.Address
Cc []*mail.Address
Bcc []*mail.Address
From []*mail.Address
Date time.Time
Subject string
// Only available when replying with a quote
OriginalText string
OriginalFrom []*mail.Address
OriginalDate time.Time
OriginalMIMEType string
}
func TestTemplateData() TemplateData {
defaults := map[string]string{
2020-01-08 21:44:14 +01:00
"To": "John Doe <john@example.com>",
"Cc": "Josh Doe <josh@example.com>",
"From": "Jane Smith <jane@example.com>",
"Subject": "This is only a test",
}
2020-01-08 21:44:14 +01:00
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",
MIMEType: "text/plain",
2020-01-08 21:44:14 +01:00
}
return ParseTemplateData(defaults, original)
}
2020-01-08 21:44:14 +01:00
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,
OriginalMIMEType: original.MIMEType,
}
return td
}
func parseAddressList(list string) []*mail.Address {
addrs, err := mail.ParseAddressList(list)
if err != nil {
return nil
}
return addrs
}
2020-01-08 21:44:18 +01:00
// 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 {
return text
}
2019-11-10 19:35:09 +01:00
var wrapped strings.Builder
wrapped.WriteString(words[0])
spaceLeft := lineWidth - wrapped.Len()
for _, word := range words[1:] {
if len(word)+1 > spaceLeft {
2019-11-10 19:35:09 +01:00
wrapped.WriteRune('\n')
wrapped.WriteString(word)
spaceLeft = lineWidth - len(word)
} else {
2019-11-10 19:35:09 +01:00
wrapped.WriteRune(' ')
wrapped.WriteString(word)
spaceLeft -= 1 + len(word)
}
}
2019-11-10 19:35:09 +01:00
return wrapped.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")
2019-11-10 19:35:09 +01:00
var wrapped strings.Builder
for _, line := range lines {
2019-11-10 19:35:09 +01:00
switch {
case line == "":
// deliberately left blank
case line[0] == '>':
// leave quoted text alone
wrapped.WriteString(line)
default:
wrapped.WriteString(wrapLine(line, lineWidth))
}
wrapped.WriteRune('\n')
}
2019-11-10 19:35:09 +01:00
return wrapped.String()
}
2019-11-10 19:35:09 +01:00
// 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")
2019-11-10 19:35:09 +01:00
lines := strings.Split(text, "\n")
var quoted strings.Builder
for _, line := range lines {
if line == "" {
quoted.WriteString(">\n")
2019-12-10 11:54:53 +01:00
continue
2019-11-10 19:35:09 +01:00
}
quoted.WriteString("> ")
quoted.WriteString(line)
quoted.WriteRune('\n')
}
2019-11-10 19:35:09 +01:00
return quoted.String()
}
2020-01-08 21:44:18 +01:00
// 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()
}
func toLocal(t time.Time) time.Time {
return time.Time.In(t, time.Local)
}
var templateFuncs = template.FuncMap{
"quote": quote,
"wrapText": wrapText,
2020-01-08 21:44:18 +01:00
"wrap": wrap,
"dateFormat": time.Time.Format,
"toLocal": toLocal,
2020-01-08 21:44:18 +01:00
"exec": cmd,
}
func findTemplate(templateName string, templateDirs []string) (string, error) {
for _, dir := range templateDirs {
templateFile, err := homedir.Expand(path.Join(dir, templateName))
if err != nil {
return "", err
}
if _, err := os.Stat(templateFile); os.IsNotExist(err) {
continue
}
return templateFile, nil
}
return "", errors.New("Can't find template - " + templateName)
}
func ParseTemplateFromFile(templateName string, templateDirs []string, data interface{}) (io.Reader, error) {
templateFile, err := findTemplate(templateName, templateDirs)
if err != nil {
return nil, err
}
2019-11-10 17:00:21 +01:00
emailTemplate, err := template.New(templateName).
Funcs(templateFuncs).ParseFiles(templateFile)
if err != nil {
return nil, err
}
var body bytes.Buffer
if err := emailTemplate.Execute(&body, data); err != nil {
return nil, err
}
return &body, nil
}
func ParseTemplate(templateText string, data interface{}) ([]byte, error) {
emailTemplate, err :=
template.New("email_template").Funcs(templateFuncs).Parse(templateText)
if err != nil {
return nil, err
}
var outString bytes.Buffer
if err := emailTemplate.Execute(&outString, data); err != nil {
return nil, err
}
return outString.Bytes(), nil
}