diff --git a/maildir.go b/maildir.go new file mode 100644 index 0000000..0ea3010 --- /dev/null +++ b/maildir.go @@ -0,0 +1,80 @@ +// Package email helps manage emails stored in Maildir format. A good +// write-up on the format is available at http://cr.yp.to/proto/maildir.html +package email + +import ( + "bytes" + "fmt" + "strings" +) + +// Info represents the info bits encoded as the suffix of a Maildir file. info +// starting with "2,": Each character after the comma is an independent flag. +// Flag "P" (passed): the user has resent/forwarded/bounced this message to someone else. +// Flag "R" (replied): the user has replied to this message. +// Flag "S" (seen): the user has viewed this message, though perhaps he didn't read all the way through it. +// Flag "T" (trashed): the user has moved this message to the trash; the trash will be emptied by a later user action. +// Flag "D" (draft): the user considers this message a draft; toggled at user discretion. +// Flag "F" (flagged): user-defined flag; toggled at user discretion. +// New flags may be defined later. Flags must be stored in ASCII order: e.g., "2,FRS". +type Info struct { + Base string // The portion of the filename before the ':'. + Passed bool + Replied bool + Seen bool + Trashed bool + Draft bool + Flagged bool +} + +// NewInfo returns an Info containing the version 2 Maildir flags parsed from +// fn's suffix. +func NewInfo(fn string) (info Info) { + info.Base = fn + i := strings.LastIndex(fn, ":") + if i != -1 { + info.Base = fn[:i] + infoStr := fn[i:] + i = strings.Index(infoStr, ",") + if i == -1 { + return + } + + flags := infoStr[i:] + info.Passed = strings.Contains(flags, "P") + info.Replied = strings.Contains(flags, "R") + info.Seen = strings.Contains(flags, "S") + info.Trashed = strings.Contains(flags, "T") + info.Draft = strings.Contains(flags, "D") + info.Flagged = strings.Contains(flags, "F") + } + return +} + +func (i Info) String() string { + if i.Passed || i.Replied || i.Seen || i.Trashed || i.Draft || i.Flagged { + b := new(bytes.Buffer) + fmt.Fprintf(b, "%s:2,", i.Base) + if i.Passed { + fmt.Fprint(b, "P") + } + if i.Replied { + fmt.Fprint(b, "R") + } + if i.Seen { + fmt.Fprint(b, "S") + } + if i.Trashed { + fmt.Fprint(b, "T") + } + if i.Draft { + fmt.Fprint(b, "D") + } + if i.Flagged { + fmt.Fprint(b, "F") + } + return b.String() + } + + return i.Base +} diff --git a/maildir_test.go b/maildir_test.go new file mode 100644 index 0000000..27beee3 --- /dev/null +++ b/maildir_test.go @@ -0,0 +1,91 @@ +package email + +import ( + "reflect" + "testing" +) + +func TestInfo(t *testing.T) { + datum := []struct { + input string + want Info + clean string // Expected result from .String(), empty defaults to input. + }{ + { + input: "mail:2,PRSTDF", + want: Info{ + Base: "mail", + Passed: true, + Replied: true, + Seen: true, + Trashed: true, + Draft: true, + Flagged: true, + }, + }, + { + input: "mail:2,PRTDF", + want: Info{ + Base: "mail", + Passed: true, + Replied: true, + Seen: false, + Trashed: true, + Draft: true, + Flagged: true, + }, + }, + { + input: "mail:2,PRSTD", + want: Info{ + Base: "mail", + Passed: true, + Replied: true, + Seen: true, + Trashed: true, + Draft: true, + Flagged: false, + }, + }, + { + input: "mail:2,RSTDF", + want: Info{ + Base: "mail", + Passed: false, + Replied: true, + Seen: true, + Trashed: true, + Draft: true, + Flagged: true, + }, + }, + { + input: "mail", + want: Info{ + Base: "mail", + }, + }, + { + input: "mail:2,", + want: Info{ + Base: "mail", + }, + clean: "mail", + }, + } + + for i, d := range datum { + got := NewInfo(d.input) + if !reflect.DeepEqual(d.want, got) { + t.Errorf("%d. Got %#v want %#v", i, got, d.want) + } + + expected := d.input + if d.clean != "" { + expected = d.clean + } + if gotStr := got.String(); gotStr != expected { + t.Errorf("%d. Got %q want %q", i, gotStr, expected) + } + } +}