A => cmd.go +13 -0
@@ 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())
+}
R cmd/mbs/main.go => +0 -44
@@ 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)
- }
-}
R cmd/mbs/mbs => +0 -0
R cmd/nedmb/main.go => +0 -160
@@ 1,160 0,0 @@
-package main
-
-import (
- "bufio"
- "fmt"
- "net/mail"
- "os"
- "path"
- "strconv"
-
- mb "bitbucket.org/telesto/mailbox"
-)
-
-const arch = "archive"
-
-/* attached msg problem solution:
--s mailfile Read a single message file mailfile, as pro-
- duced by fs, and treat it as an entire mailbox.
- This is provided for use in plumbing rules; see
- faces(1). */
-func main() {
-
- in := bufio.NewReader(os.Stdin)
- var cur uint64
- if len(os.Args) == 1 {
- fmt.Fprintln(os.Stderr, "missing mailbox")
- os.Exit(2)
- }
-
- // todo: give this command a path, not a folder -
- // so we can do things like "nedmb .", to work on the current path?
- /* The -f command line option speci-
- fies an alternate mailbox. Unrooted path names are inter-
- preted relative to /mail/box/username. If the mailfile
- argument is omitted, the name defaults to stored. */
- mbox := mb.NewMhMailbox()
- folder := os.Args[1]
- if err := mbox.Lock(folder); err != nil {
- fmt.Fprintln(os.Stderr, err)
- os.Exit(1)
- }
- // todo: no need to exit in case of an error, first print all msgs
- mails, err := mbox.ReadFolder(folder)
- if err != nil {
- fmt.Fprintln(os.Stderr, err)
- os.Exit(1)
- }
- mb.List(mails, os.Stdout, folder)
- // todo: mkdir archive
- for {
- fmt.Printf("%s/%d: ", folder, cur)
- cmd, err := in.ReadString('\n')
- if err != nil {
- fmt.Fprintln(os.Stderr, err)
- os.Exit(1)
- }
- cmd = cmd[:len(cmd)-1]
- hits := parseCmd(cmd, mails, cur)
- if n, err := strconv.Atoi(cmd); err == nil {
- cur = uint64(n)
- // when choosing a msg, print it
- cmd = "p"
- }
- switch cmd {
- case "q":
- if err := mbox.Unlock(folder); err != nil {
- fmt.Fprintln(os.Stderr, err)
- os.Exit(1)
- }
- return
- case "a":
- // archive a msg, move it to archive/<from address>
- 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) {
-}
M command.go +5 -7
@@ 1,4 1,4 @@
-package mailbox
+package main
import (
"bytes"
@@ 13,13 13,11 @@ import (
"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 @@ func walk(mbox *mh.Mailbox, folder strin
}
// 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 @@ func Move(mbox *mh.Mailbox, srcFolder st
}
// 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 @@ func Save(msg *mail.Message, number int)
// 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)
A => command_test.go +20 -0
@@ 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)
+
+}
M config.go +1 -1
@@ 1,4 1,4 @@
-package mailbox
+package main
// The folder types for different outputs in list view.
//
M decode.go +4 -2
@@ 1,4 1,4 @@
-package mailbox
+package main
import (
"io"
@@ 35,10 35,12 @@ func ReadMessage(r io.Reader) (*Message,
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
}
M edit.go +7 -9
@@ 1,4 1,4 @@
-package mailbox
+package main
import (
"bufio"
@@ 22,8 22,6 @@ import (
"time"
"golang.org/x/text/transform"
-
- "bitbucket.org/telesto/mailbox/mh"
)
type quoteMessage struct {
@@ 56,28 54,28 @@ func (qm *quoteMessage) Transform(dst, s
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 @@ func boundary(hdr textproto.MIMEHeader)
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 {
M edit_test.go +1 -1
@@ 1,4 1,4 @@
-package mailbox
+package main
import "testing"
M list.go +3 -5
@@ 1,4 1,4 @@
-package mailbox
+package main
import (
"fmt"
@@ 6,12 6,10 @@ import (
"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 @@ func List(mbox *mh.Mailbox, out io.Write
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()
M list_test.go +2 -4
@@ 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 @@ func BenchmarkList(b *testing.B) {
if err != nil {
panic(err)
}
- mbox := mh.NewMailbox()
+ mbox := NewMailbox()
msgs := NewAlwaysMatcher()
b.StartTimer()
for i := 0; i < b.N; i++ {
A => ls.go +8 -0
@@ 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.
+ `}
A => mailbox_test.go +58 -0
@@ 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...))
+ }
+}
M cmd/mb/main.go => main.go +29 -32
@@ 8,16 8,13 @@ import (
"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 @@ func main() {
}
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 @@ func main() {
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 @@ func main() {
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 @@ func main() {
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 @@ func main() {
}
// 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 @@ func main() {
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 @@ func main() {
// 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 @@ func main() {
// }
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 @@ func main() {
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 @@ func main() {
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 @@ func main() {
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 @@ func main() {
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 @@ func main() {
src.Close()
break
}
- if err = mb.Send(msg); err != nil {
+ if err = Send(msg); err != nil {
src.Close()
break
}
@@ 317,12 314,12 @@ func main() {
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 @@ func main() {
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 main() {
}
}
-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 @@ func openMsg(mbox *mh.Mailbox, msg strin
// 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 @@ func parseSequence(mbox *mh.Mailbox, val
// 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
}
M mh/mh.go => mh.go +6 -18
@@ 1,4 1,4 @@
-package mh
+package main
// todo: move this to a mh subpackage?
// todo: locking
@@ 16,17 16,6 @@ import (
// 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 @@ func (mh *Mailbox) nextId(folder string)
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 @@ func (mbox Mailbox) ReadFolder(folder st
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 @@ func errMail(id int64, err error) Messag
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 {
M parse.go +1 -1
@@ 1,4 1,4 @@
-package mailbox
+package main
import (
"encoding/base64"
M reply_test.go +1 -1
@@ 1,4 1,4 @@
-package mailbox
+package main
import (
"bytes"
M send.go +1 -1
@@ 1,4 1,4 @@
-package mailbox
+package main
import (
"fmt"
M sequence.go +3 -5
@@ 1,4 1,4 @@
-package mailbox
+package main
import (
"errors"
@@ 7,8 7,6 @@ import (
"strconv"
"strings"
"unicode/utf8"
-
- "bitbucket.org/telesto/mailbox/mh"
)
type Sequence struct {
@@ 72,8 70,8 @@ type Regexp struct {
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
}
M sequence_test.go +1 -1
@@ 1,4 1,4 @@
-package mailbox
+package main
import "testing"
M mh/setup_template.go => setup_template.go +1 -1
@@ 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).