Remove Commands from core goirc.

This dictates too much about how people might want to parse and act upon
information from PRIVMSGs, and thus should be an optional thing.
This commit is contained in:
Alex Bramley 2013-02-27 20:23:24 +00:00
parent 7bb84985ee
commit 4cd3831e92
5 changed files with 1 additions and 241 deletions

View File

@ -20,9 +20,8 @@ type Conn struct {
// Contains parameters that people can tweak to change client behaviour. // Contains parameters that people can tweak to change client behaviour.
cfg *Config cfg *Config
// Handlers and Commands // Handlers
handlers *hSet handlers *hSet
commands *cSet
// State tracker for nicks and channels // State tracker for nicks and channels
st state.Tracker st state.Tracker
@ -61,9 +60,6 @@ type Config struct {
// Client->server ping frequency, in seconds. Defaults to 3m. // Client->server ping frequency, in seconds. Defaults to 3m.
PingFreq time.Duration PingFreq time.Duration
// Controls what is stripped from line.Args[1] for Commands
CommandStripNick, CommandStripPrefix bool
// Set this to true to disable flood protection and false to re-enable // Set this to true to disable flood protection and false to re-enable
Flood bool Flood bool
@ -113,7 +109,6 @@ func Client(cfg *Config) (*Conn, error) {
cLoop: make(chan bool), cLoop: make(chan bool),
cPing: make(chan bool), cPing: make(chan bool),
handlers: handlerSet(), handlers: handlerSet(),
commands: commandSet(),
stRemovers: make([]Remover, 0, len(stHandlers)), stRemovers: make([]Remover, 0, len(stHandlers)),
lastsent: time.Now(), lastsent: time.Now(),
} }

View File

@ -113,92 +113,6 @@ func (hs *hSet) dispatch(conn *Conn, line *Line) {
} }
} }
// An IRC command looks like this:
type Command interface {
Execute(*Conn, *Line)
Help() string
}
type command struct {
fn HandlerFunc
help string
}
func (c *command) Execute(conn *Conn, line *Line) {
c.fn(conn, line)
}
func (c *command) Help() string {
return c.help
}
type cNode struct {
cmd Command
set *cSet
prefix string
}
func (cn *cNode) Execute(conn *Conn, line *Line) {
cn.cmd.Execute(conn, line)
}
func (cn *cNode) Help() string {
return cn.cmd.Help()
}
func (cn *cNode) Remove() {
cn.set.remove(cn)
}
type cSet struct {
set map[string]*cNode
sync.RWMutex
}
func commandSet() *cSet {
return &cSet{set: make(map[string]*cNode)}
}
func (cs *cSet) add(pf string, c Command) Remover {
cs.Lock()
defer cs.Unlock()
pf = strings.ToLower(pf)
if _, ok := cs.set[pf]; ok {
logging.Error("Command prefix '%s' already registered.", pf)
return nil
}
cn := &cNode{
cmd: c,
set: cs,
prefix: pf,
}
cs.set[pf] = cn
return cn
}
func (cs *cSet) remove(cn *cNode) {
cs.Lock()
defer cs.Unlock()
delete(cs.set, cn.prefix)
cn.set = nil
}
func (cs *cSet) match(txt string) (final Command, prefixlen int) {
cs.RLock()
defer cs.RUnlock()
txt = strings.ToLower(txt)
for prefix, cmd := range cs.set {
if !strings.HasPrefix(txt, prefix) {
continue
}
if final == nil || len(prefix) > prefixlen {
prefixlen = len(prefix)
final = cmd
}
}
return
}
// Handlers are triggered on incoming Lines from the server, with the handler // Handlers are triggered on incoming Lines from the server, with the handler
// "name" being equivalent to Line.Cmd. Read the RFCs for details on what // "name" being equivalent to Line.Cmd. Read the RFCs for details on what
// replies could come from the server. They'll generally be things like // replies could come from the server. They'll generally be things like
@ -213,18 +127,6 @@ func (conn *Conn) HandleFunc(name string, hf HandlerFunc) Remover {
return conn.Handle(name, hf) return conn.Handle(name, hf)
} }
func (conn *Conn) Command(prefix string, c Command) Remover {
return conn.commands.add(prefix, c)
}
func (conn *Conn) CommandFunc(prefix string, hf HandlerFunc, help string) Remover {
return conn.Command(prefix, &command{hf, help})
}
func (conn *Conn) dispatch(line *Line) { func (conn *Conn) dispatch(line *Line) {
conn.handlers.dispatch(conn, line) conn.handlers.dispatch(conn, line)
} }
func (conn *Conn) cmdMatch(txt string) (Command, int) {
return conn.commands.match(txt)
}

View File

