list: print emails while reading them

don't read all emails and print them, but read all filenames and parse/print emails as we go.
13 files changed, 261 insertions(+), 205 deletions(-)

M README.md
A => bin/mbhtml
M bin/mbml
M bin/mbrl
M bin/mbvl
M cmd/mb/main.go
M command.go
M decode.go
M edit.go
M list.go
M mh/mh.go
M parse.go
M sequence.go
M README.md +47 -26
@@ 1,12 1,41 @@ 
 # mailbox - an MH message system
-Mailbox consists of a library and a command line tool and is written in
- [Go](http://golang.org/). Mailbox is a program in the spirit of [nmh](http://www.nongnu.org/nmh/) - many ideas of nmh found their way into mailbox. Compared to nmh, mailbox has less features, as it aims to be simpler to setup and to use than nmh.   
-As mailbox is setup/installed by editing and compiling the source code it takes some programming skills and a Go compiler to use mailbox.
+Mailbox is a command line tool and is written in  [Go](http://golang.org/). 
+Mailbox is a program in the spirit of [nmh](http://www.nongnu.org/nmh/) - many ideas of nmh found their way into mailbox. Compared to nmh, mailbox has less features, as it aims to be simpler to setup and to use than nmh.   
+As mailbox is setup/installed by editing and compiling the source code - it takes some programming skills and a go compiler to use mailbox.
+
+## Nonfeatures
+### No state
+State is the root of all evil. Mailbox has no "current message", no "current folder" or the like.
+
+### No support for .mh_sequence files
+No State means: no permanent sequences. IMO it's not worth the effort, as I never use sequences (except marking messages as new, but i can arrange that by using the folder structure).
+Saying that, mailbox is problably not useful for people who receive a lot of emails.
+
+### No MTA or MRA functionality
+Mailbox is a MUA - it does one thing and does it well.
 
-## Features/Nonfeatures
-### No support for .mh_sequence files
-Not worth the SLOC, as I never use sequences (except marking new messages, but i can live without).
-Saying that, mailbox is problably not useful for people who receive a lot of emails.
+## Features
+### Acts as a Filter
+Many Mailbox commands can act as a filter by reading from/writing to stdin.
+Example:
+
+	mb raw inbox/4 | mb append draft  # copy msg no. 4 from folder "inbox" to "draft"
+
+### Mime-Support
+Reading Mime-Messages is fun again!
+
+### Sequences
+Mailbox supports a slightly modified subset of nmh sequences when adressing messages.    
+There are two fundamental types of sequence tokens (n, m are non zero numbers):
+
+* "n": msg id == n
+* "n-m": msg id >= n and msg id <= m
+
+Sequence tokens can be or-combined with ",".
+Sequences can be prefixed with a folder name. If no folder name is given the default 
+folder will be used.
+
+Example: Sequence "inbox/1,6-8,11" will match messages 1,6,7,8,11 in the "inbox" folder.
 
 ## Installation
 Mailbox is installed by editing and compiling the source code.

          
@@ 19,30 48,18 @@ Proceed with the instructions you find i
 $GOPATH/dev/src/bitbucket.org/telesto/mailbox/setup_template.go 
 to setup mailbox.
 
-## Sequences
-Mailbox supports a slightly modified subset of nmh sequences when 
-adressing messages.    
-There are two fundamental sequence tokens (n, m, o are non zero numbers):
-
-* "n": msg id == n
-* "m-o": message id is >= m and <= o
+## Command
+### ls
+List messages in a folder.
 
-These simple sequences tokens can be or-combined with ",", like:
+Example: List all messages in inbox folder:
 
-* "n,m": msg id == n or msg id == m
-* "n,m-o": msg id == n or (message id is >= m and <= o)
-
-There is no limit, a sequence like 1,5,18-29,44,71,91-98,103,112-145 
-is valid.
+		mb ls inbox
 
 ## Examples
-* list all messages in inbox folder
+* view message 318 in default folder
 
-		mb ls inbox
-		
-* view message 318 in "inbox"
-
-		mb view inbox/318
+		mb view 318
 
 * move some messages to the trash folder
 

          
@@ 51,3 68,7 @@ is valid.
 * remove all messages in "trash"
 
 		mb rm trash/1-99999
+
+## TODO
+### Setting up your Email enviroment
+fetchmail && "mb append"
  No newline at end of file

          
A => bin/mbhtml +5 -0
@@ 0,0 1,5 @@ 
+#!/bin/sh
+# mbhtml pipes the output of an mb part command to lynx
+# todo: check args
+# todo: if len(args) == 1 read from stdin
+mb part "$@" | lynx -stdin
  No newline at end of file

          
M bin/mbml +1 -1
@@ 7,5 7,5 @@ if [ $# != 2 ]; then
 	echo "usage: mbml src dst"
 	exit 1
 fi
-mb mv $1/$( lib_LastMsg $1 ) $2
+mb mv "$1"/"$( lib_LastMsg $1 )" "$2"
 

          
M bin/mbrl +2 -2
@@ 3,9 3,9 @@ 
 
 source $GOPATH/src/bitbucket.org/telesto/mailbox/bin/lib
 
-if ! [ $1 ]; then
+if ! [ "$1" ]; then
 	echo "usage: mbrl folder"
 	exit 1
 fi
-mb rm $1/$( lib_LastMsg $1 )
+mb rm "$1"/$( lib_LastMsg "$1" )
 

          
M bin/mbvl +2 -2
@@ 3,9 3,9 @@ 
 
 source $GOPATH/src/bitbucket.org/telesto/mailbox/bin/lib
 
-if ! [ $1 ]; then
+if ! [ "$1" ]; then
 	echo "usage: mbvl folder"
 	exit 1
 fi
-mb view $1/$( lib_LastMsg $1 )
+mb view "$1"/$( lib_LastMsg "$1" )
 

          
M cmd/mb/main.go +43 -39
@@ 8,7 8,6 @@ import (
 	"os"
 	"path"
 	"strconv"
-	"strings"
 
 	mb "bitbucket.org/telesto/mailbox"
 	"bitbucket.org/telesto/mailbox/mh"

          
@@ 171,52 170,51 @@ func main() {
 			err = err1
 		}
 	case "parts":
-		var src io.ReadCloser = os.Stdin
-		if len(os.Args) > 2 {
-			if src, err = openMsg(mbox, os.Args[2]); err != nil {
-				break
-			}
-		}
-		var msg *mail.Message
-		if msg, err = mail.ReadMessage(src); err != nil {
-			src.Close()
-			break
-		}
-		var parts []string
-		parts, err = mb.Parts(msg)
-		for i, part := range parts {
-			os.Stdout.WriteString(strconv.Itoa(i+1) + ": " + part + "\n")
-		}
-		if err != nil {
-			src.Close()
-			break
-		}
-		err = src.Close()
-	case "grep":
-		// syntax: pick <folder> regexp
+		// todo: read sequence from stdin
 		if len(os.Args) < 3 {
+			// todo: read stdin
 			err = missingParams
 			break
 		}
 		var folder string
-		var match *mb.Regexp
-		if folder, match, err = parseRegexpCmd(os.Args[2:]); err != nil {
+		var msgs *mb.Sequence
+		folder, msgs, err = parseSequence(mbox, os.Args[2])
+		if err != nil {
 			break
 		}
-		var msgs []mh.Message
-		if msgs, err = mbox.ReadFolder(folder); err != nil {
+		var parts []string
+		parts, err = mb.Parts(mbox, folder, msgs)
+		for _, part := range parts {
+			os.Stdout.WriteString(part + "\n")
+		}
+		if err != nil {
 			break
 		}
-		var picked []string
-		for i := range msgs {
-			if match.Matches(msgs[i]) {
-				picked = append(picked, strconv.FormatInt(msgs[i].Id, 10))
-			}
-		}
-		if len(picked) > 0 {
-			// main must not know how a filename is created TODO
-			os.Stdout.WriteString(path.Join(folder, strings.Join(picked, ",")) + "\n")
-		}
+	case "grep":
+		// syntax: pick <folder> regexp
+//		if len(os.Args) < 3 {
+//			err = missingParams
+//			break
+//		}
+//		var folder string
+//		var match *mb.Regexp
+//		if folder, match, err = parseRegexpCmd(os.Args[2:]); err != nil {
+//			break
+//		}
+//		var msgs []mh.Message
+//		if msgs, errors = mbox.ReadFolder(folder); err != nil {
+//			break
+//		}
+//		var picked []string
+//		for i := range msgs {
+//			if match.Matches(msgs[i]) {
+//				picked = append(picked, strconv.FormatInt(msgs[i].Id, 10))
+//			}
+//		}
+//		if len(picked) > 0 {
+//			// main must not know how a filename is created TODO
+//			os.Stdout.WriteString(path.Join(folder, strings.Join(picked, ",")) + "\n")
+//		}
 	case "new":
 		var name string
 		if name, err = mb.Create(mbox); len(name) > 0 {

          
@@ 385,9 383,15 @@ func openMsg(mbox *mh.Mailbox, msg strin
 	if len(f) == 0 {
 		f = mbox.Default()
 	}
-	return mbox.Open(f, b)
+	id, err := strconv.ParseInt(b, 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	return mbox.Open(f, id)
 }
 
+// todo: accept things like "inbox/5 inbox/8\ndraft/18-20"
+// todo: make the folder part of the sequence
 func parseSequence(mbox *mh.Mailbox, value string) (string, *mb.Sequence, error) {
 	folder, seq := path.Split(value)
 	if len(folder) == 0 {

          
M command.go +38 -21
@@ 20,22 20,29 @@ import (
 
 // its not an error if no match is found.
 func walk(mbox *mh.Mailbox, folder string, seq *Sequence, proc func(int64) error) error {
-	// todo: let only sorted msgs out here
-	msgs, err := mbox.ReadFolder(folder)
-	if err != nil {
-		return err
-	}
-	// we need a sorted list, so the "Move" cmd will will process the msgs
+	// we need a sorted list, so the "Move" cmd will process the msgs
 	// in the correct time order
-	// todo: see above, need sorted list
-	for j := range msgs {
-		if seq.IsMatch(msgs[j].Id) {
-			if err = proc(msgs[j].Id); err != nil {
-				return err
+	msgs, errors := mbox.ReadFolder(folder)
+	var err error
+	select {
+	case msg, ok := <-msgs:
+		if !ok {
+			return err
+		}
+		if seq.IsMatch(msg.Id) {
+			if err1 := proc(msg.Id); err == nil {
+				err = err1
 			}
 		}
+	case err1, ok := <-errors:
+		if !ok {
+			return err
+		}
+		if err == nil {
+			err = err1
+		}
 	}
-	return nil
+	return err
 }
 
 func Move(mbox *mh.Mailbox, srcFolder string, msgs *Sequence, dst string) error {

          
@@ 92,15 99,26 @@ func Save(msg *mail.Message, number int)
 	return files, parse(msg, saver)
 }
 
-// todo: print something like "inbox/33:1 <header>"?
-func Parts(msg *mail.Message) ([]string, error) {
-	var parts []string
+// todo: decode "name"
 
-	partsLister := func(p part, no int) error {
-		parts = append(parts, p.Header.Get("Content-Type"))
-		return nil
+func Parts(mbox *mh.Mailbox, folder string, msgs *Sequence) ([]string, error) {
+	var parts []string
+	proc := func(id int64) error {
+		r, err := mbox.Open(folder, id)
+		if err != nil {
+			return err
+		}
+		msg, err := mail.ReadMessage(r)
+		if err != nil {
+			return err
+		}
+		partsLister := func(p part, no int) error {
+			parts = append(parts, fmt.Sprintf("%s/%d:%d  %s", folder, id, no, p.Header.Get("Content-Type")))
+			return nil
+		}
+		return parse(msg, partsLister)
 	}
-	return parts, parse(msg, partsLister)
+	return parts, walk(mbox, folder, msgs, proc)
 }
 
 func ViewPart(dst io.Writer, msg *mail.Message, number int) error {

          
@@ 150,10 168,9 @@ func newPlainTextDecoder(dst io.Writer, 
 // if the mail has only one header line without a nl at the end, "mailbox: EOF"
 // is thrown
 func View(dst io.Writer, msg *mail.Message) error {
-	dec := newDecoder()
 	var b bytes.Buffer
 	for _, key := range viewHeader {
-		b.WriteString(key + ": " + dec.safeDecodeHeader(msg.Header.Get(key)) + "\n")
+		b.WriteString(key + ": " + safeDecodeHeader(msg.Header.Get(key)) + "\n")
 	}
 	if _, err := io.Copy(dst, &b); err != nil {
 		return err

          
M decode.go +5 -17
@@ 14,14 14,6 @@ type decoder struct {
 	*mime.WordDecoder
 }
 
-func (d decoder) safeDecodeHeader(key string) string {
-	value, err := d.DecodeHeader(key)
-	if err != nil {
-		value = "Error: " + err.Error()
-	}
-	return value
-}
-
 var dec = &mime.WordDecoder{CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
 	cr, err := decodeCharSet(charset)
 	if err != nil {

          
@@ 30,16 22,12 @@ var dec = &mime.WordDecoder{CharsetReade
 	return transform.NewReader(input, cr), nil
 }}
 
-func newDecoder() decoder {
-	dec := new(mime.WordDecoder)
-	dec.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
-		cr, err := decodeCharSet(charset)
-		if err != nil {
-			return nil, err
-		}
-		return transform.NewReader(input, cr), nil
+func safeDecodeHeader(key string) string {
+	h, err := dec.DecodeHeader(key)
+	if err != nil {
+		h = "Error: " + err.Error()
 	}
-	return decoder{dec}
+	return h
 }
 
 func decodeCharSet(charset string) (transform.Transformer, error) {

          
M edit.go +4 -6
@@ 215,15 215,14 @@ func fmtEditMsg(dst io.Writer, from stri
 // BUG: inbox/5556 gibt error EOF
 func forwardMsg(dst io.Writer, from string, msg *mail.Message) error {
 
-	dec := newDecoder()
-	subject := dec.safeDecodeHeader(msg.Header.Get("Subject"))
+	subject := safeDecodeHeader(msg.Header.Get("Subject"))
 	var b bytes.Buffer
 	fmt.Fprintf(&b, "From: %s\nTo: \nSubject: Fw: %s\nCc: \nBcc: \n", from, subject)
 	if ct := msg.Header.Get("Content-Type"); len(ct) > 0 {
 		fmt.Fprintf(&b, "Content-Type: %s\n", ct)
 	}
 	b.WriteString("\n\n\nBegin forwarded message:\n\n\n")
-	fmt.Fprintf(&b, "Date: %s\nFrom: %s\nTo: %s\nCc: %s\nSubject: %s\n\n", dec.safeDecodeHeader(msg.Header.Get("Date")),
+	fmt.Fprintf(&b, "Date: %s\nFrom: %s\nTo: %s\nCc: %s\nSubject: %s\n\n", safeDecodeHeader(msg.Header.Get("Date")),
 		parseAddressList(msg.Header.Get("From")), parseAddressList(msg.Header.Get("To")),
 		parseAddressList(msg.Header.Get("Cc")), subject)
 

          
@@ 244,7 243,6 @@ func forwardMsg(dst io.Writer, from stri
 // fmtReply does always a "group reply"
 func fmtReply(dst io.Writer, sender string, msg *mail.Message) error {
 
-	dec := newDecoder()
 	b, err := boundary(textproto.MIMEHeader(msg.Header))
 	if err != nil {
 		return errors.New("reading mime boundary: " + err.Error())

          
@@ 256,9 254,9 @@ func fmtReply(dst io.Writer, sender stri
 		if _, err := fmt.Fprintf(dst, "From: %s\nTo: %s\nSubject: Re: %s\nCc: %s\nBcc: \n\n"+
 			"\nOn %s, %s wrote\n\n\n",
 			sender, parseAddressList(h.Get("From")),
-			dec.safeDecodeHeader(h.Get("Subject")),
+			safeDecodeHeader(h.Get("Subject")),
 			parseAddressList(h.Get("Cc")),
-			dec.safeDecodeHeader(h.Get("Date")),
+			safeDecodeHeader(h.Get("Date")),
 			parseAddressList(h.Get("From"))); err != nil {
 			return err
 		}

          
M list.go +34 -31
@@ 1,7 1,6 @@ 
 package mailbox
 
 import (
-	"bufio"
 	"fmt"
 	"io"
 	"net/mail"

          
@@ 15,41 14,45 @@ var sixMonthPast = time.Now().Add(-6 * 3
 var oneDayAhead = time.Now().Add(24 * time.Hour)
 
 // todo: delim as param
-func List(mbox *mh.Mailbox, dst io.Writer, folder string, pick *Sequence) error {
-	msgs, err := mbox.ReadFolder(folder)
-	if err != nil && msgs == nil {
-		// don't leave if we have msgs, show as much as we can
-		return err
-	}
-	out := bufio.NewWriter(dst)
-	defer out.Flush()
-	dec := newDecoder()
+func List(mbox *mh.Mailbox, out io.Writer, folder string, pick *Sequence) error {
 	fType, ok := mbox.FolderType(folder)
 	if !ok {
 		fType = defaultFolderType
 	}
-	for _, msg := range msgs {
-		if !pick.IsMatch(msg.Id) {
-			continue
-		}
-		header := msg.Header
-		// ignore error, we can't do anything about it
-		date, _ := header.Date()
-		format := "Jan _2 15:04"
-		if date.Before(sixMonthPast) || date.After(oneDayAhead) {
-			format = "Jan _2 2006"
+	var err error
+	msgs, errors := mbox.ReadFolder(folder)
+	for {
+		select {
+		case msg, ok := <-msgs:
+			if !ok {
+				return err
+			}
+			if !pick.IsMatch(msg.Id) {
+				continue
+			}
+			header := msg.Header
+			// ignore error, we can't do anything about it
+			date, _ := header.Date()
+			format := "Jan _2 15:04"
+			if date.Before(sixMonthPast) || date.After(oneDayAhead) {
+				format = "Jan _2 2006"
+			}
+			// this was a deleted flag, we dont need that for now
+			flags := " "
+			fmt.Fprintf(out, formats[fType], msg.Id, flags,
+				date.Format(format),
+				decodeAddressList(header.Get("From")),
+				decodeAddressList(header.Get("To")),
+				safeDecodeHeader(header.Get("Subject")))
+			out.Write([]byte{'\n'})
+		case err1, ok := <-errors:
+			if !ok {
+				return err
+			}
+			if err == nil {
+				err = err1
+			}
 		}
-		// this was a deleted flag, we dont need that for now
-		flags := " "
-		fmt.Fprintf(out, formats[fType], msg.Id, flags,
-			date.Format(format),
-			decodeAddressList(header.Get("From")),
-			decodeAddressList(header.Get("To")),
-			dec.safeDecodeHeader(header.Get("Subject")))
-		out.WriteByte('\n')
-	}
-	if err1 := out.Flush(); err == nil {
-		err = err1
 	}
 	return err
 }

          
M mh/mh.go +77 -55
@@ 5,6 5,7 @@ package mh
 import (
 	"errors"
 	"io"
+	"io/ioutil"
 	"net/mail"
 	"os"
 	"path"

          
@@ 32,6 33,8 @@ type Message struct {
 	*mail.Message
 }
 
+// todo: maildir only, the rest is for account
+// we want to be able to use Mailbox by only providing the mail dir
 type Mailbox struct {
 	// MailDir points to the MH Maildir.
 	mailDir,

          
@@ 94,43 97,85 @@ func (mbox Mailbox) FolderType(folder st
 	return t, ok
 }
 
-func (mbox Mailbox) ReadFolder(folder string) ([]Message, error) {
-	if err := mbox.lock(); err != nil {
+func (mbox Mailbox) Folders(parent string) ([]string, error) {
+	files, err := ioutil.ReadDir(mbox.osPath(parent))
+	if err != nil {
 		return nil, err
 	}
-	defer mbox.unlock()
-
-	// todo: return sorted set here?
-	list, err := mbox.readDir(folder)
-	setErr := func(err1 error) {
-		if err == nil {
-			err = err1
+	var folders []string
+	for _, file := range files {
+		if file.IsDir() {
+			folders = append(folders, file.Name())
 		}
 	}
-	// continue on error, show what we got
-	var mails []Message
-	for i := range list {
-		file, err1 := mbox.open(folder, list[i].Name())
-		if err1 != nil {
-			setErr(err1)
-			// save error msg in subject instead of returning error
-			mails = append(mails, errMail(list[i].Id, err1))
-			continue
+	return folders, nil
+}
+
+// returns a sorted list
+func (mbox Mailbox) ReadFolder(folder string) (chan Message, chan error) {
+	mails := make(chan Message, 20)
+	errors := make(chan error)
+	go func() {
+		defer close(errors)
+		defer close(mails)
+		if err := mbox.lock(); err != nil {
+			errors <- err
+		}
+		defer mbox.unlock()
+		list, err := mbox.readDir(folder)
+		if err != nil {
+			errors <- err
 		}
-		msg, err1 := mail.ReadMessage(file)
-		// safe err1 for error msg in mail, see below
-		if err2 := file.Close(); err2 != nil {
-			setErr(err2)
+		// continue on error, show what we got
+		for i := range list {
+			msgid, err := strconv.ParseInt(list[i].Name(), 10, 64)
+			if err != nil {
+				continue
+			}
+			file, err := mbox.open(folder, msgid)
+			if err != nil {
+				// save error msg in subject instead of returning error
+				mails <- errMail(list[i].Id, err)
+				continue
+			}
+			msg, err := mail.ReadMessage(file)
+			// safe err1 for error msg in mail, see below
+			if err1 := file.Close(); err == nil {
+				err = err1
+			}
+			if err != nil {
+				// save error msg in subject instead of returning error
+				mails <- errMail(list[i].Id, err)
+				continue
+			}
+			mails <- Message{list[i].Id, msg}
 		}
-		setErr(err1)
-		if err1 != nil {
-			// save error msg in subject instead of returning error
-			mails = append(mails, errMail(list[i].Id, err1))
-			continue
+	}()
+	return mails, errors
+}
+
+// returns a sorted list
+func (mh *Mailbox) readDir(folder string) ([]msgFile, error) {
+
+	f, err := os.Open(mh.osPath(folder))
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	files, err := f.Readdir(-1)
+	if err != nil {
+		return nil, err
+	}
+	l := len(files)
+	var msgs []msgFile
+	for i := 0; i < l; i++ {
+		// ignore every file that is not a mail
+		if id, err := strconv.ParseInt(files[i].Name(), 10, 0); err == nil && !files[i].IsDir() {
+			msgs = append(msgs, msgFile{files[i], id})
 		}
-		mails = append(mails, Message{list[i].Id, msg})
 	}
-	return mails, err
+	sort.Sort(byId{msgs})
+	return msgs, f.Close()
 }
 
 func (mh *Mailbox) Append(folder string, msg io.Reader) (newMsg string, err error) {

          
@@ 193,29 238,6 @@ type msgFile struct {
 	Id int64
 }
 
-func (mh *Mailbox) readDir(folder string) ([]msgFile, error) {
-
-	f, err := os.Open(mh.osPath(folder))
-	if err != nil {
-		return nil, err
-	}
-	defer f.Close()
-	files, err := f.Readdir(-1)
-	if err != nil {
-		return nil, err
-	}
-	l := len(files)
-	var msgs []msgFile
-	for i := 0; i < l; i++ {
-		// ignore every file that is not a mail
-		if id, err := strconv.ParseInt(files[i].Name(), 10, 0); err == nil && !files[i].IsDir() {
-			msgs = append(msgs, msgFile{files[i], id})
-		}
-	}
-	sort.Sort(byId{msgs})
-	return msgs, f.Close()
-}
-
 func (mh *Mailbox) rename(fOld string, old int64, fNew string, new int64) error {
 	return os.Rename(mh.msgFile(fOld, old), mh.msgFile(fNew, new))
 }

          
@@ 273,9 295,9 @@ func (mh *Mailbox) Remove(folder string,
 	return mh.unlock()
 }
 
-func (mh *Mailbox) open(folder, msg string) (io.ReadCloser, error) {
+func (mh *Mailbox) open(folder string, msg int64) (io.ReadCloser, error) {
 	var src io.ReadCloser
-	src, err := os.Open(mh.osPath(folder, msg))
+	src, err := os.Open(mh.msgFile(folder, msg))
 	return src, err
 }
 

          
@@ 294,7 316,7 @@ func (lc *lockedCloser) Close() error {
 }
 
 // todo: we need this for Raw, rename this?
-func (mh *Mailbox) Open(folder, msg string) (io.ReadCloser, error) {
+func (mh *Mailbox) Open(folder string, msg int64) (io.ReadCloser, error) {
 	if err := mh.lock(); err != nil {
 		return nil, err
 	}

          
M parse.go +2 -3
@@ 11,7 11,7 @@ import (
 	"strings"
 )
 
-// the api for mail.Message and multipart.Part is totally different,
+// the api for mail.Message and multipart.Part is different,
 // which sucks. so we need to wrap them into a struct
 type part struct {
 	Header textproto.MIMEHeader

          
@@ 81,8 81,7 @@ func mimeDecoder(p part) (io.Reader, err
 		return nil, err
 	}
 	if charset, ok := params["charset"]; ok {
-		out, err = newDecoder().CharsetReader(charset, out)
-		if err != nil {
+		if out, err = dec.CharsetReader(charset, out); err != nil {
 			return nil, err
 		}
 	}

          
M sequence.go +1 -2
@@ 72,9 72,8 @@ type Regexp struct {
 }
 
 func (s Regexp) Matches(m mh.Message) bool {
-	dec := newDecoder()
 	for key := range m.Header {
-		if s.matchHeader(key) && s.r.MatchString(dec.safeDecodeHeader(m.Header.Get(key))) {
+		if s.matchHeader(key) && s.r.MatchString(safeDecodeHeader(m.Header.Get(key))) {
 			return true
 		}
 	}