-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #47 from clambin/commands
feat(slackbot): support nested commands
- Loading branch information
Showing
21 changed files
with
958 additions
and
593 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,9 +2,6 @@ name: Test | |
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
- go121 | ||
|
||
jobs: | ||
test: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
with-expecter: true | ||
filename: "{{.InterfaceName}}.go" | ||
dir: "{{.InterfaceDir}}/mocks" | ||
mockname: "{{.InterfaceName}}" | ||
outpkg: "mocks" | ||
packages: | ||
github.com/clambin/go-common/slackbot: | ||
interfaces: | ||
SlackClient: | ||
config: | ||
dir: "{{.InterfaceDir}}/internal/mocks" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package slackbot | ||
|
||
import ( | ||
"context" | ||
"github.com/slack-go/slack" | ||
"slices" | ||
"strings" | ||
) | ||
|
||
// A Handler executes a command and returns messages to be posted to Slack. | ||
type Handler interface { | ||
Handle(context.Context, ...string) []slack.Attachment | ||
} | ||
|
||
// HandlerFunc is an adapter that allows a function to be used as a Handler | ||
type HandlerFunc func(context.Context, ...string) []slack.Attachment | ||
|
||
// Handle calls f(ctx, args) | ||
func (f HandlerFunc) Handle(ctx context.Context, args ...string) []slack.Attachment { | ||
return f(ctx, args...) | ||
} | ||
|
||
var _ Handler = &Commands{} | ||
|
||
// Commands is a map of verb/Handler pairs. | ||
// | ||
// Note that Commands itself implements the Handler interface. This allows nested command structures to be built: | ||
// | ||
// Commands | ||
// "foo" -> handler | ||
// "bar" -> Commands | ||
// "snafu" -> handler | ||
// | ||
// This creates the commands "foo" and "bar snafu" | ||
type Commands map[string]Handler | ||
|
||
// Handle processes the incoming command. The first arg is considered the verb. If it matches a supported command, its | ||
// corresponding handler is called, passing the remaining arguments. | ||
// | ||
// If the verb is not supported, an attachment is returned with all supported commands. | ||
func (c Commands) Handle(ctx context.Context, args ...string) []slack.Attachment { | ||
if subCmd, subArgs := split(args...); subCmd != "" { | ||
if subCommand, ok := c[subCmd]; ok { | ||
return subCommand.Handle(ctx, subArgs...) | ||
} | ||
} | ||
|
||
return []slack.Attachment{{ | ||
Title: "invalid command", | ||
Color: "bad", | ||
Text: "supported commands: " + strings.Join(c.GetCommands(), ", "), | ||
}} | ||
} | ||
|
||
// GetCommands returns a sorted list of all supported commands. | ||
func (c Commands) GetCommands() []string { | ||
commands := make([]string, 0, len(c)) | ||
for verb := range c { | ||
commands = append(commands, verb) | ||
} | ||
slices.Sort(commands) | ||
return commands | ||
} | ||
|
||
// Add adds one or more commands. | ||
func (c Commands) Add(commands Commands) { | ||
for verb, handler := range commands { | ||
c[verb] = handler | ||
} | ||
} | ||
|
||
func split(args ...string) (string, []string) { | ||
if len(args) == 0 { | ||
return "", nil | ||
} | ||
if len(args) == 1 { | ||
return args[0], nil | ||
} | ||
return args[0], args[1:] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package slackbot | ||
|
||
import ( | ||
"context" | ||
"github.com/slack-go/slack" | ||
"github.com/stretchr/testify/assert" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func TestCommands(t *testing.T) { | ||
handler := func(text string) Handler { | ||
return HandlerFunc(func(_ context.Context, args ...string) []slack.Attachment { | ||
if len(args) > 0 { | ||
text += ": " + strings.Join(args, ", ") | ||
} | ||
return []slack.Attachment{{Text: text}} | ||
}) | ||
} | ||
|
||
tests := []struct { | ||
name string | ||
commands Commands | ||
args []string | ||
want []slack.Attachment | ||
}{ | ||
{ | ||
name: "single command", | ||
commands: Commands{"foo": handler("foo")}, | ||
args: []string{"foo"}, | ||
want: []slack.Attachment{{Text: "foo"}}, | ||
}, | ||
{ | ||
name: "single command with args", | ||
commands: Commands{"foo": handler("foo")}, | ||
args: []string{"foo", "a=b"}, | ||
want: []slack.Attachment{{Text: "foo: a=b"}}, | ||
}, | ||
{ | ||
name: "empty", | ||
commands: Commands{"foo": handler("foo")}, | ||
args: nil, | ||
want: []slack.Attachment{{Color: "bad", Title: "invalid command", Text: "supported commands: foo"}}, | ||
}, | ||
{ | ||
name: "invalid command", | ||
commands: Commands{"foo": handler("foo")}, | ||
args: []string{"bar"}, | ||
want: []slack.Attachment{{Color: "bad", Title: "invalid command", Text: "supported commands: foo"}}, | ||
}, | ||
{ | ||
name: "nested command", | ||
commands: Commands{"foo": &Commands{"bar": handler("bar")}}, | ||
args: []string{"foo", "bar"}, | ||
want: []slack.Attachment{{Text: "bar"}}, | ||
}, | ||
{ | ||
name: "invalid nested command", | ||
commands: Commands{"foo": &Commands{"bar": handler("bar")}}, | ||
args: []string{"foo", "foo"}, | ||
want: []slack.Attachment{{Color: "bad", Title: "invalid command", Text: "supported commands: bar"}}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
tt := tt | ||
t.Run(tt.name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
c := make(Commands) | ||
c.Add(tt.commands) | ||
output := c.Handle(context.Background(), tt.args...) | ||
assert.Equal(t, tt.want, output) | ||
}) | ||
} | ||
} |
Oops, something went wrong.