From 33a76bb7f49efcf640c1b972d3425fac8dfd874c Mon Sep 17 00:00:00 2001 From: Andrey Petrov Date: Sat, 10 Sep 2016 20:36:23 -0400 Subject: [PATCH] broken: Use Member rather than message.User when possible; chat tests are broken. --- chat/command.go | 18 +++---- chat/member.go | 4 +- chat/message/message.go | 33 +++++++----- chat/message/theme.go | 4 +- chat/message/user.go | 113 ++++++++++++++++++++-------------------- chat/room.go | 7 +-- chat/room_test.go | 16 +----- 7 files changed, 94 insertions(+), 101 deletions(-) diff --git a/chat/command.go b/chat/command.go index 1d8e6fb..1b518ba 100644 --- a/chat/command.go +++ b/chat/command.go @@ -5,6 +5,7 @@ package chat import ( "errors" "fmt" + "io" "strings" "github.com/shazow/ssh-chat/chat/message" @@ -110,7 +111,7 @@ func InitCommands(c *Commands) { c.Add(Command{ Prefix: "/help", Handler: func(room *Room, msg message.CommandMsg) error { - op := room.IsOp(msg.From()) + op := room.IsOp(msg.From().(Member)) room.Send(message.NewSystemMsg(room.commands.Help(op), msg.From())) return nil }, @@ -135,8 +136,7 @@ func InitCommands(c *Commands) { Prefix: "/exit", Help: "Exit the chat.", Handler: func(room *Room, msg message.CommandMsg) error { - msg.From().Close() - return nil + return msg.From().(io.Closer).Close() }, }) c.Alias("/exit", "/quit") @@ -150,7 +150,7 @@ func InitCommands(c *Commands) { if len(args) != 1 { return ErrMissingArg } - member, ok := room.MemberByID(msg.From().ID()) + member, ok := room.MemberByID(msg.From().(Member).ID()) if !ok { return ErrMissingMember } @@ -184,7 +184,7 @@ func InitCommands(c *Commands) { PrefixHelp: "[colors|...]", Help: "Set your color theme. (More themes: solarized, mono, hacker)", Handler: func(room *Room, msg message.CommandMsg) error { - user := msg.From() + user := msg.From().(Member) args := msg.Args() cfg := user.Config() if len(args) == 0 { @@ -215,7 +215,7 @@ func InitCommands(c *Commands) { Prefix: "/quiet", Help: "Silence room announcements.", Handler: func(room *Room, msg message.CommandMsg) error { - u := msg.From() + u := msg.From().(Member) cfg := u.Config() cfg.Quiet = !cfg.Quiet u.SetConfig(cfg) @@ -253,7 +253,7 @@ func InitCommands(c *Commands) { PrefixHelp: "[USER]", Help: "Hide messages from USER, /unignore USER to stop hiding.", Handler: func(room *Room, msg message.CommandMsg) error { - from, ok := room.Member(msg.From()) + from, ok := room.Member(msg.From().(Member)) if !ok { return ErrMissingMember } @@ -278,7 +278,7 @@ func InitCommands(c *Commands) { return nil } - if id == msg.From().ID() { + if id == msg.From().(Member).ID() { return errors.New("cannot ignore self") } target, ok := room.MemberByID(id) @@ -302,7 +302,7 @@ func InitCommands(c *Commands) { Prefix: "/unignore", PrefixHelp: "USER", Handler: func(room *Room, msg message.CommandMsg) error { - from, ok := room.Member(msg.From()) + from, ok := room.Member(msg.From().(Member)) if !ok { return ErrMissingMember } diff --git a/chat/member.go b/chat/member.go index f0d3b1c..471cdd4 100644 --- a/chat/member.go +++ b/chat/member.go @@ -12,9 +12,9 @@ type roomMember struct { } type Member interface { - ID() string + message.Author - Name() string + ID() string SetName(string) Config() message.UserConfig diff --git a/chat/message/message.go b/chat/message/message.go index 5b4f76a..891e6ab 100644 --- a/chat/message/message.go +++ b/chat/message/message.go @@ -6,6 +6,11 @@ import ( "time" ) +type Author interface { + Name() string + Color() int +} + // Message is an interface for messages. type Message interface { Render(*Theme) string @@ -16,15 +21,15 @@ type Message interface { type MessageTo interface { Message - To() *User + To() Author } type MessageFrom interface { Message - From() *User + From() Author } -func ParseInput(body string, from *User) Message { +func ParseInput(body string, from Author) Message { m := NewPublicMsg(body, from) cmd, isCmd := m.ParseCommand() if isCmd { @@ -69,10 +74,10 @@ func (m Msg) Timestamp() time.Time { // PublicMsg is any message from a user sent to the room. type PublicMsg struct { Msg - from *User + from Author } -func NewPublicMsg(body string, from *User) PublicMsg { +func NewPublicMsg(body string, from Author) PublicMsg { return PublicMsg{ Msg: Msg{ body: body, @@ -82,7 +87,7 @@ func NewPublicMsg(body string, from *User) PublicMsg { } } -func (m PublicMsg) From() *User { +func (m PublicMsg) From() Author { return m.from } @@ -137,10 +142,10 @@ func (m PublicMsg) String() string { // sender to see the emote. type EmoteMsg struct { Msg - from *User + from Author } -func NewEmoteMsg(body string, from *User) *EmoteMsg { +func NewEmoteMsg(body string, from Author) *EmoteMsg { return &EmoteMsg{ Msg: Msg{ body: body, @@ -161,17 +166,17 @@ func (m EmoteMsg) String() string { // PrivateMsg is a message sent to another user, not shown to anyone else. type PrivateMsg struct { PublicMsg - to *User + to Author } -func NewPrivateMsg(body string, from *User, to *User) PrivateMsg { +func NewPrivateMsg(body string, from Author, to Author) PrivateMsg { return PrivateMsg{ PublicMsg: NewPublicMsg(body, from), to: to, } } -func (m PrivateMsg) To() *User { +func (m PrivateMsg) To() Author { return m.to } @@ -191,10 +196,10 @@ func (m PrivateMsg) String() string { // to anyone else. Usually in response to something, like /help. type SystemMsg struct { Msg - to *User + to Author } -func NewSystemMsg(body string, to *User) *SystemMsg { +func NewSystemMsg(body string, to Author) *SystemMsg { return &SystemMsg{ Msg: Msg{ body: body, @@ -215,7 +220,7 @@ func (m *SystemMsg) String() string { return fmt.Sprintf("-> %s", m.body) } -func (m *SystemMsg) To() *User { +func (m *SystemMsg) To() Author { return m.to } diff --git a/chat/message/theme.go b/chat/message/theme.go index d2585b7..7ff8a72 100644 --- a/chat/message/theme.go +++ b/chat/message/theme.go @@ -127,12 +127,12 @@ func (t Theme) ID() string { } // Colorize name string given some index -func (t Theme) ColorName(u *User) string { +func (t Theme) ColorName(u Author) string { if t.names == nil { return u.Name() } - return t.names.Get(u.colorIdx).Format(u.Name()) + return t.names.Get(u.Color()).Format(u.Name()) } // Colorize the PM string diff --git a/chat/message/user.go b/chat/message/user.go index fd656b2..7c9c2ac 100644 --- a/chat/message/user.go +++ b/chat/message/user.go @@ -16,10 +16,53 @@ const reHighlight = `\b(%s)\b` var ErrUserClosed = errors.New("user closed") +// User container that knows about writing to an IO screen. +type UserScreen struct { + *User + io.WriteCloser +} + +func (u *UserScreen) Close() error { + u.User.Close() + return u.WriteCloser.Close() +} + +// HandleMsg will render the message to the screen, blocking. +func (u *UserScreen) HandleMsg(m Message) error { + r := u.render(m) + _, err := u.Write([]byte(r)) + if err != nil { + logger.Printf("Write failed to %s, closing: %s", u.Name(), err) + u.User.Close() + u.WriteCloser.Close() + } + return err +} + +// Consume message buffer into the handler. Will block, should be called in a +// goroutine. +func (u *UserScreen) Consume() { + for { + select { + case <-u.done: + return + case m, ok := <-u.msg: + if !ok { + return + } + u.HandleMsg(m) + } + } +} + +// Consume one message and stop, mostly for testing +// TODO: Stop using it and remove it. +func (u *UserScreen) ConsumeOne() Message { + return <-u.msg +} + // User definition, implemented set Item interface and io.Writer type User struct { - io.WriteCloser - colorIdx int joined time.Time closeOnce sync.Once @@ -29,7 +72,7 @@ type User struct { mu sync.Mutex name string config UserConfig - replyTo *User // Set when user gets a /msg, for replying. + replyTo Author // Set when user gets a /msg, for replying. } func NewUser(name string) *User { @@ -45,11 +88,11 @@ func NewUser(name string) *User { return &u } -func NewUserScreen(name string, screen io.WriteCloser) *User { - u := NewUser(name) - u.WriteCloser = screen - - return u +func NewUserScreen(name string, screen io.WriteCloser) *UserScreen { + return &UserScreen{ + User: NewUser(name), + WriteCloser: screen, + } } func (u *User) Config() UserConfig { @@ -70,6 +113,10 @@ func (u *User) ID() string { return SanitizeName(u.name) } +func (u *User) Color() int { + return u.colorIdx +} + func (u *User) Name() string { u.mu.Lock() defer u.mu.Unlock() @@ -89,14 +136,14 @@ func (u *User) SetName(name string) { } // ReplyTo returns the last user that messaged this user. -func (u *User) ReplyTo() *User { +func (u *User) ReplyTo() Author { u.mu.Lock() defer u.mu.Unlock() return u.replyTo } // SetReplyTo sets the last user to message this user. -func (u *User) SetReplyTo(user *User) { +func (u *User) SetReplyTo(user Author) { // TODO: Use UserConfig.ReplyTo string u.mu.Lock() defer u.mu.Unlock() @@ -112,46 +159,11 @@ func (u *User) setColorIdx(idx int) { // Disconnect user, stop accepting messages func (u *User) Close() { u.closeOnce.Do(func() { - u.WriteCloser.Close() // close(u.msg) TODO: Close? close(u.done) }) } -// Consume message buffer into the handler. Will block, should be called in a -// goroutine. -func (u *User) Consume() { - for { - select { - case <-u.done: - return - case m, ok := <-u.msg: - if !ok { - return - } - u.HandleMsg(m) - } - } -} - -// Consume one message and stop, mostly for testing -// TODO: Stop using it and remove it. -func (u *User) ConsumeOne() Message { - return <-u.msg -} - -// Check if there are pending messages, used for testing -// TODO: Stop using it and remove it. -func (u *User) HasMessages() bool { - select { - case msg := <-u.msg: - u.msg <- msg - return true - default: - return false - } -} - // SetHighlight sets the highlighting regular expression to match string. func (u *User) SetHighlight(s string) error { re, err := regexp.Compile(fmt.Sprintf(reHighlight, s)) @@ -177,17 +189,6 @@ func (u *User) render(m Message) string { } } -// HandleMsg will render the message to the screen, blocking. -func (u *User) HandleMsg(m Message) error { - r := u.render(m) - _, err := u.Write([]byte(r)) - if err != nil { - logger.Printf("Write failed to %s, closing: %s", u.Name(), err) - u.Close() - } - return err -} - // Add message to consume by user func (u *User) Send(m Message) error { select { diff --git a/chat/room.go b/chat/room.go index e94f454..0e4e33f 100644 --- a/chat/room.go +++ b/chat/room.go @@ -78,21 +78,22 @@ func (r *Room) HandleMsg(m message.Message) { go r.HandleMsg(m) } case message.MessageTo: - user := m.To() + user := m.To().(Member) user.Send(m) default: fromMsg, skip := m.(message.MessageFrom) var skipUser Member if skip { - skipUser = fromMsg.From() + skipUser = fromMsg.From().(Member) } r.history.Add(m) r.Members.Each(func(_ string, item set.Item) (err error) { roomMember := item.Value().(*roomMember) user := roomMember.Member + from := fromMsg.From().(Member) - if fromMsg != nil && roomMember.Ignored.In(fromMsg.From().ID()) { + if fromMsg != nil && roomMember.Ignored.In(from.ID()) { // Skip because ignored return } diff --git a/chat/room_test.go b/chat/room_test.go index d2ed523..eb215c7 100644 --- a/chat/room_test.go +++ b/chat/room_test.go @@ -1,7 +1,6 @@ package chat import ( - "errors" "reflect" "testing" @@ -42,7 +41,7 @@ func TestRoomServe(t *testing.T) { } type ScreenedUser struct { - user *message.User + *message.User screen *MockScreen } @@ -159,19 +158,6 @@ func expectOutput(t *testing.T, buffer []byte, expected string) { } } -func sendCommand(cmd string, mock ScreenedUser, room *Room, buffer *[]byte) error { - msg, ok := message.NewPublicMsg(cmd, mock.user).ParseCommand() - if !ok { - return errors.New("cannot parse command message") - } - - room.Send(msg) - mock.user.HandleMsg(mock.user.ConsumeOne()) - mock.screen.Read(buffer) - - return nil -} - func TestRoomJoin(t *testing.T) { var expected, actual []byte