Bot: Allow hanging quotes if command has a custom parser
This commit is contained in:
parent
94cca0adca
commit
3db68bcb0e
|
@ -72,6 +72,8 @@ func (r ArgumentParts) Usage() string {
|
|||
// CustomParser has a CustomParse method, which would be passed in the full
|
||||
// message content with the prefix and command trimmed. This is used
|
||||
// for commands that require more advanced parsing than the default parser.
|
||||
//
|
||||
// Keep in mind that this does not trim arguments before it.
|
||||
type CustomParser interface {
|
||||
CustomParse(arguments string) error
|
||||
}
|
||||
|
|
|
@ -125,13 +125,12 @@ func (ctx *Context) callMessageCreate(mc *gateway.MessageCreateEvent, value refl
|
|||
}
|
||||
|
||||
// parse arguments
|
||||
parts, err := ctx.ParseArgs(content)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse command")
|
||||
}
|
||||
parts, parseErr := ctx.ParseArgs(content)
|
||||
// We're not checking parse errors yet, as raw arguments may be able to
|
||||
// ignore it.
|
||||
|
||||
if len(parts) == 0 {
|
||||
return nil // ???
|
||||
return parseErr
|
||||
}
|
||||
|
||||
// Find the command and subcommand.
|
||||
|
@ -258,6 +257,10 @@ func (ctx *Context) callMessageCreate(mc *gateway.MessageCreateEvent, value refl
|
|||
|
||||
// If the argument wants all arguments in string:
|
||||
case last.custom != nil:
|
||||
// Ignore parser errors. This allows custom commands sliced away to
|
||||
// have erroneous hanging quotes.
|
||||
parseErr = nil
|
||||
|
||||
// Manual string seeking is a must here. This is because the string
|
||||
// could contain multiple whitespaces, and the parser would not
|
||||
// count them.
|
||||
|
@ -268,9 +271,17 @@ func (ctx *Context) callMessageCreate(mc *gateway.MessageCreateEvent, value refl
|
|||
}
|
||||
|
||||
// Seek to the string.
|
||||
if i := strings.Index(content, seekTo); i > -1 {
|
||||
var i = strings.Index(content, seekTo)
|
||||
// Edge case if the subcommand is the same as the command.
|
||||
if cmd.Command == sub.Command {
|
||||
// Seek again past the command.
|
||||
i = strings.Index(content[i+len(seekTo):], seekTo)
|
||||
}
|
||||
|
||||
if i > -1 {
|
||||
// Seek past the substring.
|
||||
i += len(seekTo)
|
||||
|
||||
content = strings.TrimSpace(content[i:])
|
||||
}
|
||||
|
||||
|
@ -292,6 +303,11 @@ func (ctx *Context) callMessageCreate(mc *gateway.MessageCreateEvent, value refl
|
|||
argv = append(argv, v)
|
||||
}
|
||||
|
||||
// Check for parsing errors after parsing arguments.
|
||||
if parseErr != nil {
|
||||
return parseErr
|
||||
}
|
||||
|
||||
Call:
|
||||
// call the function and parse the error return value
|
||||
v, err := cmd.call(value, argv...)
|
||||
|
|
|
@ -1,19 +1,31 @@
|
|||
package shellwords
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ErrParse struct {
|
||||
Line string
|
||||
Position string
|
||||
Position int
|
||||
ErrorStart,
|
||||
ErrorPart,
|
||||
ErrorEnd string
|
||||
}
|
||||
|
||||
func (e ErrParse) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Unexpected quote or escape: %s__%s__%s",
|
||||
e.ErrorStart, e.ErrorPart, e.ErrorEnd,
|
||||
)
|
||||
}
|
||||
|
||||
// Parse parses the given text to a slice of words.
|
||||
func Parse(line string) ([]string, error) {
|
||||
var args []string
|
||||
buf := ""
|
||||
var escaped, doubleQuoted, singleQuoted bool
|
||||
backtick := ""
|
||||
|
||||
var buf strings.Builder
|
||||
buf.Grow(len(line))
|
||||
|
||||
got := false
|
||||
cursor := 0
|
||||
|
@ -22,14 +34,14 @@ func Parse(line string) ([]string, error) {
|
|||
|
||||
for _, r := range runes {
|
||||
if escaped {
|
||||
buf += string(r)
|
||||
buf.WriteRune(r)
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
|
||||
if r == '\\' {
|
||||
if singleQuoted {
|
||||
buf += string(r)
|
||||
buf.WriteRune(r)
|
||||
} else {
|
||||
escaped = true
|
||||
}
|
||||
|
@ -39,12 +51,11 @@ func Parse(line string) ([]string, error) {
|
|||
if isSpace(r) {
|
||||
switch {
|
||||
case singleQuoted, doubleQuoted:
|
||||
buf += string(r)
|
||||
backtick += string(r)
|
||||
buf.WriteRune(r)
|
||||
case got:
|
||||
cursor += len(buf)
|
||||
args = append(args, buf)
|
||||
buf = ""
|
||||
cursor += buf.Len()
|
||||
args = append(args, buf.String())
|
||||
buf.Reset()
|
||||
got = false
|
||||
}
|
||||
continue
|
||||
|
@ -59,22 +70,28 @@ func Parse(line string) ([]string, error) {
|
|||
doubleQuoted = !doubleQuoted
|
||||
continue
|
||||
}
|
||||
case '\'':
|
||||
case '\'', '`':
|
||||
if !doubleQuoted {
|
||||
if singleQuoted {
|
||||
got = true
|
||||
}
|
||||
|
||||
// // If this is a backtick, then write it.
|
||||
// if r == '`' {
|
||||
// buf.WriteByte('`')
|
||||
// }
|
||||
|
||||
singleQuoted = !singleQuoted
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
got = true
|
||||
buf += string(r)
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
|
||||
if got {
|
||||
args = append(args, buf)
|
||||
args = append(args, buf.String())
|
||||
}
|
||||
|
||||
if escaped || singleQuoted || doubleQuoted {
|
||||
|
@ -83,18 +100,15 @@ func Parse(line string) ([]string, error) {
|
|||
pos = cursor + 5
|
||||
start = string(runes[max(cursor-100, 0) : pos-1])
|
||||
end = string(runes[pos+1 : min(cursor+100, len(runes))])
|
||||
part = ""
|
||||
part = string(runes[max(pos-1, 0):min(len(runes), pos+2)])
|
||||
)
|
||||
|
||||
for i := pos - 1; i >= 0 && i < len(runes) && i < pos+2; i++ {
|
||||
if runes[i] == '\\' {
|
||||
part += "\\"
|
||||
}
|
||||
part += string(runes[i])
|
||||
return args, &ErrParse{
|
||||
Position: cursor,
|
||||
ErrorStart: start,
|
||||
ErrorPart: part,
|
||||
ErrorEnd: end,
|
||||
}
|
||||
|
||||
return nil, errors.New(
|
||||
"Unexpected quote or escape: " + start + "__" + part + "__" + end)
|
||||
}
|
||||
|
||||
return args, nil
|
||||
|
@ -102,7 +116,7 @@ func Parse(line string) ([]string, error) {
|
|||
|
||||
func isSpace(r rune) bool {
|
||||
switch r {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
case ' ', '\t', '\r', '\n', ' ':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package shellwords
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type wordsTest struct {
|
||||
line string
|
||||
args []string
|
||||
doErr bool
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
var tests = []wordsTest{
|
||||
{
|
||||
`this is a "test"`,
|
||||
[]string{"this", "is", "a", "test"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
`hanging "quote`,
|
||||
[]string{"hanging", "quote"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
`Hello, 世界`,
|
||||
[]string{"Hello,", "世界"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"this is `inline code`",
|
||||
[]string{"this", "is", "inline code"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"how about a ```go\npackage main\n```\ngo code?",
|
||||
[]string{"how", "about", "a", "go\npackage main\n", "go", "code?"},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
w, err := Parse(test.line)
|
||||
if err != nil && !test.doErr {
|
||||
t.Errorf("Error at %q: %v", test.line, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(w, test.args) {
|
||||
t.Errorf("Inequality:\n%#v !=\n%#v", w, test.args)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue