From c2adb4d632a37d612f1c19e9886cdfae6cedb2b1 Mon Sep 17 00:00:00 2001 From: Andrey Petrov Date: Tue, 20 Jan 2015 15:57:01 -0800 Subject: [PATCH] ssh-chat/chat/{message,user,theme,history} -> ssh-chat/chat/message --- chat/command.go | 44 +++++++------- chat/help.go | 4 +- chat/{ => message}/history.go | 2 +- chat/{ => message}/history_test.go | 2 +- chat/message/identity.go | 26 ++++++++ chat/message/logger.go | 22 +++++++ chat/{ => message}/message.go | 7 ++- chat/{ => message}/message_test.go | 16 +---- chat/{ => message}/screen_test.go | 2 +- chat/{ => message}/theme.go | 2 +- chat/{ => message}/theme_test.go | 2 +- chat/{ => message}/user.go | 13 +--- chat/{ => message}/user_test.go | 2 +- chat/room.go | 50 ++++++++-------- chat/room_test.go | 96 ++++++++++++++++++------------ chat/set.go | 33 +++++----- chat/set_test.go | 10 +++- cmd.go | 3 +- host.go | 59 +++++++++--------- host_test.go | 6 +- identity.go | 5 +- 21 files changed, 237 insertions(+), 169 deletions(-) rename chat/{ => message}/history.go (98%) rename chat/{ => message}/history_test.go (98%) create mode 100644 chat/message/identity.go create mode 100644 chat/message/logger.go rename chat/{ => message}/message.go (98%) rename chat/{ => message}/message_test.go (81%) rename chat/{ => message}/screen_test.go (98%) rename chat/{ => message}/theme.go (99%) rename chat/{ => message}/theme_test.go (98%) rename chat/{ => message}/user.go (93%) rename chat/{ => message}/user_test.go (96%) diff --git a/chat/command.go b/chat/command.go index eb085c9..76e4b21 100644 --- a/chat/command.go +++ b/chat/command.go @@ -6,6 +6,8 @@ import ( "errors" "fmt" "strings" + + "github.com/shazow/ssh-chat/chat/message" ) // The error returned when an invalid command is issued. @@ -29,7 +31,7 @@ type Command struct { PrefixHelp string // If omitted, command is hidden from /help Help string - Handler func(*Room, CommandMsg) error + Handler func(*Room, message.CommandMsg) error // Command requires Op permissions Op bool } @@ -59,7 +61,7 @@ func (c Commands) Alias(command string, alias string) error { } // Run executes a command message. -func (c Commands) Run(room *Room, msg CommandMsg) error { +func (c Commands) Run(room *Room, msg message.CommandMsg) error { if msg.From == nil { return ErrNoOwner } @@ -84,9 +86,9 @@ func (c Commands) Help(showOp bool) string { normal = append(normal, cmd) } } - help := "Available commands:" + Newline + NewCommandsHelp(normal).String() + help := "Available commands:" + message.Newline + NewCommandsHelp(normal).String() if showOp { - help += Newline + "-> Operator commands:" + Newline + NewCommandsHelp(op).String() + help += message.Newline + "-> Operator commands:" + message.Newline + NewCommandsHelp(op).String() } return help } @@ -102,24 +104,24 @@ func init() { func InitCommands(c *Commands) { c.Add(Command{ Prefix: "/help", - Handler: func(room *Room, msg CommandMsg) error { + Handler: func(room *Room, msg message.CommandMsg) error { op := room.IsOp(msg.From()) - room.Send(NewSystemMsg(room.commands.Help(op), msg.From())) + room.Send(message.NewSystemMsg(room.commands.Help(op), msg.From())) return nil }, }) c.Add(Command{ Prefix: "/me", - Handler: func(room *Room, msg CommandMsg) error { - me := strings.TrimLeft(msg.body, "/me") + Handler: func(room *Room, msg message.CommandMsg) error { + me := strings.TrimLeft(msg.Body(), "/me") if me == "" { me = "is at a loss for words." } else { me = me[1:] } - room.Send(NewEmoteMsg(me, msg.From())) + room.Send(message.NewEmoteMsg(me, msg.From())) return nil }, }) @@ -127,7 +129,7 @@ func InitCommands(c *Commands) { c.Add(Command{ Prefix: "/exit", Help: "Exit the chat.", - Handler: func(room *Room, msg CommandMsg) error { + Handler: func(room *Room, msg message.CommandMsg) error { msg.From().Close() return nil }, @@ -138,7 +140,7 @@ func InitCommands(c *Commands) { Prefix: "/nick", PrefixHelp: "NAME", Help: "Rename yourself.", - Handler: func(room *Room, msg CommandMsg) error { + Handler: func(room *Room, msg message.CommandMsg) error { args := msg.Args() if len(args) != 1 { return ErrMissingArg @@ -164,11 +166,11 @@ func InitCommands(c *Commands) { c.Add(Command{ Prefix: "/names", Help: "List users who are connected.", - Handler: func(room *Room, msg CommandMsg) error { + Handler: func(room *Room, msg message.CommandMsg) error { // TODO: colorize names := room.NamesPrefix("") body := fmt.Sprintf("%d connected: %s", len(names), strings.Join(names, ", ")) - room.Send(NewSystemMsg(body, msg.From())) + room.Send(message.NewSystemMsg(body, msg.From())) return nil }, }) @@ -178,7 +180,7 @@ func InitCommands(c *Commands) { Prefix: "/theme", PrefixHelp: "[mono|colors]", Help: "Set your color theme.", - Handler: func(room *Room, msg CommandMsg) error { + Handler: func(room *Room, msg message.CommandMsg) error { user := msg.From() args := msg.Args() if len(args) == 0 { @@ -187,16 +189,16 @@ func InitCommands(c *Commands) { theme = user.Config.Theme.Id() } body := fmt.Sprintf("Current theme: %s", theme) - room.Send(NewSystemMsg(body, user)) + room.Send(message.NewSystemMsg(body, user)) return nil } id := args[0] - for _, t := range Themes { + for _, t := range message.Themes { if t.Id() == id { user.Config.Theme = &t body := fmt.Sprintf("Set theme: %s", id) - room.Send(NewSystemMsg(body, user)) + room.Send(message.NewSystemMsg(body, user)) return nil } } @@ -207,7 +209,7 @@ func InitCommands(c *Commands) { c.Add(Command{ Prefix: "/quiet", Help: "Silence room announcements.", - Handler: func(room *Room, msg CommandMsg) error { + Handler: func(room *Room, msg message.CommandMsg) error { u := msg.From() u.ToggleQuietMode() @@ -217,7 +219,7 @@ func InitCommands(c *Commands) { } else { body = "Quiet mode is toggled OFF" } - room.Send(NewSystemMsg(body, u)) + room.Send(message.NewSystemMsg(body, u)) return nil }, }) @@ -225,7 +227,7 @@ func InitCommands(c *Commands) { c.Add(Command{ Prefix: "/slap", PrefixHelp: "NAME", - Handler: func(room *Room, msg CommandMsg) error { + Handler: func(room *Room, msg message.CommandMsg) error { var me string args := msg.Args() if len(args) == 0 { @@ -234,7 +236,7 @@ func InitCommands(c *Commands) { me = fmt.Sprintf("slaps %s around a bit with a large trout.", strings.Join(args, " ")) } - room.Send(NewEmoteMsg(me, msg.From())) + room.Send(message.NewEmoteMsg(me, msg.From())) return nil }, }) diff --git a/chat/help.go b/chat/help.go index 0ab62c6..33fda21 100644 --- a/chat/help.go +++ b/chat/help.go @@ -4,6 +4,8 @@ import ( "fmt" "sort" "strings" + + "github.com/shazow/ssh-chat/chat/message" ) type helpItem struct { @@ -54,5 +56,5 @@ func (h help) String() string { } sort.Strings(r) - return strings.Join(r, Newline) + return strings.Join(r, message.Newline) } diff --git a/chat/history.go b/chat/message/history.go similarity index 98% rename from chat/history.go rename to chat/message/history.go index 6b999ca..6acc84c 100644 --- a/chat/history.go +++ b/chat/message/history.go @@ -1,4 +1,4 @@ -package chat +package message import ( "fmt" diff --git a/chat/history_test.go b/chat/message/history_test.go similarity index 98% rename from chat/history_test.go rename to chat/message/history_test.go index de767ec..4742bb1 100644 --- a/chat/history_test.go +++ b/chat/message/history_test.go @@ -1,4 +1,4 @@ -package chat +package message import "testing" diff --git a/chat/message/identity.go b/chat/message/identity.go new file mode 100644 index 0000000..4827b31 --- /dev/null +++ b/chat/message/identity.go @@ -0,0 +1,26 @@ +package message + +// Identifier is an interface that can uniquely identify itself. +type Identifier interface { + Id() string + SetId(string) + Name() string +} + +// SimpleId is a simple Identifier implementation used for testing. +type SimpleId string + +// Id returns the Id as a string. +func (i SimpleId) Id() string { + return string(i) +} + +// SetId is a no-op +func (i SimpleId) SetId(s string) { + // no-op +} + +// Name returns the Id +func (i SimpleId) Name() string { + return i.Id() +} diff --git a/chat/message/logger.go b/chat/message/logger.go new file mode 100644 index 0000000..66e8ab4 --- /dev/null +++ b/chat/message/logger.go @@ -0,0 +1,22 @@ +package message + +import "io" +import stdlog "log" + +var logger *stdlog.Logger + +func SetLogger(w io.Writer) { + flags := stdlog.Flags() + prefix := "[chat/message] " + logger = stdlog.New(w, prefix, flags) +} + +type nullWriter struct{} + +func (nullWriter) Write(data []byte) (int, error) { + return len(data), nil +} + +func init() { + SetLogger(nullWriter{}) +} diff --git a/chat/message.go b/chat/message/message.go similarity index 98% rename from chat/message.go rename to chat/message/message.go index 9a0b30a..491e8b6 100644 --- a/chat/message.go +++ b/chat/message/message.go @@ -1,4 +1,4 @@ -package chat +package message import ( "fmt" @@ -245,7 +245,6 @@ type CommandMsg struct { *PublicMsg command string args []string - room *Room } func (m *CommandMsg) Command() string { @@ -255,3 +254,7 @@ func (m *CommandMsg) Command() string { func (m *CommandMsg) Args() []string { return m.args } + +func (m *CommandMsg) Body() string { + return m.body +} diff --git a/chat/message_test.go b/chat/message/message_test.go similarity index 81% rename from chat/message_test.go rename to chat/message/message_test.go index bafe014..1075cee 100644 --- a/chat/message_test.go +++ b/chat/message/message_test.go @@ -1,19 +1,7 @@ -package chat +package message import "testing" -type testId string - -func (i testId) Id() string { - return string(i) -} -func (i testId) SetId(s string) { - // no-op -} -func (i testId) Name() string { - return i.Id() -} - func TestMessage(t *testing.T) { var expected, actual string @@ -23,7 +11,7 @@ func TestMessage(t *testing.T) { t.Errorf("Got: `%s`; Expected: `%s`", actual, expected) } - u := NewUser(testId("foo")) + u := NewUser(SimpleId("foo")) expected = "foo: hello" actual = NewPublicMsg("hello", u).String() if actual != expected { diff --git a/chat/screen_test.go b/chat/message/screen_test.go similarity index 98% rename from chat/screen_test.go rename to chat/message/screen_test.go index c530f94..3348adc 100644 --- a/chat/screen_test.go +++ b/chat/message/screen_test.go @@ -1,4 +1,4 @@ -package chat +package message import ( "reflect" diff --git a/chat/theme.go b/chat/message/theme.go similarity index 99% rename from chat/theme.go rename to chat/message/theme.go index 27085c0..8250b59 100644 --- a/chat/theme.go +++ b/chat/message/theme.go @@ -1,4 +1,4 @@ -package chat +package message import "fmt" diff --git a/chat/theme_test.go b/chat/message/theme_test.go similarity index 98% rename from chat/theme_test.go rename to chat/message/theme_test.go index 28b9d43..da0cd0a 100644 --- a/chat/theme_test.go +++ b/chat/message/theme_test.go @@ -1,4 +1,4 @@ -package chat +package message import ( "fmt" diff --git a/chat/user.go b/chat/message/user.go similarity index 93% rename from chat/user.go rename to chat/message/user.go index 4a31816..73d384e 100644 --- a/chat/user.go +++ b/chat/message/user.go @@ -1,4 +1,4 @@ -package chat +package message import ( "errors" @@ -15,13 +15,6 @@ const reHighlight = `\b(%s)\b` var ErrUserClosed = errors.New("user closed") -// Identifier is an interface that can uniquely identify itself. -type Identifier interface { - Id() string - SetId(string) - Name() string -} - // User definition, implemented set Item interface and io.Writer type User struct { Identifier @@ -106,8 +99,8 @@ func (u *User) Consume(out io.Writer) { } // Consume one message and stop, mostly for testing -func (u *User) ConsumeOne(out io.Writer) { - u.HandleMsg(<-u.msg, out) +func (u *User) ConsumeChan() <-chan Message { + return u.msg } // SetHighlight sets the highlighting regular expression to match string. diff --git a/chat/user_test.go b/chat/message/user_test.go similarity index 96% rename from chat/user_test.go rename to chat/message/user_test.go index 37ecc29..6e5caeb 100644 --- a/chat/user_test.go +++ b/chat/message/user_test.go @@ -1,4 +1,4 @@ -package chat +package message import ( "reflect" diff --git a/chat/room.go b/chat/room.go index 7d1b3af..11f0bb1 100644 --- a/chat/room.go +++ b/chat/room.go @@ -5,6 +5,8 @@ import ( "fmt" "io" "sync" + + "github.com/shazow/ssh-chat/chat/message" ) const historyLen = 20 @@ -20,16 +22,16 @@ var ErrInvalidName = errors.New("invalid name") // Member is a User with per-Room metadata attached to it. type Member struct { - *User + *message.User Op bool } // Room definition, also a Set of User Items type Room struct { topic string - history *History + history *message.History members *Set - broadcast chan Message + broadcast chan message.Message commands Commands closed bool closeOnce sync.Once @@ -37,11 +39,11 @@ type Room struct { // NewRoom creates a new room. func NewRoom() *Room { - broadcast := make(chan Message, roomBuffer) + broadcast := make(chan message.Message, roomBuffer) return &Room{ broadcast: broadcast, - history: NewHistory(historyLen), + history: message.NewHistory(historyLen), members: NewSet(), commands: *defaultCommands, } @@ -56,7 +58,7 @@ func (r *Room) SetCommands(commands Commands) { func (r *Room) Close() { r.closeOnce.Do(func() { r.closed = true - r.members.Each(func(m Identifier) { + r.members.Each(func(m Item) { m.(*Member).Close() }) r.members.Clear() @@ -70,33 +72,33 @@ func (r *Room) SetLogging(out io.Writer) { } // HandleMsg reacts to a message, will block until done. -func (r *Room) HandleMsg(m Message) { +func (r *Room) HandleMsg(m message.Message) { switch m := m.(type) { - case *CommandMsg: + case *message.CommandMsg: cmd := *m err := r.commands.Run(r, cmd) if err != nil { - m := NewSystemMsg(fmt.Sprintf("Err: %s", err), cmd.from) + m := message.NewSystemMsg(fmt.Sprintf("Err: %s", err), cmd.From()) go r.HandleMsg(m) } - case MessageTo: + case message.MessageTo: user := m.To() user.Send(m) default: - fromMsg, skip := m.(MessageFrom) - var skipUser *User + fromMsg, skip := m.(message.MessageFrom) + var skipUser *message.User if skip { skipUser = fromMsg.From() } r.history.Add(m) - r.members.Each(func(u Identifier) { + r.members.Each(func(u Item) { user := u.(*Member).User if skip && skipUser == user { // Skip return } - if _, ok := m.(*AnnounceMsg); ok { + if _, ok := m.(*message.AnnounceMsg); ok { if user.Config.Quiet { // Skip return @@ -116,19 +118,19 @@ func (r *Room) Serve() { } // Send message, buffered by a chan. -func (r *Room) Send(m Message) { +func (r *Room) Send(m message.Message) { r.broadcast <- m } // History feeds the room's recent message history to the user's handler. -func (r *Room) History(u *User) { +func (r *Room) History(u *message.User) { for _, m := range r.history.Get(historyLen) { u.Send(m) } } // Join the room as a user, will announce. -func (r *Room) Join(u *User) (*Member, error) { +func (r *Room) Join(u *message.User) (*Member, error) { if r.closed { return nil, ErrRoomClosed } @@ -142,23 +144,23 @@ func (r *Room) Join(u *User) (*Member, error) { } r.History(u) s := fmt.Sprintf("%s joined. (Connected: %d)", u.Name(), r.members.Len()) - r.Send(NewAnnounceMsg(s)) + r.Send(message.NewAnnounceMsg(s)) return &member, nil } // Leave the room as a user, will announce. Mostly used during setup. -func (r *Room) Leave(u *User) error { +func (r *Room) Leave(u message.Identifier) error { err := r.members.Remove(u) if err != nil { return err } s := fmt.Sprintf("%s left.", u.Name()) - r.Send(NewAnnounceMsg(s)) + r.Send(message.NewAnnounceMsg(s)) return nil } // Rename member with a new identity. This will not call rename on the member. -func (r *Room) Rename(oldId string, identity Identifier) error { +func (r *Room) Rename(oldId string, identity message.Identifier) error { if identity.Id() == "" { return ErrInvalidName } @@ -168,13 +170,13 @@ func (r *Room) Rename(oldId string, identity Identifier) error { } s := fmt.Sprintf("%s is now known as %s.", oldId, identity.Id()) - r.Send(NewAnnounceMsg(s)) + r.Send(message.NewAnnounceMsg(s)) return nil } // Member returns a corresponding Member object to a User if the Member is // present in this room. -func (r *Room) Member(u *User) (*Member, bool) { +func (r *Room) Member(u *message.User) (*Member, bool) { m, ok := r.MemberById(u.Id()) if !ok { return nil, false @@ -195,7 +197,7 @@ func (r *Room) MemberById(id string) (*Member, bool) { } // IsOp returns whether a user is an operator in this room. -func (r *Room) IsOp(u *User) bool { +func (r *Room) IsOp(u *message.User) bool { m, ok := r.Member(u) return ok && m.Op } diff --git a/chat/room_test.go b/chat/room_test.go index e415a0c..05fbf02 100644 --- a/chat/room_test.go +++ b/chat/room_test.go @@ -3,11 +3,29 @@ package chat import ( "reflect" "testing" + + "github.com/shazow/ssh-chat/chat/message" ) +// Used for testing +type MockScreen struct { + buffer []byte +} + +func (s *MockScreen) Write(data []byte) (n int, err error) { + s.buffer = append(s.buffer, data...) + return len(data), nil +} + +func (s *MockScreen) Read(p *[]byte) (n int, err error) { + *p = s.buffer + s.buffer = []byte{} + return len(*p), nil +} + func TestRoomServe(t *testing.T) { ch := NewRoom() - ch.Send(NewAnnounceMsg("hello")) + ch.Send(message.NewAnnounceMsg("hello")) received := <-ch.broadcast actual := received.String() @@ -22,7 +40,7 @@ func TestRoomJoin(t *testing.T) { var expected, actual []byte s := &MockScreen{} - u := NewUser(testId("foo")) + u := message.NewUser(message.SimpleId("foo")) ch := NewRoom() go ch.Serve() @@ -33,24 +51,24 @@ func TestRoomJoin(t *testing.T) { t.Fatal(err) } - u.ConsumeOne(s) - expected = []byte(" * foo joined. (Connected: 1)" + Newline) + u.HandleMsg(<-u.ConsumeChan(), s) + expected = []byte(" * foo joined. (Connected: 1)" + message.Newline) s.Read(&actual) if !reflect.DeepEqual(actual, expected) { t.Errorf("Got: `%s`; Expected: `%s`", actual, expected) } - ch.Send(NewSystemMsg("hello", u)) - u.ConsumeOne(s) - expected = []byte("-> hello" + Newline) + ch.Send(message.NewSystemMsg("hello", u)) + u.HandleMsg(<-u.ConsumeChan(), s) + expected = []byte("-> hello" + message.Newline) s.Read(&actual) if !reflect.DeepEqual(actual, expected) { t.Errorf("Got: `%s`; Expected: `%s`", actual, expected) } - ch.Send(ParseInput("/me says hello.", u)) - u.ConsumeOne(s) - expected = []byte("** foo says hello." + Newline) + ch.Send(message.ParseInput("/me says hello.", u)) + u.HandleMsg(<-u.ConsumeChan(), s) + expected = []byte("** foo says hello." + message.Newline) s.Read(&actual) if !reflect.DeepEqual(actual, expected) { t.Errorf("Got: `%s`; Expected: `%s`", actual, expected) @@ -58,8 +76,8 @@ func TestRoomJoin(t *testing.T) { } func TestRoomDoesntBroadcastAnnounceMessagesWhenQuiet(t *testing.T) { - u := NewUser(testId("foo")) - u.Config = UserConfig{ + u := message.NewUser(message.SimpleId("foo")) + u.Config = message.UserConfig{ Quiet: true, } @@ -75,8 +93,8 @@ func TestRoomDoesntBroadcastAnnounceMessagesWhenQuiet(t *testing.T) { <-ch.broadcast go func() { - for msg := range u.msg { - if _, ok := msg.(*AnnounceMsg); ok { + for msg := range u.ConsumeChan() { + if _, ok := msg.(*message.AnnounceMsg); ok { t.Errorf("Got unexpected `%T`", msg) } } @@ -84,17 +102,17 @@ func TestRoomDoesntBroadcastAnnounceMessagesWhenQuiet(t *testing.T) { // Call with an AnnounceMsg and all the other types // and assert we received only non-announce messages - ch.HandleMsg(NewAnnounceMsg("Ignored")) + ch.HandleMsg(message.NewAnnounceMsg("Ignored")) // Assert we still get all other types of messages - ch.HandleMsg(NewEmoteMsg("hello", u)) - ch.HandleMsg(NewSystemMsg("hello", u)) - ch.HandleMsg(NewPrivateMsg("hello", u, u)) - ch.HandleMsg(NewPublicMsg("hello", u)) + ch.HandleMsg(message.NewEmoteMsg("hello", u)) + ch.HandleMsg(message.NewSystemMsg("hello", u)) + ch.HandleMsg(message.NewPrivateMsg("hello", u, u)) + ch.HandleMsg(message.NewPublicMsg("hello", u)) } func TestRoomQuietToggleBroadcasts(t *testing.T) { - u := NewUser(testId("foo")) - u.Config = UserConfig{ + u := message.NewUser(message.SimpleId("foo")) + u.Config = message.UserConfig{ Quiet: true, } @@ -111,19 +129,19 @@ func TestRoomQuietToggleBroadcasts(t *testing.T) { u.ToggleQuietMode() - expectedMsg := NewAnnounceMsg("Ignored") + expectedMsg := message.NewAnnounceMsg("Ignored") ch.HandleMsg(expectedMsg) - msg := <-u.msg - if _, ok := msg.(*AnnounceMsg); !ok { + msg := <-u.ConsumeChan() + if _, ok := msg.(*message.AnnounceMsg); !ok { t.Errorf("Got: `%T`; Expected: `%T`", msg, expectedMsg) } u.ToggleQuietMode() - ch.HandleMsg(NewAnnounceMsg("Ignored")) - ch.HandleMsg(NewSystemMsg("hello", u)) - msg = <-u.msg - if _, ok := msg.(*AnnounceMsg); ok { + ch.HandleMsg(message.NewAnnounceMsg("Ignored")) + ch.HandleMsg(message.NewSystemMsg("hello", u)) + msg = <-u.ConsumeChan() + if _, ok := msg.(*message.AnnounceMsg); ok { t.Errorf("Got unexpected `%T`", msg) } } @@ -132,7 +150,7 @@ func TestQuietToggleDisplayState(t *testing.T) { var expected, actual []byte s := &MockScreen{} - u := NewUser(testId("foo")) + u := message.NewUser(message.SimpleId("foo")) ch := NewRoom() go ch.Serve() @@ -146,17 +164,17 @@ func TestQuietToggleDisplayState(t *testing.T) { // Drain the initial Join message <-ch.broadcast - ch.Send(ParseInput("/quiet", u)) - u.ConsumeOne(s) - expected = []byte("-> Quiet mode is toggled ON" + Newline) + ch.Send(message.ParseInput("/quiet", u)) + u.HandleMsg(<-u.ConsumeChan(), s) + expected = []byte("-> Quiet mode is toggled ON" + message.Newline) s.Read(&actual) if !reflect.DeepEqual(actual, expected) { t.Errorf("Got: `%s`; Expected: `%s`", actual, expected) } - ch.Send(ParseInput("/quiet", u)) - u.ConsumeOne(s) - expected = []byte("-> Quiet mode is toggled OFF" + Newline) + ch.Send(message.ParseInput("/quiet", u)) + u.HandleMsg(<-u.ConsumeChan(), s) + expected = []byte("-> Quiet mode is toggled OFF" + message.Newline) s.Read(&actual) if !reflect.DeepEqual(actual, expected) { @@ -168,7 +186,7 @@ func TestRoomNames(t *testing.T) { var expected, actual []byte s := &MockScreen{} - u := NewUser(testId("foo")) + u := message.NewUser(message.SimpleId("foo")) ch := NewRoom() go ch.Serve() @@ -182,9 +200,9 @@ func TestRoomNames(t *testing.T) { // Drain the initial Join message <-ch.broadcast - ch.Send(ParseInput("/names", u)) - u.ConsumeOne(s) - expected = []byte("-> 1 connected: foo" + Newline) + ch.Send(message.ParseInput("/names", u)) + u.HandleMsg(<-u.ConsumeChan(), s) + expected = []byte("-> 1 connected: foo" + message.Newline) s.Read(&actual) if !reflect.DeepEqual(actual, expected) { t.Errorf("Got: `%s`; Expected: `%s`", actual, expected) diff --git a/chat/set.go b/chat/set.go index 8617e14..ed3c09f 100644 --- a/chat/set.go +++ b/chat/set.go @@ -12,17 +12,22 @@ var ErrIdTaken = errors.New("id already taken") // The error returned when a requested item does not exist in the set. var ErrItemMissing = errors.New("item does not exist") +// Interface for an item storeable in the set +type Item interface { + Id() string +} + // Set with string lookup. // TODO: Add trie for efficient prefix lookup? type Set struct { - lookup map[string]Identifier + lookup map[string]Item sync.RWMutex } // NewSet creates a new set. func NewSet() *Set { return &Set{ - lookup: map[string]Identifier{}, + lookup: map[string]Item{}, } } @@ -30,7 +35,7 @@ func NewSet() *Set { func (s *Set) Clear() int { s.Lock() n := len(s.lookup) - s.lookup = map[string]Identifier{} + s.lookup = map[string]Item{} s.Unlock() return n } @@ -41,7 +46,7 @@ func (s *Set) Len() int { } // In checks if an item exists in this set. -func (s *Set) In(item Identifier) bool { +func (s *Set) In(item Item) bool { s.RLock() _, ok := s.lookup[item.Id()] s.RUnlock() @@ -49,7 +54,7 @@ func (s *Set) In(item Identifier) bool { } // Get returns an item with the given Id. -func (s *Set) Get(id string) (Identifier, error) { +func (s *Set) Get(id string) (Item, error) { s.RLock() item, ok := s.lookup[id] s.RUnlock() @@ -62,7 +67,7 @@ func (s *Set) Get(id string) (Identifier, error) { } // Add item to this set if it does not exist already. -func (s *Set) Add(item Identifier) error { +func (s *Set) Add(item Item) error { s.Lock() defer s.Unlock() @@ -76,7 +81,7 @@ func (s *Set) Add(item Identifier) error { } // Remove item from this set. -func (s *Set) Remove(item Identifier) error { +func (s *Set) Remove(item Item) error { s.Lock() defer s.Unlock() id := item.Id() @@ -88,9 +93,9 @@ func (s *Set) Remove(item Identifier) error { return nil } -// Replace item from old id with new Identifier. -// Used for moving the same identifier to a new Id, such as a rename. -func (s *Set) Replace(oldId string, item Identifier) error { +// Replace item from old id with new Item. +// Used for moving the same Item to a new Id, such as a rename. +func (s *Set) Replace(oldId string, item Item) error { s.Lock() defer s.Unlock() @@ -107,7 +112,7 @@ func (s *Set) Replace(oldId string, item Identifier) error { } delete(s.lookup, oldId) - // Add new identifier + // Add new Item s.lookup[item.Id()] = item return nil @@ -115,7 +120,7 @@ func (s *Set) Replace(oldId string, item Identifier) error { // Each loops over every item while holding a read lock and applies fn to each // element. -func (s *Set) Each(fn func(item Identifier)) { +func (s *Set) Each(fn func(item Item)) { s.RLock() for _, item := range s.lookup { fn(item) @@ -124,8 +129,8 @@ func (s *Set) Each(fn func(item Identifier)) { } // ListPrefix returns a list of items with a prefix, case insensitive. -func (s *Set) ListPrefix(prefix string) []Identifier { - r := []Identifier{} +func (s *Set) ListPrefix(prefix string) []Item { + r := []Item{} prefix = strings.ToLower(prefix) s.RLock() diff --git a/chat/set_test.go b/chat/set_test.go index b92bdeb..c70ef2e 100644 --- a/chat/set_test.go +++ b/chat/set_test.go @@ -1,11 +1,15 @@ package chat -import "testing" +import ( + "testing" + + "github.com/shazow/ssh-chat/chat/message" +) func TestSet(t *testing.T) { var err error s := NewSet() - u := NewUser(testId("foo")) + u := message.NewUser(message.SimpleId("foo")) if s.In(u) { t.Errorf("Set should be empty.") @@ -20,7 +24,7 @@ func TestSet(t *testing.T) { t.Errorf("Set should contain user.") } - u2 := NewUser(testId("bar")) + u2 := message.NewUser(message.SimpleId("bar")) err = s.Add(u2) if err != nil { t.Error(err) diff --git a/cmd.go b/cmd.go index e60acb9..55646fc 100644 --- a/cmd.go +++ b/cmd.go @@ -16,6 +16,7 @@ import ( "golang.org/x/crypto/ssh" "github.com/shazow/ssh-chat/chat" + "github.com/shazow/ssh-chat/chat/message" "github.com/shazow/ssh-chat/sshd" ) import _ "net/http/pprof" @@ -109,7 +110,7 @@ func main() { host := NewHost(s) host.auth = auth - host.theme = &chat.Themes[0] + host.theme = &message.Themes[0] err = fromFile(options.Admin, func(line []byte) error { key, _, _, _, err := ssh.ParseAuthorizedKey(line) diff --git a/host.go b/host.go index baa5095..aab7843 100644 --- a/host.go +++ b/host.go @@ -9,13 +9,14 @@ import ( "github.com/shazow/rateio" "github.com/shazow/ssh-chat/chat" + "github.com/shazow/ssh-chat/chat/message" "github.com/shazow/ssh-chat/sshd" ) const maxInputLength int = 1024 // GetPrompt will render the terminal prompt string based on the user. -func GetPrompt(user *chat.User) string { +func GetPrompt(user *message.User) string { name := user.Name() if user.Config.Theme != nil { name = user.Config.Theme.ColorName(user) @@ -35,7 +36,7 @@ type Host struct { count int // Default theme - theme *chat.Theme + theme *message.Theme } // NewHost creates a Host on top of an existing listener. @@ -72,7 +73,7 @@ func (h Host) isOp(conn sshd.Connection) bool { // Connect a specific Terminal to this host and its room. func (h *Host) Connect(term *sshd.Terminal) { id := NewIdentity(term.Conn) - user := chat.NewUserScreen(id, term) + user := message.NewUserScreen(id, term) user.Config.Theme = h.theme go func() { // Close term once user is closed. @@ -83,7 +84,7 @@ func (h *Host) Connect(term *sshd.Terminal) { // Send MOTD if h.motd != "" { - user.Send(chat.NewAnnounceMsg(h.motd)) + user.Send(message.NewAnnounceMsg(h.motd)) } member, err := h.Join(user) @@ -119,11 +120,11 @@ func (h *Host) Connect(term *sshd.Terminal) { err = ratelimit.Count(1) if err != nil { - user.Send(chat.NewSystemMsg("Message rejected: Rate limiting is in effect.", user)) + user.Send(message.NewSystemMsg("Message rejected: Rate limiting is in effect.", user)) continue } if len(line) > maxInputLength { - user.Send(chat.NewSystemMsg("Message rejected: Input too long.", user)) + user.Send(message.NewSystemMsg("Message rejected: Input too long.", user)) continue } if line == "" { @@ -131,7 +132,7 @@ func (h *Host) Connect(term *sshd.Terminal) { continue } - m := chat.ParseInput(line, user) + m := message.ParseInput(line, user) // FIXME: Any reason to use h.room.Send(m) instead? h.HandleMsg(m) @@ -184,7 +185,7 @@ func (h Host) completeCommand(partial string) string { } // AutoCompleteFunction returns a callback for terminal autocompletion -func (h *Host) AutoCompleteFunction(u *chat.User) func(line string, pos int, key rune) (newLine string, newPos int, ok bool) { +func (h *Host) AutoCompleteFunction(u *message.User) func(line string, pos int, key rune) (newLine string, newPos int, ok bool) { return func(line string, pos int, key rune) (newLine string, newPos int, ok bool) { if key != 9 { return @@ -231,8 +232,8 @@ func (h *Host) AutoCompleteFunction(u *chat.User) func(line string, pos int, key } } -// GetUser returns a chat.User based on a name. -func (h *Host) GetUser(name string) (*chat.User, bool) { +// GetUser returns a message.User based on a name. +func (h *Host) GetUser(name string) (*message.User, bool) { m, ok := h.MemberById(name) if !ok { return nil, false @@ -247,7 +248,7 @@ func (h *Host) InitCommands(c *chat.Commands) { Prefix: "/msg", PrefixHelp: "USER MESSAGE", Help: "Send MESSAGE to USER.", - Handler: func(room *chat.Room, msg chat.CommandMsg) error { + Handler: func(room *chat.Room, msg message.CommandMsg) error { args := msg.Args() switch len(args) { case 0: @@ -261,7 +262,7 @@ func (h *Host) InitCommands(c *chat.Commands) { return errors.New("user not found") } - m := chat.NewPrivateMsg(strings.Join(args[1:], " "), msg.From(), target) + m := message.NewPrivateMsg(strings.Join(args[1:], " "), msg.From(), target) room.Send(m) return nil }, @@ -271,7 +272,7 @@ func (h *Host) InitCommands(c *chat.Commands) { Prefix: "/reply", PrefixHelp: "MESSAGE", Help: "Reply with MESSAGE to the previous private message.", - Handler: func(room *chat.Room, msg chat.CommandMsg) error { + Handler: func(room *chat.Room, msg message.CommandMsg) error { args := msg.Args() switch len(args) { case 0: @@ -283,7 +284,7 @@ func (h *Host) InitCommands(c *chat.Commands) { return errors.New("no message to reply to") } - m := chat.NewPrivateMsg(strings.Join(args, " "), msg.From(), target) + m := message.NewPrivateMsg(strings.Join(args, " "), msg.From(), target) room.Send(m) return nil }, @@ -293,7 +294,7 @@ func (h *Host) InitCommands(c *chat.Commands) { Prefix: "/whois", PrefixHelp: "USER", Help: "Information about USER.", - Handler: func(room *chat.Room, msg chat.CommandMsg) error { + Handler: func(room *chat.Room, msg message.CommandMsg) error { args := msg.Args() if len(args) == 0 { return errors.New("must specify user") @@ -305,7 +306,7 @@ func (h *Host) InitCommands(c *chat.Commands) { } id := target.Identifier.(*Identity) - room.Send(chat.NewSystemMsg(id.Whois(), msg.From())) + room.Send(message.NewSystemMsg(id.Whois(), msg.From())) return nil }, @@ -314,8 +315,8 @@ func (h *Host) InitCommands(c *chat.Commands) { // Hidden commands c.Add(chat.Command{ Prefix: "/version", - Handler: func(room *chat.Room, msg chat.CommandMsg) error { - room.Send(chat.NewSystemMsg(buildCommit, msg.From())) + Handler: func(room *chat.Room, msg message.CommandMsg) error { + room.Send(message.NewSystemMsg(buildCommit, msg.From())) return nil }, }) @@ -323,8 +324,8 @@ func (h *Host) InitCommands(c *chat.Commands) { timeStarted := time.Now() c.Add(chat.Command{ Prefix: "/uptime", - Handler: func(room *chat.Room, msg chat.CommandMsg) error { - room.Send(chat.NewSystemMsg(time.Now().Sub(timeStarted).String(), msg.From())) + Handler: func(room *chat.Room, msg message.CommandMsg) error { + room.Send(message.NewSystemMsg(time.Now().Sub(timeStarted).String(), msg.From())) return nil }, }) @@ -335,7 +336,7 @@ func (h *Host) InitCommands(c *chat.Commands) { Prefix: "/kick", PrefixHelp: "USER", Help: "Kick USER from the server.", - Handler: func(room *chat.Room, msg chat.CommandMsg) error { + Handler: func(room *chat.Room, msg message.CommandMsg) error { if !room.IsOp(msg.From()) { return errors.New("must be op") } @@ -351,7 +352,7 @@ func (h *Host) InitCommands(c *chat.Commands) { } body := fmt.Sprintf("%s was kicked by %s.", target.Name(), msg.From().Name()) - room.Send(chat.NewAnnounceMsg(body)) + room.Send(message.NewAnnounceMsg(body)) target.Close() return nil }, @@ -362,7 +363,7 @@ func (h *Host) InitCommands(c *chat.Commands) { Prefix: "/ban", PrefixHelp: "USER [DURATION]", Help: "Ban USER from the server.", - Handler: func(room *chat.Room, msg chat.CommandMsg) error { + Handler: func(room *chat.Room, msg message.CommandMsg) error { // TODO: Would be nice to specify what to ban. Key? Ip? etc. if !room.IsOp(msg.From()) { return errors.New("must be op") @@ -388,7 +389,7 @@ func (h *Host) InitCommands(c *chat.Commands) { h.auth.BanAddr(id.RemoteAddr(), until) body := fmt.Sprintf("%s was banned by %s.", target.Name(), msg.From().Name()) - room.Send(chat.NewAnnounceMsg(body)) + room.Send(message.NewAnnounceMsg(body)) target.Close() logger.Debugf("Banned: \n-> %s", id.Whois()) @@ -402,7 +403,7 @@ func (h *Host) InitCommands(c *chat.Commands) { Prefix: "/motd", PrefixHelp: "MESSAGE", Help: "Set the MESSAGE of the day.", - Handler: func(room *chat.Room, msg chat.CommandMsg) error { + Handler: func(room *chat.Room, msg message.CommandMsg) error { if !room.IsOp(msg.From()) { return errors.New("must be op") } @@ -415,9 +416,9 @@ func (h *Host) InitCommands(c *chat.Commands) { h.motd = motd body := fmt.Sprintf("New message of the day set by %s:", msg.From().Name()) - room.Send(chat.NewAnnounceMsg(body)) + room.Send(message.NewAnnounceMsg(body)) if motd != "" { - room.Send(chat.NewAnnounceMsg(motd)) + room.Send(message.NewAnnounceMsg(motd)) } return nil @@ -429,7 +430,7 @@ func (h *Host) InitCommands(c *chat.Commands) { Prefix: "/op", PrefixHelp: "USER [DURATION]", Help: "Set USER as admin.", - Handler: func(room *chat.Room, msg chat.CommandMsg) error { + Handler: func(room *chat.Room, msg message.CommandMsg) error { if !room.IsOp(msg.From()) { return errors.New("must be op") } @@ -453,7 +454,7 @@ func (h *Host) InitCommands(c *chat.Commands) { h.auth.Op(id.PublicKey(), until) body := fmt.Sprintf("Made op by %s.", msg.From().Name()) - room.Send(chat.NewSystemMsg(body, member.User)) + room.Send(message.NewSystemMsg(body, member.User)) return nil }, diff --git a/host_test.go b/host_test.go index 76bbe6b..0288ab2 100644 --- a/host_test.go +++ b/host_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/shazow/ssh-chat/chat" + "github.com/shazow/ssh-chat/chat/message" "github.com/shazow/ssh-chat/sshd" "golang.org/x/crypto/ssh" ) @@ -26,7 +26,7 @@ func stripPrompt(s string) string { func TestHostGetPrompt(t *testing.T) { var expected, actual string - u := chat.NewUser(&Identity{nil, "foo"}) + u := message.NewUser(&Identity{nil, "foo"}) u.SetColorIdx(2) actual = GetPrompt(u) @@ -35,7 +35,7 @@ func TestHostGetPrompt(t *testing.T) { t.Errorf("Got: %q; Expected: %q", actual, expected) } - u.Config.Theme = &chat.Themes[0] + u.Config.Theme = &message.Themes[0] actual = GetPrompt(u) expected = "[\033[38;05;2mfoo\033[0m] " if actual != expected { diff --git a/identity.go b/identity.go index bfd46fa..4403d8b 100644 --- a/identity.go +++ b/identity.go @@ -5,6 +5,7 @@ import ( "net" "github.com/shazow/ssh-chat/chat" + "github.com/shazow/ssh-chat/chat/message" "github.com/shazow/ssh-chat/sshd" ) @@ -44,7 +45,7 @@ func (i Identity) Whois() string { if i.PublicKey() != nil { fingerprint = sshd.Fingerprint(i.PublicKey()) } - return fmt.Sprintf("name: %s"+chat.Newline+ - " > ip: %s"+chat.Newline+ + return fmt.Sprintf("name: %s"+message.Newline+ + " > ip: %s"+message.Newline+ " > fingerprint: %s", i.Name(), ip, fingerprint) }