2020-01-19 06:06:00 +00:00
|
|
|
package bot
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
2020-01-25 21:01:05 +00:00
|
|
|
"strings"
|
2020-01-26 05:58:06 +00:00
|
|
|
|
2020-01-26 06:15:39 +00:00
|
|
|
"github.com/diamondburned/arikawa/bot/shellwords"
|
2020-01-19 06:06:00 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type argumentValueFn func(string) (reflect.Value, error)
|
|
|
|
|
2020-01-26 05:43:42 +00:00
|
|
|
// Parser implements a Parse(string) method for data structures that can be
|
2020-01-19 06:06:00 +00:00
|
|
|
// used as arguments.
|
2020-01-26 05:43:42 +00:00
|
|
|
type Parser interface {
|
2020-01-19 06:06:00 +00:00
|
|
|
Parse(string) error
|
|
|
|
}
|
|
|
|
|
2020-04-09 04:25:50 +00:00
|
|
|
// Usager is used in place of the automatically parsed struct name for Parser
|
|
|
|
// and other interfaces.
|
|
|
|
type Usager interface {
|
|
|
|
Usage() string
|
|
|
|
}
|
|
|
|
|
2020-01-26 05:43:42 +00:00
|
|
|
// ManualParser has a ParseContent(string) method. If the library sees
|
2020-01-19 06:06:00 +00:00
|
|
|
// this for an argument, it will send all of the arguments (including the
|
|
|
|
// command) into the method. If used, this should be the only argument followed
|
|
|
|
// after the Message Create event. Any more and the router will ignore.
|
2020-01-26 05:43:42 +00:00
|
|
|
type ManualParser interface {
|
2020-01-19 06:06:00 +00:00
|
|
|
// $0 will have its prefix trimmed.
|
|
|
|
ParseContent([]string) error
|
|
|
|
}
|
|
|
|
|
2020-01-24 03:17:03 +00:00
|
|
|
// RawArguments implements ManualParseable, in case you want to implement a
|
|
|
|
// custom argument parser. It borrows the library's argument parser.
|
2020-01-19 06:06:00 +00:00
|
|
|
type RawArguments struct {
|
2020-01-25 21:01:05 +00:00
|
|
|
Command string
|
2020-01-19 06:06:00 +00:00
|
|
|
Arguments []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *RawArguments) ParseContent(args []string) error {
|
2020-01-25 21:01:05 +00:00
|
|
|
r.Command = args[0]
|
|
|
|
|
|
|
|
if len(args) > 1 {
|
|
|
|
r.Arguments = args[1:]
|
|
|
|
}
|
|
|
|
|
2020-01-19 06:06:00 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-01-25 22:07:49 +00:00
|
|
|
func (r RawArguments) Arg(n int) string {
|
2020-01-25 21:01:05 +00:00
|
|
|
if n < 0 || n >= len(r.Arguments) {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return r.Arguments[n]
|
|
|
|
}
|
|
|
|
|
2020-01-25 22:07:49 +00:00
|
|
|
func (r RawArguments) After(n int) string {
|
2020-01-25 21:43:25 +00:00
|
|
|
if n < 0 || n >= len(r.Arguments) {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Join(r.Arguments[n:], " ")
|
|
|
|
}
|
|
|
|
|
2020-01-25 22:07:49 +00:00
|
|
|
func (r RawArguments) String() string {
|
2020-01-25 21:01:05 +00:00
|
|
|
return r.Command + " " + strings.Join(r.Arguments, " ")
|
|
|
|
}
|
|
|
|
|
2020-01-25 22:07:49 +00:00
|
|
|
func (r RawArguments) Length() int {
|
2020-01-25 21:43:25 +00:00
|
|
|
return len(r.Arguments)
|
|
|
|
}
|
|
|
|
|
2020-01-26 05:43:42 +00:00
|
|
|
// CustomParser has a CustomParse method, which would be passed in the full
|
2020-05-04 01:33:24 +00:00
|
|
|
// message content with the prefix trimmed, but not the command. This is used
|
|
|
|
// for commands that require more advanced parsing than the default parser.
|
2020-01-26 05:43:42 +00:00
|
|
|
type CustomParser interface {
|
|
|
|
CustomParse(content string) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// CustomArguments implements the CustomParser interface, which sets the string
|
2020-05-04 01:33:24 +00:00
|
|
|
// exactly. This string contains the command, subcommand, and all its arguments.
|
|
|
|
// It does not contain the prefix.
|
2020-01-26 05:43:42 +00:00
|
|
|
type Content string
|
|
|
|
|
|
|
|
func (c *Content) CustomParse(content string) error {
|
|
|
|
*c = Content(content)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-01-24 03:17:03 +00:00
|
|
|
// Argument is each argument in a method.
|
|
|
|
type Argument struct {
|
|
|
|
String string
|
2020-01-25 22:07:49 +00:00
|
|
|
// Rule: pointer for structs, direct for primitives
|
2020-05-03 22:59:10 +00:00
|
|
|
rtype reflect.Type
|
2020-01-25 22:07:49 +00:00
|
|
|
|
2020-01-26 05:43:42 +00:00
|
|
|
// indicates if the type is referenced, meaning it's a pointer but not the
|
|
|
|
// original call.
|
|
|
|
pointer bool
|
|
|
|
|
2020-01-25 22:07:49 +00:00
|
|
|
// if nil, then manual
|
2020-01-24 03:17:03 +00:00
|
|
|
fn argumentValueFn
|
2020-01-26 05:43:42 +00:00
|
|
|
manual *reflect.Method
|
|
|
|
custom *reflect.Method
|
2020-01-24 03:17:03 +00:00
|
|
|
}
|
|
|
|
|
2020-05-03 22:59:10 +00:00
|
|
|
func (a *Argument) Type() reflect.Type {
|
|
|
|
return a.rtype
|
|
|
|
}
|
|
|
|
|
2020-01-26 06:15:39 +00:00
|
|
|
var ShellwordsEscaper = strings.NewReplacer(
|
|
|
|
"\\", "\\\\",
|
|
|
|
)
|
|
|
|
|
2020-01-26 05:58:06 +00:00
|
|
|
var ParseArgs = func(args string) ([]string, error) {
|
|
|
|
return shellwords.Parse(args)
|
|
|
|
}
|
|
|
|
|
2020-01-19 06:06:00 +00:00
|
|
|
// nilV, only used to return an error
|
|
|
|
var nilV = reflect.Value{}
|
|
|
|
|
2020-05-04 01:33:24 +00:00
|
|
|
func newArgument(t reflect.Type, variadic bool) (*Argument, error) {
|
2020-05-03 22:59:10 +00:00
|
|
|
// Allow array types if varidic is true.
|
|
|
|
if variadic && t.Kind() == reflect.Slice {
|
|
|
|
t = t.Elem()
|
|
|
|
}
|
|
|
|
|
2020-01-26 05:43:42 +00:00
|
|
|
var typeI = t
|
|
|
|
var ptr = false
|
|
|
|
|
|
|
|
if t.Kind() != reflect.Ptr {
|
|
|
|
typeI = reflect.PtrTo(t)
|
|
|
|
ptr = true
|
|
|
|
}
|
|
|
|
|
2020-05-04 01:33:24 +00:00
|
|
|
// This shouldn't be varidic.
|
|
|
|
if !variadic && typeI.Implements(typeICusP) {
|
|
|
|
mt, _ := typeI.MethodByName("CustomParse")
|
|
|
|
|
|
|
|
// TODO: maybe ish?
|
|
|
|
if t.Kind() == reflect.Ptr {
|
|
|
|
t = t.Elem()
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Argument{
|
|
|
|
String: t.String(),
|
|
|
|
rtype: t,
|
|
|
|
pointer: ptr,
|
|
|
|
custom: &mt,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// This shouldn't be variadic either.
|
|
|
|
if !variadic && typeI.Implements(typeIManP) {
|
|
|
|
mt, _ := typeI.MethodByName("ParseContent")
|
|
|
|
|
|
|
|
if t.Kind() == reflect.Ptr {
|
|
|
|
t = t.Elem()
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Argument{
|
|
|
|
String: t.String(),
|
|
|
|
rtype: t,
|
|
|
|
pointer: ptr,
|
|
|
|
manual: &mt,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-01-26 05:43:42 +00:00
|
|
|
if typeI.Implements(typeIParser) {
|
|
|
|
mt, ok := typeI.MethodByName("Parse")
|
2020-01-19 06:06:00 +00:00
|
|
|
if !ok {
|
|
|
|
panic("BUG: type IParser does not implement Parse")
|
|
|
|
}
|
|
|
|
|
2020-01-26 05:43:42 +00:00
|
|
|
avfn := func(input string) (reflect.Value, error) {
|
2020-02-05 04:29:45 +00:00
|
|
|
v := reflect.New(typeI.Elem())
|
2020-01-19 06:06:00 +00:00
|
|
|
|
|
|
|
ret := mt.Func.Call([]reflect.Value{
|
|
|
|
v, reflect.ValueOf(input),
|
|
|
|
})
|
|
|
|
|
2020-01-26 09:06:54 +00:00
|
|
|
_, err := errorReturns(ret)
|
|
|
|
if err != nil {
|
2020-01-19 06:06:00 +00:00
|
|
|
return nilV, err
|
|
|
|
}
|
|
|
|
|
2020-01-26 05:43:42 +00:00
|
|
|
if ptr {
|
|
|
|
v = v.Elem()
|
|
|
|
}
|
|
|
|
|
2020-01-19 06:06:00 +00:00
|
|
|
return v, nil
|
2020-01-26 05:43:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &Argument{
|
2020-04-09 04:25:50 +00:00
|
|
|
String: fromUsager(typeI),
|
2020-05-03 22:59:10 +00:00
|
|
|
rtype: typeI,
|
2020-01-26 05:43:42 +00:00
|
|
|
pointer: ptr,
|
|
|
|
fn: avfn,
|
2020-01-19 06:06:00 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var fn argumentValueFn
|
|
|
|
|
|
|
|
switch t.Kind() {
|
|
|
|
case reflect.String:
|
|
|
|
fn = func(s string) (reflect.Value, error) {
|
|
|
|
return reflect.ValueOf(s), nil
|
|
|
|
}
|
|
|
|
|
2020-05-03 22:59:10 +00:00
|
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
2020-01-19 06:06:00 +00:00
|
|
|
fn = func(s string) (reflect.Value, error) {
|
|
|
|
i, err := strconv.ParseInt(s, 10, 64)
|
|
|
|
return quickRet(i, err, t)
|
|
|
|
}
|
|
|
|
|
2020-05-03 22:59:10 +00:00
|
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
2020-01-19 06:06:00 +00:00
|
|
|
fn = func(s string) (reflect.Value, error) {
|
|
|
|
u, err := strconv.ParseUint(s, 10, 64)
|
|
|
|
return quickRet(u, err, t)
|
|
|
|
}
|
|
|
|
|
|
|
|
case reflect.Float32, reflect.Float64:
|
|
|
|
fn = func(s string) (reflect.Value, error) {
|
|
|
|
f, err := strconv.ParseFloat(s, 64)
|
|
|
|
return quickRet(f, err, t)
|
|
|
|
}
|
|
|
|
|
|
|
|
case reflect.Bool:
|
|
|
|
fn = func(s string) (reflect.Value, error) {
|
|
|
|
switch s {
|
2020-02-05 04:29:45 +00:00
|
|
|
case "True", "TRUE", "true", "T", "t", "yes", "y", "Y", "1":
|
2020-01-19 06:06:00 +00:00
|
|
|
return reflect.ValueOf(true), nil
|
2020-02-05 04:29:45 +00:00
|
|
|
case "False", "FALSE", "false", "F", "f", "no", "n", "N", "0":
|
2020-01-19 06:06:00 +00:00
|
|
|
return reflect.ValueOf(false), nil
|
|
|
|
default:
|
2020-05-03 22:59:10 +00:00
|
|
|
return nilV, errors.New("invalid bool [true|false]")
|
2020-01-19 06:06:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if fn == nil {
|
|
|
|
return nil, errors.New("invalid type: " + t.String())
|
|
|
|
}
|
|
|
|
|
2020-01-26 05:43:42 +00:00
|
|
|
return &Argument{
|
|
|
|
String: t.String(),
|
2020-05-03 22:59:10 +00:00
|
|
|
rtype: t,
|
2020-01-26 05:43:42 +00:00
|
|
|
fn: fn,
|
|
|
|
}, nil
|
2020-01-19 06:06:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func quickRet(v interface{}, err error, t reflect.Type) (reflect.Value, error) {
|
|
|
|
if err != nil {
|
|
|
|
return nilV, err
|
|
|
|
}
|
|
|
|
|
|
|
|
rv := reflect.ValueOf(v)
|
|
|
|
|
|
|
|
if t == nil {
|
|
|
|
return rv, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv.Convert(t), nil
|
|
|
|
}
|
2020-04-09 04:25:50 +00:00
|
|
|
|
|
|
|
func fromUsager(typeI reflect.Type) string {
|
|
|
|
if typeI.Implements(typeIUsager) {
|
|
|
|
mt, ok := typeI.MethodByName("Usage")
|
|
|
|
if !ok {
|
|
|
|
panic("BUG: type IUsager does not implement Usage")
|
|
|
|
}
|
2020-05-03 22:59:10 +00:00
|
|
|
|
2020-04-09 04:25:50 +00:00
|
|
|
vs := mt.Func.Call([]reflect.Value{reflect.New(typeI.Elem())})
|
|
|
|
return vs[0].String()
|
|
|
|
}
|
2020-05-03 22:59:10 +00:00
|
|
|
|
2020-04-09 04:25:50 +00:00
|
|
|
s := strings.Split(typeI.String(), ".")
|
|
|
|
return s[len(s)-1]
|
|
|
|
}
|