mirror of https://github.com/fluffle/goirc
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:
parent
7bb84985ee
commit
4cd3831e92
|
@ -20,9 +20,8 @@ type Conn struct {
|
|||
// Contains parameters that people can tweak to change client behaviour.
|
||||
cfg *Config
|
||||
|
||||
// Handlers and Commands
|
||||
// Handlers
|
||||
handlers *hSet
|
||||
commands *cSet
|
||||
|
||||
// State tracker for nicks and channels
|
||||
st state.Tracker
|
||||
|
@ -61,9 +60,6 @@ type Config struct {
|
|||
// Client->server ping frequency, in seconds. Defaults to 3m.
|
||||
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
|
||||
Flood bool
|
||||
|
||||
|
@ -113,7 +109,6 @@ func Client(cfg *Config) (*Conn, error) {
|
|||
cLoop: make(chan bool),
|
||||
cPing: make(chan bool),
|
||||
handlers: handlerSet(),
|
||||
commands: commandSet(),
|
||||
stRemovers: make([]Remover, 0, len(stHandlers)),
|
||||
lastsent: time.Now(),
|
||||
}
|
||||
|
|
|
@ -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
|
||||
// "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
|
||||
|
@ -213,18 +127,6 @@ func (conn *Conn) HandleFunc(name string, hf HandlerFunc) Remover {
|
|||
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) {
|
||||
conn.handlers.dispatch(conn, line)
|
||||
}
|
||||
|
||||
func (conn *Conn) cmdMatch(txt string) (Command, int) {
|
||||
return conn.commands.match(txt)
|
||||
}
|
||||
|
|
|
@ -176,54 +176,3 @@ func TestHandlerSet(t *testing.T) {
|
|||
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.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,37 +99,3 @@ func (conn *Conn) h_NICK(line *Line) {
|
|||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package client
|
|||
|
||||
import (
|
||||
"code.google.com/p/gomock/gomock"
|
||||
"fmt"
|
||||
"github.com/fluffle/goirc/state"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -159,57 +158,6 @@ func TestCTCP(t *testing.T) {
|
|||
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
|
||||
func TestJOIN(t *testing.T) {
|
||||
c, s := setUp(t)
|
||||
|
|
Loading…
Reference in New Issue