1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-11-01 04:24:19 +00:00
arikawa/utils/bot/extras/shellwords/shellwords.go

123 lines
2 KiB
Go
Raw Normal View History

package shellwords
import (
"strings"
)
var escaper = strings.NewReplacer(
"__", "\\_\\_",
"\\", "\\\\",
)
// MissingCloseError is returned when the parsed line is missing a closing quote.
type MissingCloseError struct {
Position int
Words string // joined
}
func (e MissingCloseError) Error() string {
// Underline 7 characters around.
var start = e.Position
errstr := strings.Builder{}
errstr.WriteString("missing quote close")
if e.Words[start:] != "" {
errstr.WriteString(": ")
errstr.WriteString(escaper.Replace(e.Words[:start]))
errstr.WriteString("__")
errstr.WriteString(escaper.Replace(e.Words[start:]))
errstr.WriteString("__")
}
return errstr.String()
}
// Parse parses the given text to a slice of words.
2020-01-26 07:17:18 +00:00
func Parse(line string) ([]string, error) {
var args []string
2020-01-26 07:17:18 +00:00
var escaped, doubleQuoted, singleQuoted bool
var buf strings.Builder
buf.Grow(len(line))
got := false
2020-01-26 07:17:18 +00:00
cursor := 0
2020-01-26 07:17:18 +00:00
runes := []rune(line)
for _, r := range runes {
if escaped {
buf.WriteRune(r)
escaped = false
continue
}
if r == '\\' {
if singleQuoted {
buf.WriteRune(r)
} else {
escaped = true
}
continue
}
if isSpace(r) {
switch {
2020-01-26 07:17:18 +00:00
case singleQuoted, doubleQuoted:
buf.WriteRune(r)
case got:
cursor += buf.Len()
args = append(args, buf.String())
buf.Reset()
got = false
}
continue
}
switch r {
2021-01-15 00:23:14 +00:00
case '"', '“', '”':
if !singleQuoted {
if doubleQuoted {
got = true
}
doubleQuoted = !doubleQuoted
continue
}
2021-01-15 00:23:14 +00:00
case '\'', '`', '', '':
if !doubleQuoted {
if singleQuoted {
got = true
}
singleQuoted = !singleQuoted
continue
}
}
got = true
buf.WriteRune(r)
}
if got {
args = append(args, buf.String())
}
2020-01-26 07:17:18 +00:00
if escaped || singleQuoted || doubleQuoted {
return args, MissingCloseError{
Position: cursor + buf.Len(),
Words: strings.Join(args, " "),
2020-01-26 07:17:18 +00:00
}
}
return args, nil
}
2020-01-26 07:17:18 +00:00
func isSpace(r rune) bool {
switch r {
case ' ', '\t', '\r', '\n', ' ':
2020-01-26 07:17:18 +00:00
return true
}
return false
}