mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-04-14 08:07:16 +03:00
Progress, most of this probably doesnt work.
This commit is contained in:
parent
4c8d73b932
commit
1652511bf2
@ -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
27
chat/channel_test.go
Normal 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
52
chat/command.go
Normal 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
43
chat/host.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
17
chat/user.go
17
chat/user.go
@ -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
26
chat/user_test.go
Normal 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)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user