M cmd/mb/main.go +35 -4
@@ 14,6 14,32 @@ import (
"bitbucket.org/telesto/mailbox/mh"
)
+type Command struct {
+ Name string
+ // Run runs the command.
+ // The args are the arguments after the command name.
+ Run func(mbox mb.Mailbox, args []string) error
+
+ // UsageLine is the one-line usage message.
+ UsageLine string
+
+ // Short is the short description shown in the 'mb help' output.
+ Short string
+
+ // Long is the long message shown in the 'mb help <command>' output.
+ Long string
+}
+
+const command = "mb"
+
+var commands = []*Command{
+ cmdList,
+}
+
+func init() {
+ cmdList.Run = runList
+}
+
var missingParams = errors.New("missing parameters")
func die(msg string) {
@@ 273,9 299,10 @@ func main() {
if err = src.Close(); err != nil {
break
}
- folder, msgs, err1 := parseSequence(mbox, os.Args[2])
- if err1 != nil {
- err = err1
+ var folder string
+ var msgs *mb.Sequence
+ folder, msgs, err = parseSequence(mbox, os.Args[2])
+ if err != nil {
break
}
err = mb.Move(mbox, folder, msgs, mbox.Sent())
@@ 308,13 335,17 @@ func main() {
// todo: do we need this?
err = errors.New(cmd + ": not implemented")
case "store":
+ // todo: document that a maildrop mail needs to
+ // ne piped through grep -v "^From " to remove the
+ // leading "From " line
if len(os.Args) < 3 {
err = missingParams
break
}
folder := os.Args[2]
var msgId string
- if msgId, err = mb.Store(mbox, os.Stdin, folder); len(msgId) != 0 {
+ msgId, err = mbox.Append(folder, os.Stdin)
+ if len(msgId) > 0 {
os.Stdout.WriteString(path.Join(folder, msgId) + "\n")
}
case "pack": // rename to sort, as we gonna sort by date
M command.go +0 -8
@@ 50,14 50,6 @@ func Remove(mbox Mailbox, folder string,
return walk(mbox, folder, msgs, proc)
}
-func Store(mb Mailbox, msg io.Reader, folder string) (string, error) {
- msgid, err := mb.Append(folder, msg)
- if err != nil {
- return msgid, err
- }
- return msgid, err
-}
-
func partError(no int, msg string) error {
return errors.New("mime part " + strconv.Itoa(no) + ": " + msg)
}
M list.go +4 -0
@@ 12,6 12,7 @@ import (
var sixMonthPast = time.Now().Add(-6 * 30 * 24 * time.Hour)
var oneDayAhead = time.Now().Add(24 * time.Hour)
+// todo: delim as param
func List(mbox Mailbox, dst io.Writer, folder string, pick *Sequence) error {
msgs, err := mbox.ReadFolder(folder)
if err != nil && msgs == nil {
@@ 45,6 46,9 @@ func List(mbox Mailbox, dst io.Writer, f
dec.safeDecodeHeader(header.Get("Subject")))
out.WriteByte('\n')
}
+ if err1 := out.Flush(); err == nil {
+ err = err1
+ }
return err
}
M sequence.go +101 -4
@@ 2,9 2,11 @@ package mailbox
import (
"errors"
+ "io"
rgxp "regexp"
"strconv"
"strings"
+ "unicode/utf8"
)
type Sequence struct {
@@ 61,22 63,117 @@ func NewAlwaysMatcher() *Sequence {
}
type Regexp struct {
- r *rgxp.Regexp
+ matchHeader func(string) bool
+ matchBody bool
+ r *rgxp.Regexp
}
func (s Regexp) Matches(m Message) bool {
+ dec := newDecoder()
for key := range m.Header() {
- if s.r.MatchString(key + ": " + m.Header().Get(key)) {
+ if s.matchHeader(key) && s.r.MatchString(dec.safeDecodeHeader(m.Header().Get(key))) {
return true
}
}
+ if !s.matchBody {
+ return false
+ }
return false
}
func ParseRegexp(s string) (*Regexp, error) {
- r, err := rgxp.Compile(s)
+ ss := strings.Split(s, ":")
+ if len(ss) > 2 {
+ return nil, errors.New("invalid parameter")
+ }
+ var keys string
+ regexp := ss[0]
+ if len(ss) == 2 {
+ keys = ss[0]
+ regexp = ss[1]
+ }
+ r, err := rgxp.Compile(regexp)
if err != nil {
return nil, err
}
- return &Regexp{r}, nil
+ if len(keys) == 0 {
+ // match everything
+ return &Regexp{func(string) bool {
+ return true
+ }, true, r}, nil
+ }
+ m := make(map[string]struct{})
+ var matchBody bool
+ for _, key := range strings.Split(keys, ",") {
+ m[key] = struct{}{}
+ if key == "body" {
+ matchBody = true
+ }
+ }
+ // todo: don't use closure here
+ return &Regexp{func(key string) bool {
+ _, ok := m[key]
+ return ok
+ }, matchBody, r}, nil
+}
+
+// readRune is a structure to enable reading UTF-8 encoded code points
+// from an io.Reader.
+type readRune struct {
+ reader io.Reader
+ buf [utf8.UTFMax]byte // used only inside ReadRune
+ pending int // number of bytes in pendBuf; only >0 for bad UTF-8
+ pendBuf [utf8.UTFMax]byte // bytes left over
}
+
+// readByte returns the next byte from the input, which may be
+// left over from a previous read if the UTF-8 was ill-formed.
+func (r *readRune) readByte() (b byte, err error) {
+ if r.pending > 0 {
+ b = r.pendBuf[0]
+ copy(r.pendBuf[0:], r.pendBuf[1:])
+ r.pending--
+ return
+ }
+ n, err := io.ReadFull(r.reader, r.pendBuf[0:1])
+ if n != 1 {
+ return 0, err
+ }
+ return r.pendBuf[0], err
+}
+
+// unread saves the bytes for the next read.
+func (r *readRune) unread(buf []byte) {
+ copy(r.pendBuf[r.pending:], buf)
+ r.pending += len(buf)
+}
+
+// ReadRune returns the next UTF-8 encoded code point from the
+// io.Reader inside r.
+func (r *readRune) ReadRune() (rr rune, size int, err error) {
+ r.buf[0], err = r.readByte()
+ if err != nil {
+ return 0, 0, err
+ }
+ if r.buf[0] < utf8.RuneSelf { // fast check for common ASCII case
+ rr = rune(r.buf[0])
+ size = 1 // Known to be 1.
+ return
+ }
+ var n int
+ for n = 1; !utf8.FullRune(r.buf[0:n]); n++ {
+ r.buf[n], err = r.readByte()
+ if err != nil {
+ if err == io.EOF {
+ err = nil
+ break
+ }
+ return
+ }
+ }
+ rr, size = utf8.DecodeRune(r.buf[0:n])
+ if size < n { // an error
+ r.unread(r.buf[size:n])
+ }
+ return
+}