Progress, most of this probably doesnt work.

This commit is contained in:
Andrey Petrov 2014-12-20 20:21:41 -08:00
parent 4c8d73b932
commit 1652511bf2
8 changed files with 206 additions and 21 deletions

View File

@ -1,37 +1,47 @@
package chat
import "fmt"
const historyLen = 20
// Channel definition, also a Set of User Items
type Channel struct {
id string
topic string
history *History
users *Set
out chan<- Message
id string
topic string
history *History
users *Set
broadcast chan<- Message
}
func NewChannel(id string, out chan<- Message) *Channel {
func NewChannel(id string, broadcast chan<- Message) *Channel {
ch := Channel{
id: id,
out: out,
history: NewHistory(historyLen),
users: NewSet(),
id: id,
broadcast: broadcast,
history: NewHistory(historyLen),
users: NewSet(),
}
return &ch
}
func (ch *Channel) Send(m Message) {
ch.out <- m
ch.broadcast <- m
}
func (ch *Channel) Join(u *User) error {
err := ch.users.Add(u)
if err != nil {
s := fmt.Sprintf("%s joined. (Connected: %d)", u.Name(), ch.users.Len())
ch.Send(*NewMessage(s))
}
return err
}
func (ch *Channel) Leave(u *User) error {
err := ch.users.Remove(u)
if err != nil {
s := fmt.Sprintf("%s left.", u.Name())
ch.Send(*NewMessage(s))
}
return err
}

27
chat/channel_test.go Normal file
View File

@ -0,0 +1,27 @@
package chat
import (
"reflect"
"testing"
)
func TestChannel(t *testing.T) {
s := &MockScreen{}
out := make(chan Message)
defer close(out)
go func() {
for msg := range out {
s.Write([]byte(msg.Render(nil)))
}
}()
u := NewUser("foo", s)
ch := NewChannel("", out)
ch.Join(u)
expected := []byte(" * foo joined. (Connected: 1)")
if !reflect.DeepEqual(s.received, expected) {
t.Errorf("Got: %s, Expected: %s", s.received, expected)
}
}

52
chat/command.go Normal file
View File

@ -0,0 +1,52 @@
package chat
import (
"errors"
"strings"
)
var ErrInvalidCommand error = errors.New("invalid command")
var ErrNoOwner error = errors.New("command without owner")
type CmdHandler func(host Host, msg Message, args []string) error
type Commands map[string]CmdHandler
// Register command
func (c Commands) Add(cmd string, handler CmdHandler) {
c[cmd] = handler
}
// Execute command message, assumes IsCommand was checked
func (c Commands) Run(host Host, msg Message) error {
if msg.from == nil {
return ErrNoOwner
}
cmd, args := msg.ParseCommand()
handler, ok := c[cmd]
if !ok {
return ErrInvalidCommand
}
return handler(host, msg, args)
}
var defaultCmdHandlers Commands
func init() {
c := Commands{}
c.Add("me", func(host Host, msg Message, args []string) error {
me := strings.TrimLeft(msg.Body, "/me")
if me == "" {
me = " is at a loss for words."
}
// XXX: Finish this.
return nil
})
defaultCmdHandlers = c
}

43
chat/host.go Normal file
View File

@ -0,0 +1,43 @@
package chat
const messageBuffer = 20
// Host knows about all the commands and channels.
type Host struct {
defaultChannel *Channel
commands Commands
done chan struct{}
}
func NewHost() *Host {
h := Host{
commands: defaultCmdHandlers,
}
h.defaultChannel = h.CreateChannel("")
return &h
}
func (h *Host) handleCommand(m Message) {
// TODO: ...
}
func (h *Host) broadcast(ch *Channel, m Message) {
// TODO: ...
}
func (h *Host) CreateChannel(id string) *Channel {
out := make(chan Message, 20)
ch := NewChannel(id, out)
go func() {
for msg := range out {
if msg.IsCommand() {
go h.handleCommand(msg)
continue
}
h.broadcast(ch, msg)
}
}()
return ch
}

View File

@ -2,6 +2,7 @@ package chat
import (
"fmt"
"strings"
"time"
)
@ -14,10 +15,9 @@ type Message struct {
themeCache *map[*Theme]string
}
func NewMessage(from *User, body string) *Message {
func NewMessage(body string) *Message {
m := Message{
Body: body,
from: from,
timestamp: time.Now(),
}
return &m
@ -37,19 +37,35 @@ func (m *Message) From(u *User) *Message {
// Render message based on the given theme
func (m *Message) Render(*Theme) string {
// TODO: Render based on theme.
// TODO: Render based on theme
// TODO: Cache based on theme
var msg string
if m.to != nil {
msg = fmt.Sprintf("[PM from %s] %s", m.to, m.Body)
if m.to != nil && m.from != nil {
msg = fmt.Sprintf("[PM from %s] %s", m.from, m.Body)
} else if m.from != nil {
msg = fmt.Sprintf("%s: %s", m.from, m.Body)
} else if m.to != nil {
msg = fmt.Sprintf("-> %s", m.Body)
} else {
msg = fmt.Sprintf(" * %s", m.Body)
}
return msg
}
// Render message without a theme
func (m *Message) String() string {
return m.Render(nil)
}
// Wether message is a command (starts with /)
func (m *Message) IsCommand() bool {
return strings.HasPrefix(m.Body, "/")
}
// Parse command (assumes IsCommand was already called)
func (m *Message) ParseCommand() (string, []string) {
// TODO: Tokenize this properly, to support quoted args?
cmd := strings.Split(m.Body, " ")
args := cmd[1:]
return cmd[0][1:], args
}

View File

@ -5,7 +5,7 @@ import "testing"
func TestSet(t *testing.T) {
var err error
s := NewSet()
u := NewUser("foo")
u := NewUser("foo", nil)
if s.In(u) {
t.Errorf("Set should be empty.")
@ -20,7 +20,7 @@ func TestSet(t *testing.T) {
t.Errorf("Set should contain user.")
}
u2 := NewUser("bar")
u2 := NewUser("bar", nil)
err = s.Add(u2)
if err != nil {
t.Error(err)

View File

@ -1,21 +1,27 @@
package chat
import (
"io"
"math/rand"
"time"
)
// User definition, implemented set Item interface
// User definition, implemented set Item interface and io.Writer
type User struct {
name string
op bool
colorIdx int
joined time.Time
screen io.Writer
Config UserConfig
}
func NewUser(name string) *User {
u := User{Config: *DefaultUserConfig}
func NewUser(name string, screen io.Writer) *User {
u := User{
screen: screen,
joined: time.Now(),
Config: *DefaultUserConfig,
}
u.SetName(name)
return &u
}
@ -46,6 +52,11 @@ func (u *User) SetOp(op bool) {
u.op = op
}
// Write to user's screen
func (u *User) Write(p []byte) (n int, err error) {
return u.screen.Write(p)
}
// Container for per-user configurations.
type UserConfig struct {
Highlight bool

26
chat/user_test.go Normal file
View File

@ -0,0 +1,26 @@
package chat
import (
"reflect"
"testing"
)
type MockScreen struct {
received []byte
}
func (s *MockScreen) Write(data []byte) (n int, err error) {
s.received = append(s.received, data...)
return len(data), nil
}
func TestMakeUser(t *testing.T) {
s := &MockScreen{}
u := NewUser("foo", s)
line := []byte("hello")
u.Write(line)
if !reflect.DeepEqual(s.received, line) {
t.Errorf("Expected hello but got: %s", s.received)
}
}