Skip to content

Commit

Permalink
simplify handlers and allow for custom types
Browse files Browse the repository at this point in the history
  • Loading branch information
danryan committed Aug 20, 2014
1 parent cab0270 commit 2fbc8b8
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 141 deletions.
6 changes: 3 additions & 3 deletions examples/complex/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func run() int {
return res.Send("BAR")
})

tableFlipHandler := &hal.BasicHandler{
tableFlipHandler := &hal.Handler{
Method: hal.HEAR,
Pattern: `tableflip`,
Run: func(res *hal.Response) error {
Expand All @@ -45,8 +45,8 @@ func run() int {
// exactly in the way you would expect.
handler.Ping,

// Or use a hal.BasicHandler structure complete with usage...
&hal.BasicHandler{
// Or use a hal.Handler structure complete with usage...
&hal.Handler{
Method: hal.RESPOND,
Pattern: `SYN`,
Usage: `hal syn - replies with "ACK"`,
Expand Down
10 changes: 5 additions & 5 deletions hal.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,27 @@ func New() (*Robot, error) {

// Hear a message
func Hear(pattern string, fn func(res *Response) error) handler {
return &BasicHandler{Method: HEAR, Pattern: pattern, Run: fn}
return &Handler{Method: HEAR, Pattern: pattern, Run: fn}
}

// Respond creates a new listener for Respond messages
func Respond(pattern string, fn func(res *Response) error) handler {
return &BasicHandler{Method: RESPOND, Pattern: pattern, Run: fn}
return &Handler{Method: RESPOND, Pattern: pattern, Run: fn}
}

// Topic returns a new listener for Topic messages
func Topic(pattern string, fn func(res *Response) error) handler {
return &BasicHandler{Method: TOPIC, Run: fn}
return &Handler{Method: TOPIC, Run: fn}
}

// Enter returns a new listener for Enter messages
func Enter(fn func(res *Response) error) handler {
return &BasicHandler{Method: ENTER, Run: fn}
return &Handler{Method: ENTER, Run: fn}
}

// Leave creates a new listener for Leave messages
func Leave(fn func(res *Response) error) handler {
return &BasicHandler{Method: LEAVE, Run: fn}
return &Handler{Method: LEAVE, Run: fn}
}

// Close shuts down the robot. Unused?
Expand Down
172 changes: 55 additions & 117 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,170 +13,108 @@ var (
respondRegexpTemplate = fmt.Sprintf(`^(?:@?(?:%s|%s)[:,]?)\s+(?:${1})`, Config.Alias, Config.Name)
)

type handlerFunc func() handler

// handler is an interface for objects to implement in order to respond to messages.
type handler interface {
Handle(res *Response) error
}

// fullHandler is an interface for objects that wish to supply their own define methods
type fullHandler interface {
handler
Run(*Response) error
Usage() string
Pattern() string
Method() string
}

// Handlers is a map of registered handlers
var Handlers = map[string]handler{}

// FullHandler declares common functions shared by all handlers
type FullHandler struct {
method string
pattern string
usage string
run func(res *Response) error
}

// Handler type
type Handler struct {
Method string
Pattern string
Usage string
Run func(res *Response) error
}

// Match func
func (h *FullHandler) Match(res *Response) bool {
return handlerMatch(h.Regexp(), res.Message.Text)
}

// Match func
func (h *Handler) Match(res *Response) bool {
return handlerMatch(h.Regexp(), res.Message.Text)
}

func handlerMatch(r *regexp.Regexp, text string) bool {
if !r.MatchString(text) {
return false
}
return true
}

// Handle func
func (h *FullHandler) Handle(res *Response) error {
switch {
// handle the response without matching
case h.Pattern() == "":
return h.Run(res)
// handle the response after finding matches
case h.Match(res):
res.Match = h.Regexp().FindAllStringSubmatch(res.Message.Text, -1)[0]
return h.Run(res)
// if we don't find a match, return
func handlerRegexp(method, pattern string) *regexp.Regexp {
if method == RESPOND {
return regexp.MustCompile(strings.Replace(respondRegexpTemplate, "${1}", pattern, 1))
}
return regexp.MustCompile(pattern)
}

// NewHandler checks whether h implements the handler interface, wrapping it in a FullHandler
func NewHandler(h interface{}) (handler, error) {
switch v := h.(type) {
case fullHandler:
return &FullHandler{handler: v}, nil
case handler:
return v, nil
default:
return nil
return nil, fmt.Errorf("%v does not implement the handler interface", v)
}
}

// Handler type
type Handler struct {
Method string
Pattern string
Usage string
Run func(res *Response) error
}

// Handle func
func (h *Handler) Handle(res *Response) error {
switch {
// handle the response without matching
case h.Pattern == "":
return h.Run(res)
// handle the response after finding matches
case h.Match(res):
res.Match = h.Regexp().FindAllStringSubmatch(res.Message.Text, -1)[0]
case h.match(res):
res.Match = h.regexp().FindAllStringSubmatch(res.Message.Text, -1)[0]
return h.Run(res)
// if we don't find a match, return
default:
return nil
}
}

// Pattern func
func (h *FullHandler) Pattern() string {
return h.pattern
func (h *Handler) regexp() *regexp.Regexp {
return handlerRegexp(h.Method, h.Pattern)
}

// Usage func
func (h *FullHandler) Usage() string {
return h.usage
// Match func
func (h *Handler) match(res *Response) bool {
return handlerMatch(h.regexp(), res.Message.Text)
}

// Method func
func (h *FullHandler) Method() string {
return h.method
// fullHandler is an interface for objects that wish to supply their own define methods
type fullHandler interface {
Run(*Response) error
Usage() string
Pattern() string
Method() string
}

// Run func
func (h *FullHandler) Run(res *Response) error {
return h.run(res)
// FullHandler declares common functions shared by all handlers
type FullHandler struct {
handler fullHandler
}

// Regexp func
func (h *FullHandler) Regexp() *regexp.Regexp {
return handlerRegexp(h.Method(), h.Pattern())
}

// Regexp func
func (h *Handler) Regexp() *regexp.Regexp {
return handlerRegexp(h.Method, h.Pattern)
}

func handlerRegexp(method, pattern string) *regexp.Regexp {
if method == RESPOND {
return regexp.MustCompile(strings.Replace(respondRegexpTemplate, "${1}", pattern, 1))
}
return regexp.MustCompile(pattern)
return handlerRegexp(h.handler.Method(), h.handler.Pattern())
}

// NewHandler func
func NewHandler(h handler) handler {
if fh, ok := h.(fullHandler); ok {
return &FullHandler{
pattern: fh.Pattern(),
usage: fh.Usage(),
method: fh.Method(),
run: fh.Run,
}
}
return h
}

// BasicHandler is used to construct handlers that are low complexity and may
// not benefit from creating a custom handler type.
type BasicHandler struct {
Method string
Pattern string
Usage string
Run func(res *Response) error
// Match func
func (h *FullHandler) Match(res *Response) bool {
return handlerMatch(h.Regexp(), res.Message.Text)
}

// Handle implements the hal.Handler interface
func (h *BasicHandler) Handle(res *Response) error {
text := res.Message.Text
var regex *regexp.Regexp

if h.Method == RESPOND {
regex = regexp.MustCompile(strings.Replace(respondRegexpTemplate, "${1}", h.Pattern, 1))
} else {
regex = regexp.MustCompile(h.Pattern)
}

// assume we handle if no pattern was specified
if h.Pattern == "" {
return nil
}

if !regex.MatchString(text) {
// Handle func
func (h *FullHandler) Handle(res *Response) error {
switch {
// handle the response without matching
case h.handler.Pattern() == "":
return h.handler.Run(res)
// handle the response after finding matches
case h.Match(res):
res.Match = h.Regexp().FindAllStringSubmatch(res.Text(), -1)[0]
return h.handler.Run(res)
// if we don't find a match, return
default:
return nil
}

Logger.Debugf(`%s matched /%s/`, text, regex)
res.Match = regex.FindAllStringSubmatch(text, -1)[0]
return h.Run(res)
}
13 changes: 4 additions & 9 deletions handler/echo.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@ import (
"github.com/danryan/hal"
)

// Echo exports our echo handler
var Echo = &hal.Handler{
Method: hal.RESPOND,
Pattern: `echo (.+)`,
Usage: "echo STRING - echoes STRING",
Run: func(res *hal.Response) error {
return res.Reply(res.Match[1])
},
}
// Echo is an example of a simple handler.
var Echo = hal.Respond(`echo (.+)`, func(res *hal.Response) error {
return res.Reply(res.Match[1])
})
14 changes: 14 additions & 0 deletions handler/tableflip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package handler

import (
"github.com/danryan/hal"
)

// TableFlip is an example of a Handler
var TableFlip = &hal.Handler{
Method: hal.HEAR,
Pattern: `tableflip`,
Run: func(res *hal.Response) error {
return res.Send(`(╯°□°)╯︵ ┻━┻`)
},
}
8 changes: 4 additions & 4 deletions handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import (
"github.com/danryan/hal"
)

func ExampleBasicHandler_hear() {
func ExampleHandler_hear() {
res := hal.Response{
Match: []string{},
}
h := &hal.BasicHandler{
h := &hal.Handler{
Method: hal.HEAR,
Pattern: `echo (.+)`,
Usage: "echo <string> - repeats <string> back",
Expand All @@ -21,8 +21,8 @@ func ExampleBasicHandler_hear() {
// foo bar baz
}

func ExampleBasicHandler_respond() {
&BasicHandler{
func ExampleHandler_respond() {
&Handler{
Method: hal.RESPOND,
Pattern: `(?i)ping`, // (?i) is a flag that makes the match case insensitive
Usage: `hal ping - replies with "PONG"`,
Expand Down
11 changes: 8 additions & 3 deletions robot.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,16 @@ func NewRobot() (*Robot, error) {
}

// Handle registers a new handler with the robot
func (robot *Robot) Handle(handlers ...handler) {
func (robot *Robot) Handle(handlers ...interface{}) {
for _, h := range handlers {
robot.handlers = append(robot.handlers, NewHandler(h))
nh, err := NewHandler(h)
if err != nil {
Logger.Fatal(err)
panic(err)
}

robot.handlers = append(robot.handlers, nh)
}
// robot.handlers = append(robot.handlers, handlers...)
}

// Receive dispatches messages to our handlers
Expand Down

0 comments on commit 2fbc8b8

Please sign in to comment.