mirror of
https://github.com/diamondburned/arikawa.git
synced 2024-11-01 12:34:28 +00:00
123 lines
2 KiB
Go
123 lines
2 KiB
Go
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.
|
||
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, MissingCloseError{
|
||
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
|
||
}
|