summary refs log tree commit diff stats
path: root/lib/format/format.go
diff options
context:
space:
mode:
Diffstat (limited to 'lib/format/format.go')
-rw-r--r--lib/format/format.go263
1 files changed, 263 insertions, 0 deletions
diff --git a/lib/format/format.go b/lib/format/format.go
new file mode 100644
index 0000000..b403f2d
--- /dev/null
+++ b/lib/format/format.go
@@ -0,0 +1,263 @@
+package format
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+	"unicode"
+
+	"git.sr.ht/~sircmpwn/aerc/models"
+)
+
+func ParseMessageFormat(format string, timestampformat string,
+	accountName string, number int, msg *models.MessageInfo) (string,
+	[]interface{}, error) {
+	retval := make([]byte, 0, len(format))
+	var args []interface{}
+
+	var c rune
+	for i, ni := 0, 0; i < len(format); {
+		ni = strings.IndexByte(format[i:], '%')
+		if ni < 0 {
+			ni = len(format)
+			retval = append(retval, []byte(format[i:ni])...)
+			break
+		}
+		ni += i + 1
+		// Check for fmt flags
+		if ni == len(format) {
+			goto handle_end_error
+		}
+		c = rune(format[ni])
+		if c == '+' || c == '-' || c == '#' || c == ' ' || c == '0' {
+			ni++
+		}
+
+		// Check for precision and width
+		if ni == len(format) {
+			goto handle_end_error
+		}
+		c = rune(format[ni])
+		for unicode.IsDigit(c) {
+			ni++
+			c = rune(format[ni])
+		}
+		if c == '.' {
+			ni++
+			c = rune(format[ni])
+			for unicode.IsDigit(c) {
+				ni++
+				c = rune(format[ni])
+			}
+		}
+
+		retval = append(retval, []byte(format[i:ni])...)
+		// Get final format verb
+		if ni == len(format) {
+			goto handle_end_error
+		}
+		c = rune(format[ni])
+		switch c {
+		case '%':
+			retval = append(retval, '%')
+		case 'a':
+			if len(msg.Envelope.From) == 0 {
+				return "", nil,
+					errors.New("found no address for sender")
+			}
+			addr := msg.Envelope.From[0]
+			retval = append(retval, 's')
+			args = append(args,
+				fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host))
+		case 'A':
+			var addr *models.Address
+			if len(msg.Envelope.ReplyTo) == 0 {
+				if len(msg.Envelope.From) == 0 {
+					return "", nil,
+						errors.New("found no address for sender or reply-to")
+				} else {
+					addr = msg.Envelope.From[0]
+				}
+			} else {
+				addr = msg.Envelope.ReplyTo[0]
+			}
+			retval = append(retval, 's')
+			args = append(args,
+				fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host))
+		case 'C':
+			retval = append(retval, 'd')
+			args = append(args, number)
+		case 'd':
+			retval = append(retval, 's')
+			args = append(args,
+				msg.InternalDate.Format(timestampformat))
+		case 'D':
+			retval = append(retval, 's')
+			args = append(args,
+				msg.InternalDate.Local().Format(timestampformat))
+		case 'f':
+			if len(msg.Envelope.From) == 0 {
+				return "", nil,
+					errors.New("found no address for sender")
+			}
+			addr := msg.Envelope.From[0].Format()
+			retval = append(retval, 's')
+			args = append(args, addr)
+		case 'F':
+			if len(msg.Envelope.From) == 0 {
+				return "", nil,
+					errors.New("found no address for sender")
+			}
+			addr := msg.Envelope.From[0]
+			// TODO: handle case when sender is current user. Then
+			// use recipient's name
+			var val string
+			if addr.Name != "" {
+				val = addr.Name
+			} else {
+				val = fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host)
+			}
+			retval = append(retval, 's')
+			args = append(args, val)
+
+		case 'i':
+			retval = append(retval, 's')
+			args = append(args, msg.Envelope.MessageId)
+		case 'n':
+			if len(msg.Envelope.From) == 0 {
+				return "", nil,
+					errors.New("found no address for sender")
+			}
+			addr := msg.Envelope.From[0]
+			var val string
+			if addr.Name != "" {
+				val = addr.Name
+			} else {
+				val = fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host)
+			}
+			retval = append(retval, 's')
+			args = append(args, val)
+		case 'r':
+			addrs := models.FormatAddresses(msg.Envelope.To)
+			retval = append(retval, 's')
+			args = append(args, addrs)
+		case 'R':
+			addrs := models.FormatAddresses(msg.Envelope.Cc)
+			retval = append(retval, 's')
+			args = append(args, addrs)
+		case 's':
+			retval = append(retval, 's')
+			args = append(args, msg.Envelope.Subject)
+		case 't':
+			if len(msg.Envelope.To) == 0 {
+				return "", nil,
+					errors.New("found no address for recipient")
+			}
+			addr := msg.Envelope.To[0]
+			retval = append(retval, 's')
+			args = append(args,
+				fmt.Sprintf("%s@%s", addr.Mailbox, addr.Host))
+		case 'T':
+			retval = append(retval, 's')
+			args = append(args, accountName)
+		case 'u':
+			if len(msg.Envelope.From) == 0 {
+				return "", nil,
+					errors.New("found no address for sender")
+			}
+			addr := msg.Envelope.From[0]
+			retval = append(retval, 's')
+			args = append(args, addr.Mailbox)
+		case 'v':
+			if len(msg.Envelope.From) == 0 {
+				return "", nil,
+					errors.New("found no address for sender")
+			}
+			addr := msg.Envelope.From[0]
+			// check if message is from current user
+			if addr.Name != "" {
+				retval = append(retval, 's')
+				args = append(args,
+					strings.Split(addr.Name, " ")[0])
+			}
+		case 'Z':
+			// calculate all flags
+			var readReplyFlag = ""
+			var delFlag = ""
+			var flaggedFlag = ""
+			seen := false
+			recent := false
+			answered := false
+			for _, flag := range msg.Flags {
+				if flag == models.SeenFlag {
+					seen = true
+				} else if flag == models.RecentFlag {
+					recent = true
+				} else if flag == models.AnsweredFlag {
+					answered = true
+				}
+				if flag == models.DeletedFlag {
+					delFlag = "D"
+					// TODO: check if attachments
+				}
+				if flag == models.FlaggedFlag {
+					flaggedFlag = "!"
+				}
+				// TODO: check gpg stuff
+			}
+			if seen {
+				if answered {
+					readReplyFlag = "r" // message has been replied to
+				}
+			} else {
+				if recent {
+					readReplyFlag = "N" // message is new
+				} else {
+					readReplyFlag = "O" // message is old
+				}
+			}
+			retval = append(retval, '3', 's')
+			args = append(args, readReplyFlag+delFlag+flaggedFlag)
+
+		// Move the below cases to proper alphabetical positions once
+		// implemented
+		case 'l':
+			// TODO: number of lines in the message
+			retval = append(retval, 'd')
+			args = append(args, msg.Size)
+		case 'e':
+			// TODO: current message number in thread
+			fallthrough
+		case 'E':
+			// TODO: number of messages in current thread
+			fallthrough
+		case 'H':
+			// TODO: spam attribute(s) of this message
+			fallthrough
+		case 'L':
+			// TODO:
+			fallthrough
+		case 'X':
+			// TODO: number of attachments
+			fallthrough
+		case 'y':
+			// TODO: X-Label field
+			fallthrough
+		case 'Y':
+			// TODO: X-Label field and some other constraints
+			fallthrough
+		default:
+			// Just ignore it and print as is
+			// so %k in index format becomes %%k to Printf
+			retval = append(retval, '%')
+			retval = append(retval, byte(c))
+		}
+		i = ni + 1
+	}
+
+	return string(retval), args, nil
+
+handle_end_error:
+	return "", nil,
+		errors.New("reached end of string while parsing message format")
+}