From fca7321639f77bbf825dc897156d7a21993a2c69 Mon Sep 17 00:00:00 2001 From: Yash Srivastav Date: Sat, 8 Jun 2019 01:05:23 +0530 Subject: Message list: implement index-format option --- lib/address.go | 40 ++++++++++ lib/indexformat.go | 231 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 lib/address.go create mode 100644 lib/indexformat.go (limited to 'lib') diff --git a/lib/address.go b/lib/address.go new file mode 100644 index 0000000..b557195 --- /dev/null +++ b/lib/address.go @@ -0,0 +1,40 @@ +package lib + +import ( + "bytes" + "fmt" + "regexp" + "strings" + + "github.com/emersion/go-imap" +) + +var ( + atom *regexp.Regexp = regexp.MustCompile("^[a-z0-9!#$%7'*+-/=?^_`{}|~ ]+$") +) + +func FormatAddresses(addrs []*imap.Address) string { + val := bytes.Buffer{} + for i, addr := range addrs { + val.WriteString(FormatAddress(addr)) + if i != len(addrs)-1 { + val.WriteString(", ") + } + } + return val.String() +} + +func FormatAddress(addr *imap.Address) string { + if addr.PersonalName != "" { + if atom.MatchString(addr.PersonalName) { + return fmt.Sprintf("%s <%s@%s>", + addr.PersonalName, addr.MailboxName, addr.HostName) + } else { + return fmt.Sprintf("\"%s\" <%s@%s>", + strings.ReplaceAll(addr.PersonalName, "\"", "'"), + addr.MailboxName, addr.HostName) + } + } else { + return fmt.Sprintf("<%s@%s>", addr.MailboxName, addr.HostName) + } +} diff --git a/lib/indexformat.go b/lib/indexformat.go new file mode 100644 index 0000000..3e139e6 --- /dev/null +++ b/lib/indexformat.go @@ -0,0 +1,231 @@ +package lib + +import ( + "errors" + "fmt" + "strings" + "unicode" + + "github.com/emersion/go-imap" + + "git.sr.ht/~sircmpwn/aerc/config" + "git.sr.ht/~sircmpwn/aerc/worker/types" +) + +func ParseIndexFormat(conf *config.AercConfig, number int, + msg *types.MessageInfo) (string, []interface{}, error) { + + format := conf.Ui.IndexFormat + 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.MailboxName, + addr.HostName)) + case 'A': + var addr *imap.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.MailboxName, + addr.HostName)) + case 'C': + retval = append(retval, 'd') + args = append(args, number) + case 'd': + retval = append(retval, 's') + args = append(args, msg.InternalDate.Format(conf.Ui.TimestampFormat)) + case 'D': + retval = append(retval, 's') + args = append(args, msg.InternalDate.Local().Format(conf.Ui.TimestampFormat)) + case 'f': + if len(msg.Envelope.From) == 0 { + return "", nil, errors.New("found no address for sender") + } + addr := FormatAddress(msg.Envelope.From[0]) + 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.PersonalName != "" { + val = addr.PersonalName + } else { + val = fmt.Sprintf("%s@%s", + addr.MailboxName, addr.HostName) + } + 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.PersonalName != "" { + val = addr.PersonalName + } else { + val = fmt.Sprintf("%s@%s", + addr.MailboxName, addr.HostName) + } + retval = append(retval, 's') + args = append(args, val) + case 'r': + addrs := FormatAddresses(msg.Envelope.To) + retval = append(retval, 's') + args = append(args, addrs) + case 'R': + addrs := 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 '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.MailboxName) + 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.PersonalName != "" { + retval = append(retval, 's') + args = append(args, strings.Split(addr.PersonalName, " ")[0]) + } + case 'Z': + // calculate all flags + var readFlag = "" + var delFlag = "" + var flaggedFlag = "" + for _, flag := range msg.Flags { + if flag == "\\Seen" { + readFlag = "O" // message is old + } else if flag == "\\Recent" { + readFlag = "N" // message is new + } else if flag == "\\Answered" { + readFlag = "r" // message has been replied to + } else if flag == "\\Deleted" { + delFlag = "D" + // TODO: check if attachments + } else if flag == "\\Flagged" { + flaggedFlag = "!" + } + // TODO: check gpg stuff + } + retval = append(retval, '3', 's') + args = append(args, readFlag+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 index format") +} -- cgit v1.2.3