about summary refs log tree commit diff stats
Commit message (Expand)AuthorAgeFilesLines
* Modify package filesredcreeper143852021-11-192-2/+2
* Initial project commitredcreeper143852021-11-1911-116/+220
* Initialize project using Create React Appredcreeper143852021-11-1818-0/+38426
id='n23' href='#n23'>23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
package templates

import (
	"bytes"
	"errors"
	"io"
	"net/mail"
	"os"
	"os/exec"
	"path"
	"strings"
	"text/template"
	"time"

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

var version string

//SetVersion initializes the aerc version displayed in template functions
func SetVersion(v string) {
	version = v
}

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{
		"To":      "John Doe <john@example.com>",
		"Cc":      "Josh Doe <josh@example.com>",
		"From":    "Jane Smith <jane@example.com>",
		"Subject": "This is only a test",
	}

	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",
	}

	return ParseTemplateData(defaults, original)
}

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
}

// 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
	}
	var wrapped strings.Builder
	wrapped.WriteString(words[0])
	spaceLeft := lineWidth - wrapped.Len()
	for _, word := range words[1:] {
		if len(word)+1 > spaceLeft {
			wrapped.WriteRune('\n')
			wrapped.WriteString(word)
			spaceLeft = lineWidth - len(word)
		} else {
			wrapped.WriteRune(' ')
			wrapped.WriteString(word)
			spaceLeft -= 1 + len(word)
		}
	}

	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")
	var wrapped strings.Builder

	for _, line := range lines {
		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')
	}
	return wrapped.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 {
		if line == "" {
			quoted.WriteString(">\n")
			continue
		}
		quoted.WriteString("> ")
		quoted.WriteString(line)
		quoted.WriteRune('\n')
	}

	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()
}

func toLocal(t time.Time) time.Time {
	return time.Time.In(t, time.Local)
}

var templateFuncs = template.FuncMap{
	"quote":      quote,
	"wrapText":   wrapText,
	"wrap":       wrap,
	"dateFormat": time.Time.Format,
	"toLocal":    toLocal,
	"exec":       cmd,
	"version":    func() string { return version },
}

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
	}
	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
}