From e4da830c55a6bcab840c7693c43679e7318ffb53 Mon Sep 17 00:00:00 2001 From: Chris Rhodes Date: Sun, 17 Feb 2013 18:40:58 -0800 Subject: [PATCH] Handlers/Commands now use the container/list. Re introduced strip nick and strip prefix for SimpleCommands Fixed tests. --- client/connection.go | 7 +- client/dispatch.go | 164 ++++++++++++++++------------------------ client/dispatch_test.go | 145 ++++++++++------------------------- client/handlers.go | 20 ++++- client/handlers_test.go | 6 +- 5 files changed, 134 insertions(+), 208 deletions(-) diff --git a/client/connection.go b/client/connection.go index 3647ed0..92681d6 100644 --- a/client/connection.go +++ b/client/connection.go @@ -21,7 +21,7 @@ type Conn struct { password string // Handlers and Commands - handlers *hSet + handlers *handlerSet commands *commandList // State tracker for nicks and channels @@ -54,6 +54,9 @@ type Conn struct { // Client->server ping frequency, in seconds. Defaults to 3m. PingFreq time.Duration + // Controls what is stripped from line.Args[1] for Commands + CommandStripNick, SimpleCommandStripPrefix bool + // Set this to true to disable flood protection and false to re-enable Flood bool @@ -81,7 +84,7 @@ func Client(nick string, args ...string) *Conn { cSend: make(chan bool), cLoop: make(chan bool), cPing: make(chan bool), - handlers: handlerSet(), + handlers: newHandlerSet(), commands: newCommandList(), stRemovers: make([]Remover, 0, len(stHandlers)), PingFreq: 3 * time.Minute, diff --git a/client/dispatch.go b/client/dispatch.go index a501e14..3369cd4 100644 --- a/client/dispatch.go +++ b/client/dispatch.go @@ -1,6 +1,7 @@ package client import ( + "container/list" "fmt" "github.com/fluffle/golog/logging" "math" @@ -31,163 +32,119 @@ func (r RemoverFunc) Remove() { r() } -type hList struct { - start, end *hNode +type handlerElement struct { + event string + handler Handler } -type hNode struct { - next, prev *hNode - set *hSet - event string - handler Handler -} - -func (hn *hNode) Handle(conn *Conn, line *Line) { - hn.handler.Handle(conn, line) -} - -func (hn *hNode) Remove() { - hn.set.remove(hn) -} - -type hSet struct { - set map[string]*hList +type handlerSet struct { + set map[string]*list.List sync.RWMutex } -func handlerSet() *hSet { - return &hSet{set: make(map[string]*hList)} +func newHandlerSet() *handlerSet { + return &handlerSet{set: make(map[string]*list.List)} } -func (hs *hSet) add(ev string, h Handler) Remover { +func (hs *handlerSet) add(event string, handler Handler) Remover { hs.Lock() defer hs.Unlock() - ev = strings.ToLower(ev) - l, ok := hs.set[ev] + event = strings.ToLower(event) + l, ok := hs.set[event] if !ok { - l = &hList{} + l = list.New() + hs.set[event] = l } - hn := &hNode{ - set: hs, - event: ev, - handler: h, - } - if !ok { - l.start = hn - } else { - hn.prev = l.end - l.end.next = hn - } - l.end = hn - hs.set[ev] = l - return hn + element := l.PushBack(&handlerElement{event, handler}) + return RemoverFunc(func() { + hs.remove(element) + }) } -func (hs *hSet) remove(hn *hNode) { +func (hs *handlerSet) remove(element *list.Element) { hs.Lock() defer hs.Unlock() - l, ok := hs.set[hn.event] + h := element.Value.(*handlerElement) + l, ok := hs.set[h.event] if !ok { - logging.Error("Removing node for unknown event '%s'", hn.event) + logging.Error("Removing node for unknown event '%s'", h.event) return } - if hn.next == nil { - l.end = hn.prev - } else { - hn.next.prev = hn.prev - } - if hn.prev == nil { - l.start = hn.next - } else { - hn.prev.next = hn.next - } - hn.next = nil - hn.prev = nil - hn.set = nil - if l.start == nil || l.end == nil { - delete(hs.set, hn.event) + l.Remove(element) + if l.Len() == 0 { + delete(hs.set, h.event) } } -func (hs *hSet) dispatch(conn *Conn, line *Line) { +func (hs *handlerSet) dispatch(conn *Conn, line *Line) { hs.RLock() defer hs.RUnlock() - ev := strings.ToLower(line.Cmd) - list, ok := hs.set[ev] + event := strings.ToLower(line.Cmd) + l, ok := hs.set[event] if !ok { return } - for hn := list.start; hn != nil; hn = hn.next { - go hn.Handle(conn, line) + + for e := l.Front(); e != nil; e = e.Next() { + h := e.Value.(*handlerElement) + go h.handler.Handle(conn, line) } } -type command struct { - handler Handler - set *commandList +type commandElement struct { regex string + handler Handler priority int } -func (c *command) Handle(conn *Conn, line *Line) { - c.handler.Handle(conn, line) -} - -func (c *command) Remove() { - c.set.remove(c) -} - type commandList struct { - set []*command + list *list.List sync.RWMutex } func newCommandList() *commandList { - return &commandList{} + return &commandList{list: list.New()} } func (cl *commandList) add(regex string, handler Handler, priority int) Remover { cl.Lock() defer cl.Unlock() - c := &command{ - handler: handler, - set: cl, + c := &commandElement{ regex: regex, + handler: handler, priority: priority, } // Check for exact regex matches. This will filter out any repeated SimpleCommands. - for _, c := range cl.set { + for e := cl.list.Front(); e != nil; e = e.Next() { + c := e.Value.(*commandElement) if c.regex == regex { logging.Error("Command prefix '%s' already registered.", regex) return nil } } - cl.set = append(cl.set, c) - return c + element := cl.list.PushBack(c) + return RemoverFunc(func() { + cl.remove(element) + }) } -func (cl *commandList) remove(c *command) { +func (cl *commandList) remove(element *list.Element) { cl.Lock() defer cl.Unlock() - for index, value := range cl.set { - if value == c { - copy(cl.set[index:], cl.set[index+1:]) - cl.set = cl.set[:len(cl.set)-1] - c.set = nil - return - } - } + cl.list.Remove(element) } // Matches the command with the highest priority. -func (cl *commandList) match(txt string) (handler Handler) { +func (cl *commandList) match(text string) (handler Handler) { cl.RLock() defer cl.RUnlock() maxPriority := math.MinInt32 - for _, c := range cl.set { + text = strings.ToLower(text) + for e := cl.list.Front(); e != nil; e = e.Next() { + c := e.Value.(*commandElement) if c.priority > maxPriority { if regex, error := regexp.Compile(c.regex); error == nil { - if regex.MatchString(txt) { + if regex.MatchString(text) { maxPriority = c.priority handler = c.handler } @@ -226,7 +183,18 @@ var SimpleCommandRegex string = `^!%v(\s|$)` // !roll // Because simple commands are simple, they get the highest priority. func (conn *Conn) SimpleCommand(prefix string, handler Handler) Remover { - return conn.Command(fmt.Sprintf(SimpleCommandRegex, strings.ToLower(prefix)), handler, math.MaxInt32) + stripHandler := func(conn *Conn, line *Line) { + text := line.Message() + if conn.SimpleCommandStripPrefix { + text = strings.TrimSpace(text[len(prefix):]) + } + if text != line.Message() { + line = line.Copy() + line.Args[1] = text + } + handler.Handle(conn, line) + } + return conn.Command(fmt.Sprintf(SimpleCommandRegex, strings.ToLower(prefix)), HandlerFunc(stripHandler), math.MaxInt32) } func (conn *Conn) SimpleCommandFunc(prefix string, handlerFunc HandlerFunc) Remover { @@ -256,10 +224,6 @@ func (conn *Conn) dispatch(line *Line) { conn.handlers.dispatch(conn, line) } -func (conn *Conn) command(line *Line) { - command := conn.commands.match(strings.ToLower(line.Message())) - if command != nil { - command.Handle(conn, line) - } - +func (conn *Conn) command(line *Line) Handler { + return conn.commands.match(line.Message()) } diff --git a/client/dispatch_test.go b/client/dispatch_test.go index 84b9e54..1e3caca 100644 --- a/client/dispatch_test.go +++ b/client/dispatch_test.go @@ -6,7 +6,7 @@ import ( ) func TestHandlerSet(t *testing.T) { - hs := handlerSet() + hs := newHandlerSet() if len(hs.set) != 0 { t.Errorf("New set contains things!") } @@ -17,66 +17,40 @@ func TestHandlerSet(t *testing.T) { } // Add one - hn1 := hs.add("ONE", HandlerFunc(f)).(*hNode) + hn1 := hs.add("ONE", HandlerFunc(f)) hl, ok := hs.set["one"] if len(hs.set) != 1 || !ok { t.Errorf("Set doesn't contain 'one' list after add().") } - if hn1.set != hs || hn1.event != "one" || hn1.prev != nil || hn1.next != nil { - t.Errorf("First node for 'one' not created correctly") - } - if hl.start != hn1 || hl.end != hn1 { - t.Errorf("Node not added to empty 'one' list correctly.") + if hl.Len() != 1 { + t.Errorf("List doesn't contain 'one' after add().") } // Add another one... - hn2 := hs.add("one", HandlerFunc(f)).(*hNode) + hn2 := hs.add("one", HandlerFunc(f)) if len(hs.set) != 1 { t.Errorf("Set contains more than 'one' list after add().") } - if hn2.set != hs || hn2.event != "one" { - t.Errorf("Second node for 'one' not created correctly") - } - if hn1.prev != nil || hn1.next != hn2 || hn2.prev != hn1 || hn2.next != nil { - t.Errorf("Nodes for 'one' not linked correctly.") - } - if hl.start != hn1 || hl.end != hn2 { - t.Errorf("Node not appended to 'one' list correctly.") + if hl.Len() != 2 { + t.Errorf("List doesn't contain second 'one' after add().") } // Add a third one! - hn3 := hs.add("one", HandlerFunc(f)).(*hNode) + hn3 := hs.add("one", HandlerFunc(f)) if len(hs.set) != 1 { t.Errorf("Set contains more than 'one' list after add().") } - if hn3.set != hs || hn3.event != "one" { - t.Errorf("Third node for 'one' not created correctly") - } - if hn1.prev != nil || hn1.next != hn2 || - hn2.prev != hn1 || hn2.next != hn3 || - hn3.prev != hn2 || hn3.next != nil { - t.Errorf("Nodes for 'one' not linked correctly.") - } - if hl.start != hn1 || hl.end != hn3 { - t.Errorf("Node not appended to 'one' list correctly.") + if hl.Len() != 3 { + t.Errorf("List doesn't contain third 'one' after add().") } // And finally a fourth one! - hn4 := hs.add("one", HandlerFunc(f)).(*hNode) + hn4 := hs.add("one", HandlerFunc(f)) if len(hs.set) != 1 { t.Errorf("Set contains more than 'one' list after add().") } - if hn4.set != hs || hn4.event != "one" { - t.Errorf("Fourth node for 'one' not created correctly.") - } - if hn1.prev != nil || hn1.next != hn2 || - hn2.prev != hn1 || hn2.next != hn3 || - hn3.prev != hn2 || hn3.next != hn4 || - hn4.prev != hn3 || hn4.next != nil { - t.Errorf("Nodes for 'one' not linked correctly.") - } - if hl.start != hn1 || hl.end != hn4 { - t.Errorf("Node not appended to 'one' list correctly.") + if hl.Len() != 4 { + t.Errorf("List doesn't contain fourth 'one' after add().") } // Dispatch should result in 4 additions. @@ -94,16 +68,8 @@ func TestHandlerSet(t *testing.T) { if len(hs.set) != 1 { t.Errorf("Set list count changed after remove().") } - if hn3.set != nil || hn3.prev != nil || hn3.next != nil { - t.Errorf("Third node for 'one' not removed correctly.") - } - if hn1.prev != nil || hn1.next != hn2 || - hn2.prev != hn1 || hn2.next != hn4 || - hn4.prev != hn2 || hn4.next != nil { - t.Errorf("Third node for 'one' not unlinked correctly.") - } - if hl.start != hn1 || hl.end != hn4 { - t.Errorf("Third node for 'one' changed list pointers.") + if hl.Len() != 3 { + t.Errorf("Third 'one' not removed correctly.") } // Dispatch should result in 3 additions. @@ -114,18 +80,12 @@ func TestHandlerSet(t *testing.T) { } // Remove node 1. - hs.remove(hn1) + hn1.Remove() if len(hs.set) != 1 { t.Errorf("Set list count changed after remove().") } - if hn1.set != nil || hn1.prev != nil || hn1.next != nil { - t.Errorf("First node for 'one' not removed correctly.") - } - if hn2.prev != nil || hn2.next != hn4 || hn4.prev != hn2 || hn4.next != nil { - t.Errorf("First node for 'one' not unlinked correctly.") - } - if hl.start != hn2 || hl.end != hn4 { - t.Errorf("First node for 'one' didn't change list pointers.") + if hl.Len() != 2 { + t.Errorf("First 'one' not removed correctly.") } // Dispatch should result in 2 additions. @@ -140,14 +100,8 @@ func TestHandlerSet(t *testing.T) { if len(hs.set) != 1 { t.Errorf("Set list count changed after remove().") } - if hn4.set != nil || hn4.prev != nil || hn4.next != nil { - t.Errorf("Fourth node for 'one' not removed correctly.") - } - if hn2.prev != nil || hn2.next != nil { - t.Errorf("Fourth node for 'one' not unlinked correctly.") - } - if hl.start != hn2 || hl.end != hn2 { - t.Errorf("Fourth node for 'one' didn't change list pointers.") + if hl.Len() != 1 { + t.Errorf("Fourth 'one' not removed correctly.") } // Dispatch should result in 1 addition. @@ -158,16 +112,10 @@ func TestHandlerSet(t *testing.T) { } // Remove node 2. - hs.remove(hn2) + hn2.Remove() if len(hs.set) != 0 { t.Errorf("Removing last node in 'one' didn't remove list.") } - if hn2.set != nil || hn2.prev != nil || hn2.next != nil { - t.Errorf("Second node for 'one' not removed correctly.") - } - if hl.start != nil || hl.end != nil { - t.Errorf("Second node for 'one' didn't change list pointers.") - } // Dispatch should result in NO additions. hs.dispatch(nil, &Line{Cmd: "One"}) @@ -178,52 +126,43 @@ func TestHandlerSet(t *testing.T) { } func TestCommandSet(t *testing.T) { - cs := commandSet() - if len(cs.set) != 0 { - t.Errorf("New set contains things!") + cl := newCommandList() + if cl.list.Len() != 0 { + t.Errorf("New list contains things!") } - c := &command{ - fn: func(c *Conn, l *Line) {}, - help: "wtf?", + cn1 := cl.add("one", HandlerFunc(func(c *Conn, l *Line) {}), 0) + if cl.list.Len() != 1 { + t.Errorf("Command 'one' not added to list correctly.") } - 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" { + cn2 := cl.add("one two", HandlerFunc(func(c *Conn, l *Line) {}), 0) + if cl.list.Len() != 2 { t.Errorf("Command 'one two' not added to set correctly.") } - if c, l := cs.match("foo"); c != nil || l != 0 { + if c := cl.match("foo"); c != nil { 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 := cl.match("one"); c == nil { + t.Errorf("Didn't match 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.") + if c := cl.match("one two three"); c == nil { + t.Errorf("Didn't match when we should have.") } - cs.remove(cn2) - if _, ok := cs.set["one two"]; ok || cn2.set != nil { + cn2.Remove() + if cl.list.Len() != 1 { 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.") + if c := cl.match("one two three"); c == nil { + t.Errorf("Didn't match when we should have.") } cn1.Remove() - if _, ok := cs.set["one"]; ok || cn1.set != nil { - t.Errorf("Command 'one' not removed correctly.") + if cl.list.Len() != 0 { + t.Errorf("Command 'one two' not removed correctly.") } - if c, l := cs.match("one two three"); c != nil || l != 0 { - t.Errorf("Matched 'one' when we shouldn't have.") + if c := cl.match("one two three"); c != nil { + t.Errorf("Matched 'one' when we shouldn't.") } } diff --git a/client/handlers.go b/client/handlers.go index f5ded17..14774d3 100644 --- a/client/handlers.go +++ b/client/handlers.go @@ -97,5 +97,23 @@ func (conn *Conn) h_NICK(line *Line) { // Handle PRIVMSGs that trigger Commands func (conn *Conn) h_PRIVMSG(line *Line) { - conn.command(line) + text := line.Message() + if conn.CommandStripNick && strings.HasPrefix(text, conn.Me.Nick) { + // Look for '^${nick}[:;>,-]? ' + l := len(conn.Me.Nick) + switch text[l] { + case ':', ';', '>', ',', '-': + l++ + } + if text[l] == ' ' { + text = strings.TrimSpace(text[l:]) + } + line = line.Copy() + line.Args[1] = text + } + cmd := conn.command(line) + if cmd == nil { + return + } + cmd.Handle(conn, line) } diff --git a/client/handlers_test.go b/client/handlers_test.go index 1389967..c6c0e28 100644 --- a/client/handlers_test.go +++ b/client/handlers_test.go @@ -146,7 +146,9 @@ func TestPRIVMSG(t *testing.T) { f := func(conn *Conn, line *Line) { conn.Privmsg(line.Args[0], line.Args[1]) } - c.CommandFunc("prefix", f, "") + // Test legacy simpleCommands, with out the !. + SimpleCommandRegex = `^%v(\s|$)` + c.SimpleCommandFunc("prefix", f) // CommandStripNick and CommandStripPrefix are both false to begin c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :prefix bar")) @@ -163,7 +165,7 @@ func TestPRIVMSG(t *testing.T) { c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :test: prefix bar")) s.nc.Expect("PRIVMSG #foo :prefix bar") - c.CommandStripPrefix = true + c.SimpleCommandStripPrefix = 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"))