broken: Use Member rather than message.User when possible; chat tests are broken.

This commit is contained in:
Andrey Petrov 2016-09-10 20:36:23 -04:00
parent 9ecd2a6fa2
commit 33a76bb7f4
7 changed files with 94 additions and 101 deletions

View File

@ -5,6 +5,7 @@ package chat
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"strings" "strings"
"github.com/shazow/ssh-chat/chat/message" "github.com/shazow/ssh-chat/chat/message"
@ -110,7 +111,7 @@ func InitCommands(c *Commands) {
c.Add(Command{ c.Add(Command{
Prefix: "/help", Prefix: "/help",
Handler: func(room *Room, msg message.CommandMsg) error { 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())) room.Send(message.NewSystemMsg(room.commands.Help(op), msg.From()))
return nil return nil
}, },
@ -135,8 +136,7 @@ func InitCommands(c *Commands) {
Prefix: "/exit", Prefix: "/exit",
Help: "Exit the chat.", Help: "Exit the chat.",
Handler: func(room *Room, msg message.CommandMsg) error { Handler: func(room *Room, msg message.CommandMsg) error {
msg.From().Close() return msg.From().(io.Closer).Close()
return nil
}, },
}) })
c.Alias("/exit", "/quit") c.Alias("/exit", "/quit")
@ -150,7 +150,7 @@ func InitCommands(c *Commands) {
if len(args) != 1 { if len(args) != 1 {
return ErrMissingArg return ErrMissingArg
} }
member, ok := room.MemberByID(msg.From().ID()) member, ok := room.MemberByID(msg.From().(Member).ID())
if !ok { if !ok {
return ErrMissingMember return ErrMissingMember
} }
@ -184,7 +184,7 @@ func InitCommands(c *Commands) {
PrefixHelp: "[colors|...]", PrefixHelp: "[colors|...]",
Help: "Set your color theme. (More themes: solarized, mono, hacker)", Help: "Set your color theme. (More themes: solarized, mono, hacker)",
Handler: func(room *Room, msg message.CommandMsg) error { Handler: func(room *Room, msg message.CommandMsg) error {
user := msg.From() user := msg.From().(Member)
args := msg.Args() args := msg.Args()
cfg := user.Config() cfg := user.Config()
if len(args) == 0 { if len(args) == 0 {
@ -215,7 +215,7 @@ func InitCommands(c *Commands) {
Prefix: "/quiet", Prefix: "/quiet",
Help: "Silence room announcements.", Help: "Silence room announcements.",
Handler: func(room *Room, msg message.CommandMsg) error { Handler: func(room *Room, msg message.CommandMsg) error {
u := msg.From() u := msg.From().(Member)
cfg := u.Config() cfg := u.Config()
cfg.Quiet = !cfg.Quiet cfg.Quiet = !cfg.Quiet
u.SetConfig(cfg) u.SetConfig(cfg)
@ -253,7 +253,7 @@ func InitCommands(c *Commands) {
PrefixHelp: "[USER]", PrefixHelp: "[USER]",
Help: "Hide messages from USER, /unignore USER to stop hiding.", Help: "Hide messages from USER, /unignore USER to stop hiding.",
Handler: func(room *Room, msg message.CommandMsg) error { Handler: func(room *Room, msg message.CommandMsg) error {
from, ok := room.Member(msg.From()) from, ok := room.Member(msg.From().(Member))
if !ok { if !ok {
return ErrMissingMember return ErrMissingMember
} }
@ -278,7 +278,7 @@ func InitCommands(c *Commands) {
return nil return nil
} }
if id == msg.From().ID() { if id == msg.From().(Member).ID() {
return errors.New("cannot ignore self") return errors.New("cannot ignore self")
} }
target, ok := room.MemberByID(id) target, ok := room.MemberByID(id)
@ -302,7 +302,7 @@ func InitCommands(c *Commands) {
Prefix: "/unignore", Prefix: "/unignore",
PrefixHelp: "USER", PrefixHelp: "USER",
Handler: func(room *Room, msg message.CommandMsg) error { Handler: func(room *Room, msg message.CommandMsg) error {
from, ok := room.Member(msg.From()) from, ok := room.Member(msg.From().(Member))
if !ok { if !ok {
return ErrMissingMember return ErrMissingMember
} }

View File

@ -12,9 +12,9 @@ type roomMember struct {
} }
type Member interface { type Member interface {
ID() string message.Author
Name() string ID() string
SetName(string) SetName(string)
Config() message.UserConfig Config() message.UserConfig

View File

@ -6,6 +6,11 @@ import (
"time" "time"
) )
type Author interface {
Name() string
Color() int
}
// Message is an interface for messages. // Message is an interface for messages.
type Message interface { type Message interface {
Render(*Theme) string Render(*Theme) string
@ -16,15 +21,15 @@ type Message interface {
type MessageTo interface { type MessageTo interface {
Message Message
To() *User To() Author
} }
type MessageFrom interface { type MessageFrom interface {
Message Message
From() *User From() Author
} }
func ParseInput(body string, from *User) Message { func ParseInput(body string, from Author) Message {
m := NewPublicMsg(body, from) m := NewPublicMsg(body, from)
cmd, isCmd := m.ParseCommand() cmd, isCmd := m.ParseCommand()
if isCmd { if isCmd {
@ -69,10 +74,10 @@ func (m Msg) Timestamp() time.Time {
// PublicMsg is any message from a user sent to the room. // PublicMsg is any message from a user sent to the room.
type PublicMsg struct { type PublicMsg struct {
Msg Msg
from *User from Author
} }
func NewPublicMsg(body string, from *User) PublicMsg { func NewPublicMsg(body string, from Author) PublicMsg {
return PublicMsg{ return PublicMsg{
Msg: Msg{ Msg: Msg{
body: body, 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 return m.from
} }
@ -137,10 +142,10 @@ func (m PublicMsg) String() string {
// sender to see the emote. // sender to see the emote.
type EmoteMsg struct { type EmoteMsg struct {
Msg Msg
from *User from Author
} }
func NewEmoteMsg(body string, from *User) *EmoteMsg { func NewEmoteMsg(body string, from Author) *EmoteMsg {
return &EmoteMsg{ return &EmoteMsg{
Msg: Msg{ Msg: Msg{
body: body, body: body,
@ -161,17 +166,17 @@ func (m EmoteMsg) String() string {
// PrivateMsg is a message sent to another user, not shown to anyone else. // PrivateMsg is a message sent to another user, not shown to anyone else.
type PrivateMsg struct { type PrivateMsg struct {
PublicMsg 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{ return PrivateMsg{
PublicMsg: NewPublicMsg(body, from), PublicMsg: NewPublicMsg(body, from),
to: to, to: to,
} }
} }
func (m PrivateMsg) To() *User { func (m PrivateMsg) To() Author {
return m.to return m.to
} }
@ -191,10 +196,10 @@ func (m PrivateMsg) String() string {
// to anyone else. Usually in response to something, like /help. // to anyone else. Usually in response to something, like /help.
type SystemMsg struct { type SystemMsg struct {
Msg Msg
to *User to Author
} }
func NewSystemMsg(body string, to *User) *SystemMsg { func NewSystemMsg(body string, to Author) *SystemMsg {
return &SystemMsg{ return &SystemMsg{
Msg: Msg{ Msg: Msg{
body: body, body: body,
@ -215,7 +220,7 @@ func (m *SystemMsg) String() string {
return fmt.Sprintf("-> %s", m.body) return fmt.Sprintf("-> %s", m.body)
} }
func (m *SystemMsg) To() *User { func (m *SystemMsg) To() Author {
return m.to return m.to
} }

View File

@ -127,12 +127,12 @@ func (t Theme) ID() string {
} }
// Colorize name string given some index // Colorize name string given some index
func (t Theme) ColorName(u *User) string { func (t Theme) ColorName(u Author) string {
if t.names == nil { if t.names == nil {
return u.Name() 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 // Colorize the PM string

View File

@ -16,10 +16,53 @@ const reHighlight = `\b(%s)\b`
var ErrUserClosed = errors.New("user closed") 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 // User definition, implemented set Item interface and io.Writer
type User struct { type User struct {
io.WriteCloser
colorIdx int colorIdx int
joined time.Time joined time.Time
closeOnce sync.Once closeOnce sync.Once
@ -29,7 +72,7 @@ type User struct {
mu sync.Mutex mu sync.Mutex
name string name string
config UserConfig 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 { func NewUser(name string) *User {
@ -45,11 +88,11 @@ func NewUser(name string) *User {
return &u return &u
} }
func NewUserScreen(name string, screen io.WriteCloser) *User { func NewUserScreen(name string, screen io.WriteCloser) *UserScreen {
u := NewUser(name) return &UserScreen{
u.WriteCloser = screen User: NewUser(name),
WriteCloser: screen,
return u }
} }
func (u *User) Config() UserConfig { func (u *User) Config() UserConfig {
@ -70,6 +113,10 @@ func (u *User) ID() string {
return SanitizeName(u.name) return SanitizeName(u.name)
} }
func (u *User) Color() int {
return u.colorIdx
}
func (u *User) Name() string { func (u *User) Name() string {
u.mu.Lock() u.mu.Lock()
defer u.mu.Unlock() defer u.mu.Unlock()
@ -89,14 +136,14 @@ func (u *User) SetName(name string) {
} }
// ReplyTo returns the last user that messaged this user. // ReplyTo returns the last user that messaged this user.
func (u *User) ReplyTo() *User { func (u *User) ReplyTo() Author {
u.mu.Lock() u.mu.Lock()
defer u.mu.Unlock() defer u.mu.Unlock()
return u.replyTo return u.replyTo
} }
// SetReplyTo sets the last user to message this user. // 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 // TODO: Use UserConfig.ReplyTo string
u.mu.Lock() u.mu.Lock()
defer u.mu.Unlock() defer u.mu.Unlock()
@ -112,46 +159,11 @@ func (u *User) setColorIdx(idx int) {
// Disconnect user, stop accepting messages // Disconnect user, stop accepting messages
func (u *User) Close() { func (u *User) Close() {
u.closeOnce.Do(func() { u.closeOnce.Do(func() {
u.WriteCloser.Close()
// close(u.msg) TODO: Close? // close(u.msg) TODO: Close?
close(u.done) 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. // SetHighlight sets the highlighting regular expression to match string.
func (u *User) SetHighlight(s string) error { func (u *User) SetHighlight(s string) error {
re, err := regexp.Compile(fmt.Sprintf(reHighlight, s)) 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 // Add message to consume by user
func (u *User) Send(m Message) error { func (u *User) Send(m Message) error {
select { select {

View File

@ -78,21 +78,22 @@ func (r *Room) HandleMsg(m message.Message) {
go r.HandleMsg(m) go r.HandleMsg(m)
} }
case message.MessageTo: case message.MessageTo:
user := m.To() user := m.To().(Member)
user.Send(m) user.Send(m)
default: default:
fromMsg, skip := m.(message.MessageFrom) fromMsg, skip := m.(message.MessageFrom)
var skipUser Member var skipUser Member
if skip { if skip {
skipUser = fromMsg.From() skipUser = fromMsg.From().(Member)
} }
r.history.Add(m) r.history.Add(m)
r.Members.Each(func(_ string, item set.Item) (err error) { r.Members.Each(func(_ string, item set.Item) (err error) {
roomMember := item.Value().(*roomMember) roomMember := item.Value().(*roomMember)
user := roomMember.Member 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 // Skip because ignored
return return
} }

View File

@ -1,7 +1,6 @@
package chat package chat
import ( import (
"errors"
"reflect" "reflect"
"testing" "testing"
@ -42,7 +41,7 @@ func TestRoomServe(t *testing.T) {
} }
type ScreenedUser struct { type ScreenedUser struct {
user *message.User *message.User
screen *MockScreen 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) { func TestRoomJoin(t *testing.T) {
var expected, actual []byte var expected, actual []byte