Bot: Added Plumb support, fixed tests

Merged

Merged
This commit is contained in:
diamondburned (Forefront) 2020-05-12 21:37:06 -07:00
parent 9e59402591
commit 67430c6d7a
7 changed files with 85 additions and 51 deletions

View File

@ -83,10 +83,7 @@ type MethodContext struct {
MethodName string
// Command is the Discord command used to call the method.
Command string // hidden if empty
// Hidden is true if the method has a hidden nameflag.
// Hidden bool
Command string // plumb if empty
// Variadic is true if the function is a variadic one or if the last
// argument accepts multiple strings.

View File

@ -217,39 +217,23 @@ func (ctx *Context) Subcommands() []*Subcommand {
return ctx.subcommands
}
// FindMethod finds a method based on the struct and method name. The queried
// FindCommand finds a command based on the struct and method name. The queried
// names will have their flags stripped.
//
// Example
//
// // Find a command from the main context:
// cmd := ctx.FindMethod("", "Method")
// cmd := ctx.FindCommand("", "Method")
// // Find a command from a subcommand:
// cmd = ctx.FindMethod("Starboard", "Reset")
// cmd = ctx.FindCommand("Starboard", "Reset")
//
func (ctx *Context) FindMethod(structname, methodname string) *MethodContext {
func (ctx *Context) FindCommand(structname, methodname string) *MethodContext {
if structname == "" {
for _, c := range ctx.Methods {
if c.MethodName == methodname {
return c
}
}
return nil
return ctx.Subcommand.FindCommand(methodname)
}
for _, sub := range ctx.subcommands {
if sub.StructName != structname {
continue
}
for _, c := range sub.Methods {
if c.MethodName == methodname {
return c
}
if sub.StructName == structname {
return sub.FindCommand(methodname)
}
}
return nil
}

View File

@ -286,7 +286,7 @@ Call:
// findCommand filters.
func (ctx *Context) findCommand(parts []string) ([]string, *MethodContext, *Subcommand, error) {
// Main command entrypoint cannot have plumb.
for _, c := range ctx.Methods {
for _, c := range ctx.Commands {
if c.Command == parts[0] {
return parts[1:], c, ctx.Subcommand, nil
}
@ -299,15 +299,16 @@ func (ctx *Context) findCommand(parts []string) ([]string, *MethodContext, *Subc
continue
}
// If there's no second argument, TODO call Help.
if s.plumbed != nil {
// Only actually plumb if we actually have a plumbed handler AND
// 1. We only have one command handler OR
// 2. We only have the subcommand name but no command.
if s.plumbed != nil && (len(s.Commands) == 1 || len(parts) <= 2) {
return parts[1:], s.plumbed, s, nil
}
if len(parts) >= 2 {
for _, c := range s.Methods {
if c.event == typeMessageCreate && c.Command == parts[1] {
for _, c := range s.Commands {
if c.Command == parts[1] {
return parts[2:], c, s, nil
}
}

View File

@ -50,7 +50,7 @@ func TestSubcommandPlumb(t *testing.T) {
// Try call exactly what's in the Plumb example:
m := &gateway.MessageCreateEvent{
Message: discord.Message{
Content: "hasPlumb test command",
Content: "hasPlumb",
},
}
@ -61,6 +61,50 @@ func TestSubcommandPlumb(t *testing.T) {
if p.NotPlumbed {
t.Fatal("Normal method called for hasPlumb")
}
}
type onlyPlumb struct {
Ctx *Context
Plumbed string
}
func (h *onlyPlumb) Setup(sub *Subcommand) {
sub.SetPlumb("Plumber")
}
func (h *onlyPlumb) Plumber(_ *gateway.MessageCreateEvent, c RawArguments) error {
h.Plumbed = string(c)
return nil
}
func TestSubcommandOnlyPlumb(t *testing.T) {
var state = &state.State{
Store: state.NewDefaultStore(nil),
}
c, err := New(state, &testc{})
if err != nil {
t.Fatal("Failed to create new context:", err)
}
c.HasPrefix = NewPrefix("")
p := &onlyPlumb{}
_, err = c.RegisterSubcommand(p)
if err != nil {
t.Fatal("Failed to register hasPlumb:", err)
}
// Try call exactly what's in the Plumb example:
m := &gateway.MessageCreateEvent{
Message: discord.Message{
Content: "onlyPlumb test command",
},
}
if err := c.callCmd(m); err != nil {
t.Fatal("Failed to call message:", err)
}
if p.Plumbed != "test command" {
t.Fatal("Unexpected custom argument for plumbed:", p.Plumbed)

View File

@ -103,7 +103,7 @@ func TestContext(t *testing.T) {
})
t.Run("find commands", func(t *testing.T) {
cmd := ctx.FindMethod("", "NoArgs")
cmd := ctx.FindCommand("", "NoArgs")
if cmd == nil {
t.Fatal("Failed to find NoArgs")
}
@ -242,7 +242,7 @@ func TestContext(t *testing.T) {
t.Fatal("Unexpected call error:", err)
}
if cmd := ctx.FindMethod("testc", "Noop"); cmd == nil {
if cmd := ctx.FindCommand("testc", "Noop"); cmd == nil {
t.Fatal("Failed to find subcommand Noop")
}
})

View File

@ -82,9 +82,10 @@ type Subcommand struct {
// Commands can actually return either a string, an embed, or a
// SendMessageData, with error as the second argument.
// All registered method contexts, including commands:
Methods []*MethodContext
plumbed *MethodContext
// All registered method contexts:
Events []*MethodContext
Commands []*MethodContext
plumbed *MethodContext
// Global middlewares.
globalmws []*MiddlewareContext
@ -136,9 +137,9 @@ func (sub *Subcommand) NeedsName() {
sub.Command = lowerFirstLetter(sub.StructName)
}
// FindMethod finds the MethodContext. It panics if methodName is not found.
func (sub *Subcommand) FindMethod(methodName string) *MethodContext {
for _, c := range sub.Methods {
// FindCommand finds the MethodContext. It panics if methodName is not found.
func (sub *Subcommand) FindCommand(methodName string) *MethodContext {
for _, c := range sub.Commands {
if c.MethodName == methodName {
return c
}
@ -149,7 +150,7 @@ func (sub *Subcommand) FindMethod(methodName string) *MethodContext {
// ChangeCommandInfo changes the matched methodName's Command and Description.
// Empty means unchanged. The returned bool is true when the command is found.
func (sub *Subcommand) ChangeCommandInfo(methodName, cmd, desc string) bool {
for _, c := range sub.Methods {
for _, c := range sub.Commands {
if c.MethodName != methodName || !c.isEvent(typeMessageCreate) {
continue
}
@ -316,7 +317,11 @@ func (sub *Subcommand) parseCommands() error {
}
// Append.
sub.Methods = append(sub.Methods, cctx)
if cctx.event == typeMessageCreate {
sub.Commands = append(sub.Commands, cctx)
} else {
sub.Events = append(sub.Events, cctx)
}
}
return nil
@ -339,7 +344,7 @@ func (sub *Subcommand) AddMiddleware(methodName string, middleware interface{})
sub.globalmws = append(sub.globalmws, mw)
} else {
// Append middleware to that individual function.
sub.FindMethod(method).addMiddleware(mw)
sub.FindCommand(method).addMiddleware(mw)
}
}
}
@ -363,10 +368,10 @@ func (sub *Subcommand) eventCallers(evT reflect.Type) (callers []caller) {
}
// Search for specific handlers.
for _, cctx := range sub.Methods {
for _, cctx := range sub.Events {
// We only take middlewares and callers if the event matches and is not
// a MessageCreate. The other function already handles that.
if cctx.event != typeMessageCreate && cctx.isEvent(evT) {
if cctx.isEvent(evT) {
// Add the command's middlewares first.
for _, mw := range cctx.middlewares {
// Concrete struct to interface conversion done implicitly.
@ -387,7 +392,7 @@ func (sub *Subcommand) SetPlumb(methodName string) {
panic("SetPlumb called on a main command with sub.Command empty.")
}
method := sub.FindMethod(methodName)
method := sub.FindCommand(methodName)
method.Command = ""
sub.plumbed = method
}

View File

@ -29,8 +29,11 @@ func TestSubcommand(t *testing.T) {
}
// !!! CHANGE ME
if len(sub.Methods) < 8 {
t.Fatal("too low sub.Methods len", len(sub.Methods))
if len(sub.Commands) < 8 {
t.Fatal("too low sub.Methods len", len(sub.Commands))
}
if len(sub.Events) < 1 {
t.Fatal("No events found.")
}
var (
@ -39,7 +42,7 @@ func TestSubcommand(t *testing.T) {
foundNoArgs bool
)
for _, this := range sub.Methods {
for _, this := range sub.Commands {
switch this.Command {
case "send":
foundSend = true