Major updates/patches, functionality, api
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Tyler
2020-07-09 21:01:29 -04:00
parent afb79eaceb
commit b52b38179a
32 changed files with 1798 additions and 302 deletions

88
commands/arguments.go Normal file
View File

@ -0,0 +1,88 @@
package commands
import "strings"
const (
ArgumentTypeBasic = iota
ArgumentTypeUserMention
ArgumentTypeChannelMention
)
func parseCommandUsage(prefix string) *CommandHolder {
holder := &CommandHolder{}
holder.Name = prefix
holder.Usage = prefix
if idx := strings.Index(prefix, " "); idx != -1 {
holder.Name = prefix[0:idx]
prefix = prefix[idx+1:]
// Parse out command arguments, example:
// test <arg1> [optional arg2]
// Walk through string, match < and >, [ and ]
holder.Arguments = make(map[string]*CommandArgument)
str := prefix
var name string
var index int
for {
if len(str) == 0 {
break
}
ch := str[0]
if ch == '<' || ch == '[' {
// Scan until closing arrow or end of string
for i := 1; i < len(str); i++ {
if (str[i] == '>' || str[i] == ']') && str[i-1] != '\\' {
name = str[1:i]
if i+2 < len(str) {
str = str[i+2:]
} else {
str = ""
}
required := false
if ch == '<' {
required = true
holder.RequiredArgumentCount++
}
t := ArgumentTypeBasic
if name[0] == '@' {
t = ArgumentTypeUserMention
name = name[1:]
} else if name[0] == '#' {
t = ArgumentTypeChannelMention
name = name[1:]
}
holder.Arguments[name] = &CommandArgument{
Index: index,
Name: name,
Required: required,
Type: t,
}
index++
break
}
}
}
}
holder.ArgumentCount = len(holder.Arguments)
}
return holder
}

15
commands/context.go Normal file
View File

@ -0,0 +1,15 @@
package commands
type CommandContext struct {
holder *CommandHolder
User string
Prefix string
Command string
ArgumentString string
Arguments []string
ArgumentCount int
Reply func(text string)
}

69
commands/definition.go Normal file
View File

@ -0,0 +1,69 @@
package commands
import "errors"
type CommandHolder struct {
Name string
Usage string
Handler CommandHandler
Permission int
Arguments map[string]*CommandArgument
ArgumentCount int
RequiredArgumentCount int
}
type CommandArgument struct {
Index int
Name string
Required bool
Type int
}
func (c *CommandHolder) Call(ctx *CommandContext) {
ctx.holder = c
if c.ArgumentCount > 0 {
// Arguments are cached, construct usage
if err := c.Validate(ctx); err != nil {
if err == UsageError {
ctx.Reply("Usage: " + ctx.Prefix + c.Usage)
} else {
ctx.Reply(err.Error())
}
return
}
}
// Use a goroutine to avoid any blocking operations
c.Handler(ctx)
}
var (
UsageError = errors.New("usage")
)
func (c *CommandHolder) Validate(ctx *CommandContext) error {
if ctx.ArgumentCount < c.RequiredArgumentCount {
return UsageError
}
var argValue string
for _, arg := range c.Arguments {
if ctx.ArgumentCount < arg.Index+1 {
break
}
if !arg.Required {
continue
}
argValue = ctx.Arguments[arg.Index]
if argValue == "" {
return errors.New("The " + arg.Name + " argument is required.")
}
}
return nil
}

35
commands/dispatcher.go Normal file
View File

@ -0,0 +1,35 @@
package commands
import "strings"
type CommandHandler func(ctx *CommandContext)
var (
commandMap = make(map[string]*CommandHolder)
)
func Register(prefix string, f CommandHandler) {
h := parseCommandUsage(prefix)
h.Handler = f
commandMap[h.Name] = h
}
func Find(prefix, name, full string) *CommandHolder {
if strings.Index(name, prefix) == 0 {
name = name[len(prefix):]
if holder := Handler(name); holder != nil {
return holder
}
}
return nil
}
func Handler(name string) *CommandHolder {
if holder, ok := commandMap[name]; ok {
return holder
}
return nil
}

71
commands/parser.go Normal file
View File

@ -0,0 +1,71 @@
package commands
type SpaceTokenizer struct {
Input string
}
func (t *SpaceTokenizer) NextToken() string {
if len(t.Input) == 0 {
return ""
}
ch := t.Input[0]
if ch == '"' {
// Scan until closing quote or end of string
for i := 1; i < len(t.Input); i++ {
if t.Input[i] == '"' && t.Input[i-1] != '\\' {
ret := t.Input[1:i]
if i+2 < len(t.Input) {
t.Input = t.Input[i+2:]
} else {
t.Input = ""
}
return ret
}
}
} else {
for i := 0; i < len(t.Input); i++ {
if t.Input[i] == ' ' {
ret := t.Input[0:i]
if i+1 < len(t.Input) {
t.Input = t.Input[i+1:]
} else {
t.Input = ""
}
return ret
}
}
}
ret := t.Input
t.Input = ""
return ret
}
func (t *SpaceTokenizer) Empty() bool {
return t.Input == ""
}
func NewSpaceTokenizer(input string) *SpaceTokenizer {
return &SpaceTokenizer{Input: input}
}
func ParseCommandArguments(command string) []string {
tokenizer := NewSpaceTokenizer(command)
arguments := make([]string, 0)
for {
if tokenizer.Empty() {
break
}
arguments = append(arguments, tokenizer.NextToken())
}
return arguments
}

69
commands/parser_test.go Normal file
View File

@ -0,0 +1,69 @@
package commands
import (
"regexp"
"testing"
)
func TestParseCommandArguments(t *testing.T) {
args := ParseCommandArguments("normal \"testing quoted\" and normal \"end closed\"")
expected := []string{"normal", "testing quoted", "and", "normal", "end closed"}
for i := 0; i < len(expected); i++ {
if args[i] != expected[i] {
t.Errorf("Expected %s, got %s at index %d", expected[i], args[i], i)
}
}
}
func TestDispatching(t *testing.T) {
Register("testing", func(ctx *CommandContext) {
// Something
})
if handler := Find("!", "!testing", "testing"); handler == nil {
t.Error("Expected handler for testing")
}
}
func TestRegexpDispatching(t *testing.T) {
RegisterMatch(regexp.MustCompile("^compliment"), func(ctx *CommandContext) {
// Something
})
if handler := Find("", "compliment", "compliment me"); handler == nil {
t.Error("Expected handler for compliment")
}
}
func TestArgumentParsing(t *testing.T) {
holder := parseCommandUsage("test <arg1> <arg2> [arg3]")
if holder.Name != "test" {
t.Error("Unexpected command name")
}
if len(holder.Arguments) != 3 {
t.Error("Invalid number of command arguments:", len(holder.Arguments))
}
if holder.RequiredArgumentCount != 2 {
t.Error("Invalid required argument count")
}
}
func TestArgumentDispatching(t *testing.T) {
holder := parseCommandUsage("test <arg1> <arg2> [arg3]")
if holder.Name != "test" {
t.Error("Unexpected command name")
}
if len(holder.Arguments) != 3 {
t.Error("Invalid number of command arguments:", len(holder.Arguments))
}
if holder.RequiredArgumentCount != 2 {
t.Error("Invalid required argument count")
}
}