Primitive mail listing
Anirudh Oppiliappan x@icyphox.sh
Sat, 03 Jul 2021 18:09:11 +0530
8 files changed,
196 insertions(+),
16 deletions(-)
M
commands/commands.go
→
commands/commands.go
@@ -1,8 +1,12 @@
package commands +import ( + "fmt" +) + type Command interface { Aliases() []string - Execute(cmd string) (interface{}, error) + Execute(cmd string) error } var Commands = make(map[string]Command)@@ -16,7 +20,10 @@
func ProcessCommands(cmd string) { for k, v := range Commands { if cmd == k { - v.Execute(cmd) + err := v.Execute(cmd) + if err != nil { + fmt.Errorf("error: executing %s failed: %+v\n", cmd, err) + } } } }
M
commands/list.go
→
commands/list.go
@@ -2,9 +2,17 @@ package commands
import ( "fmt" + "net/mail" + "os" + "path/filepath" + "sort" + "text/tabwriter" + "time" + + gomaildir "github.com/emersion/go-maildir" "git.icyphox.sh/mx/config" - "github.com/emersion/go-maildir" + "git.icyphox.sh/mx/maildir" ) func init() {@@ -22,14 +30,82 @@ "ls",
} } -func (List) Execute(cmd string) (interface{}, error) { - cfg := config.Config - d := maildir.Dir(cfg.Maildir) - keys, _ := d.Keys() - fmt.Println(keys) +func flagToRune(flags []gomaildir.Flag) []rune { + r := make([]rune, len(flags)) + for i, f := range flags { + r[i] = rune(f) + } + return r +} + +type ListLine struct { + from string + date time.Time + flags string + subject string +} + +func printListLine(lines []ListLine) error { + // w, h, err := term.GetSize() + // if err != nil { + // return err + // } + + writer := tabwriter.NewWriter(os.Stdout, 0, 0, 5, '\t', 0) + layout := "2006-01-02 15:04" + for index, ll := range lines { + dt := time.Time.Format(ll.date, layout) + fmt.Fprintln(writer, index, ll.flags, dt, ll.from, ll.subject) + } + // fmt.Printf("%s%d %s %-20s %-s\n", ll.flags, ll.index, ll.date, ll.from, ll.subject) + return nil +} + +func (List) Execute(cmd string) error { + basePath := config.Config.Maildir.Base + mailbox := config.Config.Maildir.Inbox + dir := gomaildir.Dir(filepath.Join(basePath, mailbox)) + keys, err := dir.Keys() + if err != nil { + return err + } + + lines := []ListLine{} for _, k := range keys { - flags, _ := d.Flags(k) - fmt.Println(k, flags) + m := maildir.Message{} + flags := []gomaildir.Flag{} + + m.Key = k + m.Dir = dir + flags, err = m.Flags() + if err != nil { + return err + } + + strflags := string(flagToRune(flags)) + datestr, err := m.GetHeader("Date") + date, err := mail.ParseDate(datestr) + subject, err := m.GetHeader("Subject") + from, err := m.GetHeader("From") + addr, err := mail.ParseAddress(from) + if err != nil { + return err + } + + ll := ListLine{ + from: addr.Name, + date: date, + flags: strflags, + subject: subject, + } + + lines = append(lines, ll) } - return cmd, nil + // sort lines by date + sort.Slice(lines, func(i, j int) bool { + return lines[i].date.Before(lines[j].date) + }) + + printListLine(lines) + return nil }
M
config/config.go
→
config/config.go
@@ -15,7 +15,16 @@ Config = *ParseConfig()
} type ConfigYaml struct { - Maildir string `yaml:"maildir"` + Maildir struct { + Base string `yaml:"base"` + Inbox string `yaml:"inbox"` + Sent string `yaml:"sent"` + Trash string `yaml:"trash"` + Junk string `yaml:"junk"` + Archive string `yaml:"archive"` + Drafts string `yaml:"drafts"` + Custom []string `yaml:"custom"` + } `yaml:"maildir"` } func ParseConfig() *ConfigYaml {@@ -27,11 +36,11 @@ configPath := filepath.Join(hd, ".config", "mx", "mx.yaml")
cfile, err := os.ReadFile(configPath) if err != nil { - fmt.Printf("error: %+v\n", err) + fmt.Errorf("error: %+v\n", err) return nil } if err = yaml.Unmarshal(cfile, &cfg); err != nil { - fmt.Printf("error: %+v\n", err) + fmt.Errorf("error: %+v\n", err) return nil } return &cfg
M
go.sum
→
go.sum
@@ -4,6 +4,8 @@ github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/peterh/liner v1.2.1 h1:O4BlKaq/LWu6VRWmol4ByWfzx6MfXc5Op5HETyIy5yg= github.com/peterh/liner v1.2.1/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
A
maildir/message.go
@@ -0,0 +1,75 @@
+package maildir + +import ( + "bufio" + "fmt" + "io" + "net/mail" + + "github.com/emersion/go-maildir" +) + +// A lot of this code was borrowed from aerc/worker/maildir/message.go + +type Message struct { + Dir maildir.Dir + Key string +} + +func (m Message) NewReader() (io.Reader, error) { + f, err := m.Dir.Open(m.Key) + if err != nil { + return nil, err + } + return bufio.NewReader(f), nil +} + +// Flags fetches the set of flags currently applied to the message. +func (m Message) Flags() ([]maildir.Flag, error) { + return m.Dir.Flags(m.Key) +} + +// SetFlags replaces the message's flags with a new set. +func (m Message) SetFlags(flags []maildir.Flag) error { + return m.Dir.SetFlags(m.Key, flags) +} + +// SetOneFlag enables or disables a single message flag on the message. +func (m Message) SetOneFlag(flag maildir.Flag, enable bool) error { + flags, err := m.Flags() + if err != nil { + return fmt.Errorf("error: could not read previous flags: %v", err) + } + if enable { + flags = append(flags, flag) + return m.SetFlags(flags) + } + var newFlags []maildir.Flag + for _, oldFlag := range flags { + if oldFlag != flag { + newFlags = append(newFlags, oldFlag) + } + } + return m.SetFlags(newFlags) +} + +// MarkReplied either adds or removes the maildir.FlagReplied flag from the +// message. +func (m Message) MarkReplied(answered bool) error { + return m.SetOneFlag(maildir.FlagReplied, answered) +} + +// Remove deletes the email immediately. +func (m Message) Remove() error { + return m.Dir.Remove(m.Key) +} + +func (m Message) GetHeader(h string) (string, error) { + r, err := m.NewReader() + msg, err := mail.ReadMessage(r) + if err != nil { + return "", err + } + + return msg.Header.Get(h), nil +}
M
main.go
→
main.go
@@ -46,12 +46,12 @@ } else if err == liner.ErrPromptAborted || err == io.EOF {
fmt.Print("bye!") break } else { - fmt.Printf("error reading line: %+v") + fmt.Errorf("error reading line: %+v", err) } } if f, err := os.Create(histfile); err != nil { - fmt.Printf("error writing history file: %+v", err) + fmt.Errorf("error writing history file: %+v", err) } else { line.WriteHistory(f) f.Close()
A
term/utils.go
@@ -0,0 +1,10 @@
+package term + +import ( + "golang.org/x/sys/unix" +) + +func GetSize() (width, height int, err error) { + size, err := unix.IoctlGetWinsize(0, unix.TIOCGWINSZ) + return int(size.Col), int(size.Row), nil +}