@ -176,54 +176,3 @@ func TestHandlerSet(t *testing.T) {
t.Errorf("Our handler was called?") t.Errorf("Our handler was called?")
} }
} }
func TestCommandSet(t *testing.T) {
cs := commandSet()
if len(cs.set) != 0 {
t.Errorf("New set contains things!")
}
c := &command{
fn: func(c *Conn, l *Line) {},
help: "wtf?",
}
cn1 := cs.add("ONE", c).(*cNode)
if _, ok := cs.set["one"]; !ok || cn1.set != cs || cn1.prefix != "one" {
t.Errorf("Command 'one' not added to set correctly.")
}
if fail := cs.add("one", c); fail != nil {
t.Errorf("Adding a second 'one' command did not fail as expected.")
}
cn2 := cs.add("One Two", c).(*cNode)
if _, ok := cs.set["one two"]; !ok || cn2.set != cs || cn2.prefix != "one two" {
t.Errorf("Command 'one two' not added to set correctly.")
}
if c, l := cs.match("foo"); c != nil || l != 0 {
t.Errorf("Matched 'foo' when we shouldn't.")
}
if c, l := cs.match("one"); c.(*cNode) != cn1 || l != 3 {
t.Errorf("Didn't match 'one' when we should have.")
}
if c, l := cs.match("one two three"); c.(*cNode) != cn2 || l != 7 {
t.Errorf("Didn't match 'one two' when we should have.")
}
cs.remove(cn2)
if _, ok := cs.set["one two"]; ok || cn2.set != nil {
t.Errorf("Command 'one two' not removed correctly.")
}
if c, l := cs.match("one two three"); c.(*cNode) != cn1 || l != 3 {
t.Errorf("Didn't match 'one' when we should have.")
}
cn1.Remove()
if _, ok := cs.set["one"]; ok || cn1.set != nil {
t.Errorf("Command 'one' not removed correctly.")
}
if c, l := cs.match("one two three"); c != nil || l != 0 {
t.Errorf("Matched 'one' when we shouldn't have.")
}
}

View File

@ -99,37 +99,3 @@ func (conn *Conn) h_NICK(line *Line) {
conn.cfg.Me.Nick = line.Args[0] conn.cfg.Me.Nick = line.Args[0]
} }
} }
// Handle PRIVMSGs that trigger Commands
func (conn *Conn) h_PRIVMSG(line *Line) {
txt := line.Args[1]
if conn.cfg.CommandStripNick && strings.HasPrefix(txt, conn.cfg.Me.Nick) {
// Look for '^${nick}[:;>,-]? '
l := len(conn.cfg.Me.Nick)
switch txt[l] {
case ':', ';', '>', ',', '-':
l++
}
if txt[l] == ' ' {
txt = strings.TrimSpace(txt[l:])
}
}
cmd, l := conn.cmdMatch(txt)
if cmd == nil {
return
}
if conn.cfg.CommandStripPrefix {
txt = strings.TrimSpace(txt[l:])
}
if txt != line.Args[1] {
line = line.Copy()
line.Args[1] = txt
}
cmd.Execute(conn, line)
}
func (conn *Conn) c_HELP(line *Line) {
if cmd, _ := conn.cmdMatch(line.Args[1]); cmd != nil {
conn.Privmsg(line.Args[0], cmd.Help())
}
}

View File

@ -2,7 +2,6 @@ package client
import ( import (
"code.google.com/p/gomock/gomock" "code.google.com/p/gomock/gomock"
"fmt"
"github.com/fluffle/goirc/state" "github.com/fluffle/goirc/state"
"testing" "testing"
"time" "time"
@ -159,57 +158,6 @@ func TestCTCP(t *testing.T) {
c.h_CTCP(parseLine(":blah!moo@cows.com PRIVMSG test :\001UNKNOWN ctcp\001")) c.h_CTCP(parseLine(":blah!moo@cows.com PRIVMSG test :\001UNKNOWN ctcp\001"))
} }
func TestPRIVMSG(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
f := func(conn *Conn, line *Line) {
conn.Privmsg(line.Args[0], line.Args[1])
}
c.CommandFunc("prefix", f, "")
// CommandStripNick and CommandStripPrefix are both false to begin
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :prefix bar"))
s.nc.Expect("PRIVMSG #foo :prefix bar")
// If we're not stripping off the nick, the prefix won't match.
// This might be considered a bug, but then the library currently has a
// poor understanding of the concept of "being addressed".
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :test: prefix bar"))
s.nc.ExpectNothing()
c.cfg.CommandStripNick = true
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :prefix bar"))
s.nc.Expect("PRIVMSG #foo :prefix bar")
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :test: prefix bar"))
s.nc.Expect("PRIVMSG #foo :prefix bar")
c.cfg.CommandStripPrefix = true
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :prefix bar"))
s.nc.Expect("PRIVMSG #foo :bar")
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :test: prefix bar"))
s.nc.Expect("PRIVMSG #foo :bar")
c.cfg.CommandStripNick = false
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :prefix bar"))
s.nc.Expect("PRIVMSG #foo :bar")
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :test: prefix bar"))
s.nc.ExpectNothing()
// Check the various nick addressing notations that are supported.
c.cfg.CommandStripNick = true
for _, addr := range []string{":", ";", ",", ">", "-", ""} {
c.h_PRIVMSG(parseLine(fmt.Sprintf(
":blah!moo@cows.com PRIVMSG #foo :test%s prefix bar", addr)))
s.nc.Expect("PRIVMSG #foo :bar")
c.h_PRIVMSG(parseLine(fmt.Sprintf(
":blah!moo@cows.com PRIVMSG #foo :test%sprefix bar", addr)))
s.nc.ExpectNothing()
}
c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :test! prefix bar"))
s.nc.ExpectNothing()
}
// Test the handler for JOIN messages // Test the handler for JOIN messages
func TestJOIN(t *testing.T) { func TestJOIN(t *testing.T) {
c, s := setUp(t) c, s := setUp(t)