arikawa/bot/extras/shellwords/shellwords.go

150 lines
2.3 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package shellwords
import (
"fmt"
"strings"
)
// WordOffset is the offset from the position cursor to print on the error.
const WordOffset = 7
var escaper = strings.NewReplacer(
"`", "\\`",
"@", "\\@",
"\\", "\\\\",
)
type ErrParse struct {
Position int
Words string // joined
}
func (e ErrParse) Error() string {
// Magic number 5.
var a = max(0, e.Position-WordOffset)
var b = min(len(e.Words), e.Position+WordOffset)
var word = e.Words[a:b]
var uidx = e.Position - a
errstr := strings.Builder{}
errstr.WriteString("Unexpected quote or escape")
// Do a bound check.
if uidx+1 > len(word) {
// Invalid.
errstr.WriteString(".")
return errstr.String()
}
// Write the pre-underline part.
fmt.Fprintf(
&errstr, ": %s__%s__",
escaper.Replace(word[:uidx]),
escaper.Replace(string(word[uidx:])),
)
return errstr.String()
}
// Parse parses the given text to a slice of words.
func Parse(line string) ([]string, error) {
var args []string
var escaped, doubleQuoted, singleQuoted bool
var buf strings.Builder
buf.Grow(len(line))
got := false
cursor := 0
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 {
case singleQuoted, doubleQuoted:
buf.WriteRune(r)
case got:
cursor += buf.Len()
args = append(args, buf.String())
buf.Reset()
got = false
}
continue
}
switch r {
case '"':
if !singleQuoted {
if doubleQuoted {
got = true
}
doubleQuoted = !doubleQuoted
continue
}
case '\'', '`':
if !doubleQuoted {
if singleQuoted {
got = true
}
singleQuoted = !singleQuoted
continue
}
}
got = true
buf.WriteRune(r)
}
if got {
args = append(args, buf.String())
}
if escaped || singleQuoted || doubleQuoted {
return args, &ErrParse{
Position: cursor + buf.Len(),
Words: strings.Join(args, " "),
}
}
return args, nil
}
func isSpace(r rune) bool {
switch r {
case ' ', '\t', '\r', '\n', ' ':
return true
}
return false
}
func min(i, j int) int {
if i < j {
return i
}
return j
}
func max(i, j int) int {
if i < j {
return j
}
return i
}