# HG changeset patch # User telesto # Date 1509383220 -3600 # Mon Oct 30 18:07:00 2017 +0100 # Node ID cf14a977e0772d306fddb9e0cd2548d89ecb6f8d # Parent 9dbe074f6020071c17c5e9f74496d8f4bee4eb94 reorganize code move all code to / and have only a main package. don't bother making up packages, we never gonna use them anywhere else. diff --git a/cmd.go b/cmd.go new file mode 100644 --- /dev/null +++ b/cmd.go @@ -0,0 +1,13 @@ +package main + +import ( + "os" +) + +func runList(mbox *Mailbox, args []string) error { + folder := mbox.DefaultFolder + if len(os.Args) > 2 { + folder = os.Args[2] + } + return List(mbox, os.Stdout, folder, NewAlwaysMatcher()) +} diff --git a/cmd/mbs/main.go b/cmd/mbs/main.go deleted file mode 100644 --- a/cmd/mbs/main.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net" -) - -func echoServer(c net.Conn) { - for { - buf := make([]byte, 512) - nr, err := c.Read(buf) - if err != nil { - return - } - - data := buf[0:nr] - println("Server got:", string(data)) - _, err = fmt.Fprintf(c, "stdout: %s\n", string(data)) - if err != nil { - log.Fatal("Write: ", err) - } - _, err = fmt.Fprintln(c, "end:") - if err != nil { - log.Fatal("Write: ", err) - } - } -} - -func main() { - l, err := net.Listen("unix", "/tmp/mbs.sock") - if err != nil { - log.Fatal("listen error:", err) - } - - for { - fd, err := l.Accept() - if err != nil { - log.Fatal("accept error:", err) - } - - go echoServer(fd) - } -} diff --git a/cmd/mbs/mbs b/cmd/mbs/mbs deleted file mode 100755 index d65e66f450c37011dbdae1ce35a099c2dc72c556..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ - msgId := fmt.Sprint(cur) - f, err := mbox.Open(path.Join(folder, msgId)) - if err != nil { - fmt.Fprintln(os.Stderr, err) - continue - } - msg, err := mail.ReadMessage(f) - if err != nil { - fmt.Fprintln(os.Stderr, err) - continue - } - from, err := mail.ParseAddressList(msg.Header.Get("From")) - if err != nil { - fmt.Fprintln(os.Stderr, err) - continue - } - if len(from) == 0 { - fmt.Fprintf(os.Stderr, "%s: no from address", from) - } - arch := path.Join("archive", path.Base(from[0].Address)) - // todo: ignore IsExist, else fail - err = mbox.MkFolder(arch) - if err != nil { - fmt.Fprintln(os.Stderr, err) - } - // todo: make the filename safe? - err = mbox.Move(folder, arch, msgId) - if err != nil { - fmt.Fprintln(os.Stderr, err) - continue - } - case "d": - for _, msg := range mails { - if msg.Id == cur { - msg.Deleted = true - } - } - case "p": - // print msg - f, err := mbox.Open(path.Join(folder, fmt.Sprint(cur))) - if err != nil { - fmt.Fprintln(os.Stderr, err) - continue - } - msg, err := mail.ReadMessage(f) - if err != nil { - fmt.Fprintln(os.Stderr, err) - continue - } - mb.View(os.Stdout, msg) - case "m": - // print mime parts of current msg - f, err := mbox.Open(path.Join(folder, fmt.Sprint(cur))) - if err != nil { - fmt.Fprintln(os.Stderr, err) - continue - } - msg, err := mail.ReadMessage(f) - if err != nil { - fmt.Fprintln(os.Stderr, err) - continue - } - parts := make(chan string) - go func() { - err = mb.Parts(parts, msg) - close(parts) - }() - for part := range parts { - fmt.Println(part) - } - if err1 := f.Close(); err == nil { - err = err1 - } - if err != nil { - fmt.Fprintln(os.Stderr, err) - continue - } - case "l": - // list msgs - mb.List(mails, os.Stdout, mbox.FolderType(folder)) - default: - fmt.Printf("%s: unknown command\n", cmd) - } - } -} - -func parseCmd(cmd string, all []*mb.Message, cur *mb.Message) { -} diff --git a/command.go b/command.go --- a/command.go +++ b/command.go @@ -1,4 +1,4 @@ -package mailbox +package main import ( "bytes" @@ -13,13 +13,11 @@ "strconv" "strings" - "bitbucket.org/telesto/mailbox/mh" - "golang.org/x/text/transform" ) // its not an error if no match is found. -func walk(mbox *mh.Mailbox, folder string, seq *Sequence, proc func(int64) error) error { +func walk(mbox *Mailbox, folder string, seq *Sequence, proc func(int64) error) error { // we need a sorted list, so the "Move" cmd will process the msgs // in the correct time order @@ -58,7 +56,7 @@ } // todo: provide a set of msgs/parts by prematching all msgs of a folder -func Move(mbox *mh.Mailbox, srcFolder string, msgs *Sequence, dst string) error { +func Move(mbox *Mailbox, srcFolder string, msgs *Sequence, dst string) error { proc := func(id int64) error { return mbox.Move(srcFolder, dst, id) } @@ -66,7 +64,7 @@ } // todo: provide a set of msgs/parts by prematching all msgs of a folder -func Remove(mbox *mh.Mailbox, folder string, msgs *Sequence) error { +func Remove(mbox *Mailbox, folder string, msgs *Sequence) error { proc := func(id int64) error { return mbox.Remove(folder, id) } @@ -117,7 +115,7 @@ // todo: decode "name" // todo: provide a set of msgs/parts by prematching all msgs of a folder -func Parts(mbox *mh.Mailbox, folder string, msgs *Sequence) ([]string, error) { +func Parts(mbox *Mailbox, folder string, msgs *Sequence) ([]string, error) { var parts []string proc := func(id int64) error { r, err := mbox.Open(folder, id) diff --git a/command_test.go b/command_test.go new file mode 100644 --- /dev/null +++ b/command_test.go @@ -0,0 +1,20 @@ +package main + +import ( + "testing" +) + +func TestParts(t *testing.T) { + f := failnow(t) + mbox := NewMailbox() + // todo: fix this + mbox.Maildir = "./testdata" + parts, err := Parts(NewMailbox(), "parts", NewAlwaysMatcher()) + f(err) + if !assertEqual("#parts", len(parts), 2, t) { + return + } + assertEqual("part 1", parts[0], "parts/1:1 text/plain; charset=utf-8", t) + assertEqual("part 2", parts[1], "parts/1:2 image/jpeg; name=IMG_4993.JPG;", t) + +} diff --git a/config.go b/config.go --- a/config.go +++ b/config.go @@ -1,4 +1,4 @@ -package mailbox +package main // The folder types for different outputs in list view. // diff --git a/decode.go b/decode.go --- a/decode.go +++ b/decode.go @@ -1,4 +1,4 @@ -package mailbox +package main import ( "io" @@ -35,10 +35,12 @@ if err != nil { return nil, err } - return &Message{NewHeader(msg.Header), msg.Body}, nil + // bug: 0 is a place holder + return &Message{0, NewHeader(msg.Header), msg.Body}, nil } type Message struct { + Id int64 Header Header Body io.Reader } diff --git a/edit.go b/edit.go --- a/edit.go +++ b/edit.go @@ -1,4 +1,4 @@ -package mailbox +package main import ( "bufio" @@ -22,8 +22,6 @@ "time" "golang.org/x/text/transform" - - "bitbucket.org/telesto/mailbox/mh" ) type quoteMessage struct { @@ -56,28 +54,28 @@ type formatMsg func(io.Writer, string, *mail.Message) error // todo: -func Edit(mb *mh.Mailbox, msg *mail.Message) (string, error) { +func Edit(mb *Mailbox, msg *mail.Message) (string, error) { return edit(mb, msg, fmtEditMsg) } -func Create(mb *mh.Mailbox) (string, error) { +func Create(mb *Mailbox) (string, error) { // todo: this is ugly return edit(mb, &mail.Message{Header: make(map[string][]string)}, fmtNewMessage) } -func Forward(mb *mh.Mailbox, msg *mail.Message) (string, error) { +func Forward(mb *Mailbox, msg *mail.Message) (string, error) { return edit(mb, msg, forwardMsg) } // todo: wouldnt make a reader or a filename make more sense here? // we have to rethink the whola api -func Reply(mb *mh.Mailbox, msg *mail.Message) (string, error) { +func Reply(mb *Mailbox, msg *mail.Message) (string, error) { return edit(mb, msg, fmtReply) } // todo: in case of an error we need to inform the callee about the // tmp file that is still existing -func edit(mbox *mh.Mailbox, msg *mail.Message, format formatMsg) (string, error) { +func edit(mbox *Mailbox, msg *mail.Message, format formatMsg) (string, error) { tmp, err := ioutil.TempFile("", "mailbox") if err != nil { return "", err @@ -334,7 +332,7 @@ return "", nil } -func Attach(mbox *mh.Mailbox, dst io.Writer, src io.Reader, filename string) error { +func Attach(mbox *Mailbox, dst io.Writer, src io.Reader, filename string) error { att, err := os.Open(filename) if err != nil { diff --git a/edit_test.go b/edit_test.go --- a/edit_test.go +++ b/edit_test.go @@ -1,4 +1,4 @@ -package mailbox +package main import "testing" diff --git a/list.go b/list.go --- a/list.go +++ b/list.go @@ -1,4 +1,4 @@ -package mailbox +package main import ( "fmt" @@ -6,12 +6,10 @@ "net/mail" "strings" "time" - - "bitbucket.org/telesto/mailbox/mh" ) // todo: delim as param -func List(mbox *mh.Mailbox, out io.Writer, folder string, pick *Sequence) error { +func List(mbox *Mailbox, out io.Writer, folder string, pick *Sequence) error { fType, ok := mbox.Folders[folder] if !ok { fType = defaultFolderType @@ -29,7 +27,7 @@ if !pick.IsMatch(msg.Id) { continue } - header := NewHeader(msg.Header) + header := msg.Header // header := msg.Header // ignore error, we can't do anything about it date, _ := header.Date() diff --git a/list_test.go b/list_test.go --- a/list_test.go +++ b/list_test.go @@ -1,10 +1,8 @@ -package mailbox +package main import ( "os" "testing" - - "bitbucket.org/telesto/mailbox/mh" ) func BenchmarkList(b *testing.B) { @@ -13,7 +11,7 @@ if err != nil { panic(err) } - mbox := mh.NewMailbox() + mbox := NewMailbox() msgs := NewAlwaysMatcher() b.StartTimer() for i := 0; i < b.N; i++ { diff --git a/ls.go b/ls.go new file mode 100644 --- /dev/null +++ b/ls.go @@ -0,0 +1,8 @@ +package main + +var cmdList = &Command{ + Name: "list", + UsageLine: "list [folder]", + Short: "list mails in folder", + Long: `List lists messages in a folder. + `} diff --git a/mailbox_test.go b/mailbox_test.go new file mode 100644 --- /dev/null +++ b/mailbox_test.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "testing" +) + +func assertNotEqual(name string, got, want interface{}, t *testing.T) bool { + t.Helper() + if got == want { + failf(t)("%s: got %#v, wanted != %#v", name, got, want) + return false + } + return true +} + +func assertEqual(name string, got, want interface{}, t *testing.T) bool { + t.Helper() + if got != want { + failf(t)("%s: got %#v, want %#v", name, got, want) + return false + } + return true +} + +func fail(t testing.TB) func(error) bool { + return func(err error) bool { + t.Helper() + if err != nil { + t.Error(err) + return true + } + return false + } +} + +func failf(t testing.TB) func(format string, a ...interface{}) { + return func(format string, a ...interface{}) { + t.Helper() + fail(t)(fmt.Errorf(format, a...)) + } +} + +func failnow(t testing.TB) func(error) { + return func(err error) { + t.Helper() + if fail(t)(err) { + t.FailNow() + } + } +} + +func failnowf(t testing.TB) func(format string, a ...interface{}) { + return func(format string, a ...interface{}) { + t.Helper() + failnow(t)(fmt.Errorf(format, a...)) + } +} diff --git a/cmd/mb/main.go b/main.go rename from cmd/mb/main.go rename to main.go --- a/cmd/mb/main.go +++ b/main.go @@ -8,16 +8,13 @@ "os" "path" "strconv" - - mb "bitbucket.org/telesto/mailbox" - "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 *mh.Mailbox, args []string) error + Run func(mbox *Mailbox, args []string) error // UsageLine is the one-line usage message. UsageLine string @@ -53,16 +50,16 @@ } var err error // todo: put account into mailbox ehre - mbox := mh.NewMailbox() + mbox := NewMailbox() switch cmd := os.Args[1]; cmd { case "parse": // for testing only if len(os.Args) < 3 { err = missingParams break } - var msgs *mb.Sequence + var msgs *Sequence folder, seq := path.Split(os.Args[2]) - msgs, err = mb.ParseSequence(seq) + msgs, err = ParseSequence(seq) os.Stdout.WriteString("Folder: " + folder) fmt.Printf("\nSequence: %+v\n", msgs) if err != nil { @@ -74,7 +71,7 @@ if len(os.Args) > 2 { folder = os.Args[2] } - err = mb.List(mbox, os.Stdout, folder, mb.NewAlwaysMatcher()) + err = List(mbox, os.Stdout, folder, NewAlwaysMatcher()) case "raw": var src io.ReadCloser = os.Stdin if len(os.Args) > 2 { @@ -100,7 +97,7 @@ src.Close() break } - if err = mb.View(os.Stdout, msg); err != nil { + if err = View(os.Stdout, msg); err != nil { src.Close() break } @@ -129,7 +126,7 @@ src.Close() break } - if err = mb.ViewPart(os.Stdout, msg, part); err != nil { + if err = ViewPart(os.Stdout, msg, part); err != nil { src.Close() break } @@ -162,7 +159,7 @@ } // todo: save more than one part by using matchers var files []string - files, err = mb.Save(msg, part) + files, err = Save(msg, part) // don't break on error, print what we got so far for _, file := range files { os.Stdout.WriteString(file + "\n") @@ -179,12 +176,12 @@ break } var folder string - var msgs *mb.Sequence + var msgs *Sequence if folder, msgs, err = parseSequence(mbox, os.Args[2]); err != nil { break } var parts []string - parts, err = mb.Parts(mbox, folder, msgs) + parts, err = Parts(mbox, folder, msgs) for _, part := range parts { os.Stdout.WriteString(part + "\n") } @@ -198,11 +195,11 @@ // break // } // var folder string - // var match *mb.Regexp + // var match *Regexp // if folder, match, err = parseRegexpCmd(os.Args[2:]); err != nil { // break // } - // var msgs []mh.Message + // var msgs []Message // if msgs, errors = mbox.ReadFolder(folder); err != nil { // break // } @@ -218,7 +215,7 @@ // } case "new": var name string - if name, err = mb.Create(mbox); len(name) > 0 { + if name, err = Create(mbox); len(name) > 0 { os.Stdout.WriteString(name + "\n") } case "attach": @@ -233,7 +230,7 @@ break } } - err = mb.Attach(mbox, os.Stdout, src, os.Args[3]) + err = Attach(mbox, os.Stdout, src, os.Args[3]) if err1 := src.Close(); err == nil { err = err1 } @@ -253,7 +250,7 @@ break } var name string - if name, err = mb.Reply(mbox, msg); len(name) > 0 { + if name, err = Reply(mbox, msg); len(name) > 0 { os.Stdout.WriteString(name + "\n") } if err1 := src.Close(); err == nil { @@ -272,7 +269,7 @@ break } var name string - if name, err = mb.Forward(mbox, msg); len(name) > 0 { + if name, err = Forward(mbox, msg); len(name) > 0 { os.Stdout.WriteString(name + "\n") } if err1 := src.Close(); err == nil { @@ -291,7 +288,7 @@ break } var name string - if name, err = mb.Edit(mbox, msg); len(name) > 0 { + if name, err = Edit(mbox, msg); len(name) > 0 { os.Stdout.WriteString(name + "\n") } if err1 := src.Close(); err == nil { @@ -309,7 +306,7 @@ src.Close() break } - if err = mb.Send(msg); err != nil { + if err = Send(msg); err != nil { src.Close() break } @@ -317,12 +314,12 @@ break } var folder string - var msgs *mb.Sequence + var msgs *Sequence folder, msgs, err = parseSequence(mbox, os.Args[2]) if err != nil { break } - err = mb.Move(mbox, folder, msgs, mbox.Sent) + err = Move(mbox, folder, msgs, mbox.Sent) case "mv": // todo: defaultfolder if len(os.Args) < 4 { @@ -330,24 +327,24 @@ break } var folder string - var msgs *mb.Sequence + var msgs *Sequence folder, msgs, err = parseSequence(mbox, os.Args[2]) if err != nil { break } - err = mb.Move(mbox, folder, msgs, os.Args[3]) + err = Move(mbox, folder, msgs, os.Args[3]) case "rm": if len(os.Args) < 3 { err = missingParams break } var folder string - var msgs *mb.Sequence + var msgs *Sequence folder, msgs, err = parseSequence(mbox, os.Args[2]) if err != nil { break } - err = mb.Remove(mbox, folder, msgs) + err = Remove(mbox, folder, msgs) case "cp": // todo: do we need this? err = errors.New(cmd + ": not implemented") @@ -379,7 +376,7 @@ } } -func openMsg(mbox *mh.Mailbox, msg string) (io.ReadCloser, error) { +func openMsg(mbox *Mailbox, msg string) (io.ReadCloser, error) { f, m := path.Split(msg) if len(f) == 0 { f = mbox.DefaultFolder @@ -393,12 +390,12 @@ // 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) { +func parseSequence(mbox *Mailbox, value string) (string, *Sequence, error) { folder, seq := path.Split(value) if len(folder) == 0 { folder = mbox.DefaultFolder } - msgs, err := mb.ParseSequence(seq) + msgs, err := ParseSequence(seq) if err != nil { return "", nil, err } @@ -407,14 +404,14 @@ // todo: when len(args) == 0, then return alwaysmatcher // bullshit, give just one string here -func parseRegexpCmd(args []string) (string, *mb.Regexp, error) { +func parseRegexpCmd(args []string) (string, *Regexp, error) { var i int var folder string if len(args) == 2 { i = 1 folder = args[0] } - m, err := mb.ParseRegexp(args[i]) + m, err := ParseRegexp(args[i]) if err != nil { return "", nil, err } diff --git a/mh/mh.go b/mh.go rename from mh/mh.go rename to mh.go --- a/mh/mh.go +++ b/mh.go @@ -1,4 +1,4 @@ -package mh +package main // todo: move this to a mh subpackage? // todo: locking @@ -16,17 +16,6 @@ // set in setup.go var account *Mailbox -// The folder types for different outputs in list view. -// -// You can add more types and add a format for the new types -// in the formats variable below. -// todo: copied from mailbox pkg -const ( - inOutbox = iota - inbox - outbox -) - // 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 { @@ -75,10 +64,9 @@ return list[len(list)-1].Id + 1, nil } -type Message struct { - Id int64 - *mail.Message -} +//type Message struct { +// *mail.Message +//} // returns a sorted list, can be a number file or a number dir func (mh *Mailbox) readDir(folder string) ([]numberInode, error) { @@ -136,7 +124,7 @@ mails <- errMail(list[i].Id, err) continue } - mails <- Message{list[i].Id, msg} + mails <- Message{list[i].Id, NewHeader(msg.Header), msg.Body} } }() return mails, errors @@ -171,7 +159,7 @@ h := make(map[string][]string, 1) h["Subject"] = []string{"Error: " + err.Error()} body := strings.NewReader("Error: " + err.Error()) - return Message{id, &mail.Message{h, body}} + return Message{id, NewHeader(h), body} } func (mh *Mailbox) MkFolder(folder string) error { diff --git a/parse.go b/parse.go --- a/parse.go +++ b/parse.go @@ -1,4 +1,4 @@ -package mailbox +package main import ( "encoding/base64" diff --git a/reply_test.go b/reply_test.go --- a/reply_test.go +++ b/reply_test.go @@ -1,4 +1,4 @@ -package mailbox +package main import ( "bytes" diff --git a/send.go b/send.go --- a/send.go +++ b/send.go @@ -1,4 +1,4 @@ -package mailbox +package main import ( "fmt" diff --git a/sequence.go b/sequence.go --- a/sequence.go +++ b/sequence.go @@ -1,4 +1,4 @@ -package mailbox +package main import ( "errors" @@ -7,8 +7,6 @@ "strconv" "strings" "unicode/utf8" - - "bitbucket.org/telesto/mailbox/mh" ) type Sequence struct { @@ -72,8 +70,8 @@ r *rgxp.Regexp } -func (s Regexp) Matches(m mh.Message) bool { - for key := range m.Header { +func (s Regexp) Matches(m Message) bool { + for key := range m.Header.Header { if s.matchHeader(key) && s.r.MatchString(safeDecodeHeader(m.Header.Get(key))) { return true } diff --git a/sequence_test.go b/sequence_test.go --- a/sequence_test.go +++ b/sequence_test.go @@ -1,4 +1,4 @@ -package mailbox +package main import "testing" diff --git a/mh/setup_template.go b/setup_template.go rename from mh/setup_template.go rename to setup_template.go --- a/mh/setup_template.go +++ b/setup_template.go @@ -1,4 +1,4 @@ -package mh +package main // These are instructions for setting up the MH account: // - Copy this file to setup.go (in the same folder).