aboutsummaryrefslogtreecommitdiffstats
path: root/lib/templates
diff options
context:
space:
mode:
authorRobin Jarry <robin@jarry.cc>2023-02-02 00:48:51 +0100
committerRobin Jarry <robin@jarry.cc>2023-02-20 14:40:59 +0100
commit9fa296fb762231d386db88913d1c2ea521bd813c (patch)
tree445bf6554084bb37e6aed479c9942b5afecb2149 /lib/templates
parent28483808727fd288b95b9dd26a716f6cf7c02b5a (diff)
downloadaerc-9fa296fb762231d386db88913d1c2ea521bd813c.tar.gz
templates: unify data interface
Require that all aerc template data objects implement the same TemplateData interface. Implement that interface in two different places: 1) state.TemplateData (renamed/moved from templates.TemplateData). This structure (along with all its methods) needs to be decoupled from the templates package to break the import cycle with the config package. This allows much simpler construction of this object and ensure that values are calculated only when requested. 2) config.dummyData (extracted from templates). This is only used in the config package to validate user templates. Putting it here allows also to break an import cycle. Use state.TemplateData everywhere (including for account tabs title rendering). Signed-off-by: Robin Jarry <robin@jarry.cc> Reviewed-by: Moritz Poldrack <moritz@poldrack.dev>
Diffstat (limited to 'lib/templates')
-rw-r--r--lib/templates/data.go406
-rw-r--r--lib/templates/functions.go13
-rw-r--r--lib/templates/template.go19
3 files changed, 18 insertions, 420 deletions
diff --git a/lib/templates/data.go b/lib/templates/data.go
deleted file mode 100644
index e34dc2f3..00000000
--- a/lib/templates/data.go
+++ /dev/null
@@ -1,406 +0,0 @@
-package templates
-
-import (
- "fmt"
- "strings"
- "time"
-
- "git.sr.ht/~rjarry/aerc/models"
- sortthread "github.com/emersion/go-imap-sortthread"
- "github.com/emersion/go-message/mail"
-)
-
-type TemplateData struct {
- // only available when composing/replying/forwarding
- headers *mail.Header
- // only available when replying with a quote
- parent *models.OriginalMail
- // only available for the message list
- info *models.MessageInfo
- marked bool
- msgNum int
-
- // message list threading
- ThreadSameSubject bool
- ThreadPrefix string
-
- // account config
- myAddresses map[string]bool
- account string
- folder string // selected folder name
-
- // ui config
- timeFmt string
- thisDayTimeFmt string
- thisWeekTimeFmt string
- thisYearTimeFmt string
- iconAttachment string
-}
-
-func NewTemplateData(
- from *mail.Address,
- aliases []*mail.Address,
- account string,
- folder string,
- timeFmt string,
- thisDayTimeFmt string,
- thisWeekTimeFmt string,
- thisYearTimeFmt string,
- iconAttachment string,
-) *TemplateData {
- myAddresses := map[string]bool{from.Address: true}
- for _, addr := range aliases {
- myAddresses[addr.Address] = true
- }
- return &TemplateData{
- myAddresses: myAddresses,
- account: account,
- folder: folder,
- timeFmt: timeFmt,
- thisDayTimeFmt: thisDayTimeFmt,
- thisWeekTimeFmt: thisWeekTimeFmt,
- thisYearTimeFmt: thisYearTimeFmt,
- iconAttachment: iconAttachment,
- }
-}
-
-// only used for compose/reply/forward
-func (d *TemplateData) SetHeaders(h *mail.Header, o *models.OriginalMail) {
- d.headers = h
- d.parent = o
-}
-
-// only used for message list templates
-func (d *TemplateData) SetInfo(info *models.MessageInfo, num int, marked bool) {
- d.info = info
- d.msgNum = num
- d.marked = marked
-}
-
-func (d *TemplateData) Account() string {
- return d.account
-}
-
-func (d *TemplateData) Folder() string {
- return d.folder
-}
-
-func (d *TemplateData) To() []*mail.Address {
- var to []*mail.Address
- switch {
- case d.info != nil && d.info.Envelope != nil:
- to = d.info.Envelope.To
- case d.headers != nil:
- to, _ = d.headers.AddressList("to")
- }
- return to
-}
-
-func (d *TemplateData) Cc() []*mail.Address {
- var cc []*mail.Address
- switch {
- case d.info != nil && d.info.Envelope != nil:
- cc = d.info.Envelope.Cc
- case d.headers != nil:
- cc, _ = d.headers.AddressList("cc")
- }
- return cc
-}
-
-func (d *TemplateData) Bcc() []*mail.Address {
- var bcc []*mail.Address
- switch {
- case d.info != nil && d.info.Envelope != nil:
- bcc = d.info.Envelope.Bcc
- case d.headers != nil:
- bcc, _ = d.headers.AddressList("bcc")
- }
- return bcc
-}
-
-func (d *TemplateData) From() []*mail.Address {
- var from []*mail.Address
- switch {
- case d.info != nil && d.info.Envelope != nil:
- from = d.info.Envelope.From
- case d.headers != nil:
- from, _ = d.headers.AddressList("from")
- }
- return from
-}
-
-func (d *TemplateData) Peer() []*mail.Address {
- var from, to []*mail.Address
- switch {
- case d.info != nil && d.info.Envelope != nil:
- from = d.info.Envelope.From
- to = d.info.Envelope.To
- case d.headers != nil:
- from, _ = d.headers.AddressList("from")
- to, _ = d.headers.AddressList("to")
- }
- for _, addr := range from {
- if d.myAddresses[addr.Address] {
- return to
- }
- }
- return from
-}
-
-func (d *TemplateData) ReplyTo() []*mail.Address {
- var replyTo []*mail.Address
- switch {
- case d.info != nil && d.info.Envelope != nil:
- replyTo = d.info.Envelope.ReplyTo
- case d.headers != nil:
- replyTo, _ = d.headers.AddressList("reply-to")
- }
- return replyTo
-}
-
-func (d *TemplateData) Date() time.Time {
- var date time.Time
- switch {
- case d.info != nil && d.info.Envelope != nil:
- date = d.info.Envelope.Date
- case d.info != nil:
- date = d.info.InternalDate
- default:
- date = time.Now()
- }
- return date
-}
-
-func (d *TemplateData) DateAutoFormat(date time.Time) string {
- if date.IsZero() {
- return ""
- }
- year := date.Year()
- day := date.YearDay()
- now := time.Now()
- thisYear := now.Year()
- thisDay := now.YearDay()
- fmt := d.timeFmt
- if year == thisYear {
- switch {
- case day == thisDay && d.thisDayTimeFmt != "":
- fmt = d.thisDayTimeFmt
- case day > thisDay-7 && d.thisWeekTimeFmt != "":
- fmt = d.thisWeekTimeFmt
- case d.thisYearTimeFmt != "":
- fmt = d.thisYearTimeFmt
- }
- }
- return date.Format(fmt)
-}
-
-func (d *TemplateData) Header(name string) string {
- var h *mail.Header
- switch {
- case d.headers != nil:
- h = d.headers
- case d.info != nil && d.info.RFC822Headers != nil:
- h = d.info.RFC822Headers
- default:
- return ""
- }
- text, err := h.Text(name)
- if err != nil {
- text = h.Get(name)
- }
- return text
-}
-
-func (d *TemplateData) Subject() string {
- var subject string
- switch {
- case d.info != nil && d.info.Envelope != nil:
- subject = d.info.Envelope.Subject
- case d.headers != nil:
- subject = d.Header("subject")
- }
- if d.ThreadSameSubject {
- subject = ""
- }
- return d.ThreadPrefix + subject
-}
-
-func (d *TemplateData) SubjectBase() string {
- base, _ := sortthread.GetBaseSubject(d.Subject())
- return base
-}
-
-func (d *TemplateData) Number() string {
- return fmt.Sprintf("%d", d.msgNum)
-}
-
-func (d *TemplateData) Labels() []string {
- if d.info == nil {
- return nil
- }
- return d.info.Labels
-}
-
-func (d *TemplateData) Flags() []string {
- var flags []string
- if d.info == nil {
- return flags
- }
-
- switch {
- case d.info.Flags.Has(models.SeenFlag | models.AnsweredFlag):
- flags = append(flags, "r") // message has been replied to
- case d.info.Flags.Has(models.SeenFlag):
- break
- case d.info.Flags.Has(models.RecentFlag):
- flags = append(flags, "N") // message is new
- default:
- flags = append(flags, "O") // message is old
- }
- if d.info.Flags.Has(models.DeletedFlag) {
- flags = append(flags, "D")
- }
- if d.info.BodyStructure != nil {
- for _, bS := range d.info.BodyStructure.Parts {
- if strings.ToLower(bS.Disposition) == "attachment" {
- flags = append(flags, d.iconAttachment)
- break
- }
- }
- }
- if d.info.Flags.Has(models.FlaggedFlag) {
- flags = append(flags, "!")
- }
- if d.marked {
- flags = append(flags, "*")
- }
- return flags
-}
-
-func (d *TemplateData) MessageId() string {
- if d.info == nil || d.info.Envelope == nil {
- return ""
- }
- return d.info.Envelope.MessageId
-}
-
-func (d *TemplateData) Size() uint32 {
- if d.info == nil || d.info.Envelope == nil {
- return 0
- }
- return d.info.Size
-}
-
-func (d *TemplateData) OriginalText() string {
- if d.parent == nil {
- return ""
- }
- return d.parent.Text
-}
-
-func (d *TemplateData) OriginalDate() time.Time {
- if d.parent == nil {
- return time.Time{}
- }
- return d.parent.Date
-}
-
-func (d *TemplateData) OriginalFrom() []*mail.Address {
- if d.parent == nil || d.parent.RFC822Headers == nil {
- return nil
- }
- from, _ := d.parent.RFC822Headers.AddressList("from")
- return from
-}
-
-func (d *TemplateData) OriginalMIMEType() string {
- if d.parent == nil {
- return ""
- }
- return d.parent.MIMEType
-}
-
-func (d *TemplateData) OriginalHeader(name string) string {
- if d.parent == nil || d.parent.RFC822Headers == nil {
- return ""
- }
- text, err := d.parent.RFC822Headers.Text(name)
- if err != nil {
- text = d.parent.RFC822Headers.Get(name)
- }
- return text
-}
-
-// DummyData provides dummy data to test template validity
-func DummyData() *TemplateData {
- from := &mail.Address{
- Name: "John Doe",
- Address: "john@example.com",
- }
- to := &mail.Address{
- Name: "Alice Doe",
- Address: "alice@example.com",
- }
- h := &mail.Header{}
- h.SetAddressList("from", []*mail.Address{from})
- h.SetAddressList("to", []*mail.Address{to})
-
- oh := &mail.Header{}
- oh.SetAddressList("from", []*mail.Address{to})
- oh.SetAddressList("to", []*mail.Address{from})
-
- original := models.OriginalMail{
- Date: time.Now(),
- From: from.String(),
- Text: "This is only a test text",
- MIMEType: "text/plain",
- RFC822Headers: oh,
- }
- data := NewTemplateData(
- to,
- nil,
- "account",
- "folder",
- "2006 Jan 02, 15:04 GMT-0700",
- "15:04",
- "Monday 15:04",
- "Jan 02",
- "a",
- )
- data.SetHeaders(h, &original)
-
- info := &models.MessageInfo{
- BodyStructure: &models.BodyStructure{
- MIMEType: "text",
- MIMESubType: "plain",
- Params: make(map[string]string),
- Description: "",
- Encoding: "",
- Parts: []*models.BodyStructure{},
- Disposition: "",
- DispositionParams: make(map[string]string),
- },
- Envelope: &models.Envelope{
- Date: time.Date(1981, 6, 23, 16, 52, 0, 0, time.UTC),
- Subject: "[PATCH aerc 2/3] foo: baz bar buz",
- From: []*mail.Address{from},
- ReplyTo: []*mail.Address{},
- To: []*mail.Address{to},
- Cc: []*mail.Address{},
- Bcc: []*mail.Address{},
- MessageId: "",
- InReplyTo: "",
- },
- Flags: models.FlaggedFlag,
- Labels: []string{"inbox", "patch"},
- InternalDate: time.Now(),
- RFC822Headers: nil,
- Refs: []string{},
- Size: 65512,
- Uid: 12345,
- Error: nil,
- }
- data.SetInfo(info, 42, true)
-
- return data
-}
diff --git a/lib/templates/functions.go b/lib/templates/functions.go
index 630db264..37b014f5 100644
--- a/lib/templates/functions.go
+++ b/lib/templates/functions.go
@@ -161,9 +161,14 @@ func persons(addresses []*mail.Address) []string {
var units = []string{"K", "M", "G", "T"}
-func humanReadable(value uint32) string {
+func humanReadable(value int) string {
+ sign := ""
+ if value < 0 {
+ sign = "-"
+ value = -value
+ }
if value < 1000 {
- return fmt.Sprintf("%d", value)
+ return fmt.Sprintf("%s%d", sign, value)
}
val := float64(value)
unit := ""
@@ -172,9 +177,9 @@ func humanReadable(value uint32) string {
val /= 1000.0
}
if val < 100.0 {
- return fmt.Sprintf("%.1f%s", val, unit)
+ return fmt.Sprintf("%s%.1f%s", sign, val, unit)
}
- return fmt.Sprintf("%.0f%s", val, unit)
+ return fmt.Sprintf("%s%.0f%s", sign, val, unit)
}
func cwd() string {
diff --git a/lib/templates/template.go b/lib/templates/template.go
index 28785356..bf257669 100644
--- a/lib/templates/template.go
+++ b/lib/templates/template.go
@@ -8,6 +8,7 @@ import (
"path"
"text/template"
+ "git.sr.ht/~rjarry/aerc/models"
"github.com/mitchellh/go-homedir"
)
@@ -28,19 +29,21 @@ func findTemplate(templateName string, templateDirs []string) (string, error) {
"Can't find template %q in any of %v ", templateName, templateDirs)
}
-func ParseTemplateFromFile(templateName string, templateDirs []string, data interface{}) (io.Reader, error) {
- templateFile, err := findTemplate(templateName, templateDirs)
+func ParseTemplateFromFile(
+ name string, dirs []string, data models.TemplateData,
+) (io.Reader, error) {
+ templateFile, err := findTemplate(name, dirs)
if err != nil {
return nil, err
}
- emailTemplate, err := template.New(templateName).
+ emailTemplate, err := template.New(name).
Funcs(templateFuncs).ParseFiles(templateFile)
if err != nil {
return nil, err
}
var body bytes.Buffer
- if err := emailTemplate.Execute(&body, data); err != nil {
+ if err := Render(emailTemplate, &body, data); err != nil {
return nil, err
}
return &body, nil
@@ -50,10 +53,6 @@ func ParseTemplate(name, content string) (*template.Template, error) {
return template.New(name).Funcs(templateFuncs).Parse(content)
}
-func CheckTemplate(templateName string, templateDirs []string) error {
- if templateName != "" {
- _, err := ParseTemplateFromFile(templateName, templateDirs, DummyData())
- return err
- }
- return nil
+func Render(t *template.Template, w io.Writer, data models.TemplateData) error {
+ return t.Execute(w, data)
}