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