You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
buildx/vendor/github.com/mattn/go-shellwords/shellwords.go

318 lines
5.8 KiB
Go

package shellwords
import (
"bytes"
"errors"
"os"
"strings"
"unicode"
)
var (
ParseEnv bool = false
ParseBacktick bool = false
)
func isSpace(r rune) bool {
switch r {
case ' ', '\t', '\r', '\n':
return true
}
return false
}
func replaceEnv(getenv func(string) string, s string) string {
if getenv == nil {
getenv = os.Getenv
}
var buf bytes.Buffer
rs := []rune(s)
for i := 0; i < len(rs); i++ {
r := rs[i]
if r == '\\' {
i++
if i == len(rs) {
break
}
buf.WriteRune(rs[i])
continue
} else if r == '$' {
i++
if i == len(rs) {
buf.WriteRune(r)
break
}
if rs[i] == 0x7b {
i++
p := i
for ; i < len(rs); i++ {
r = rs[i]
if r == '\\' {
i++
if i == len(rs) {
return s
}
continue
}
if r == 0x7d || (!unicode.IsLetter(r) && r != '_' && !unicode.IsDigit(r)) {
break
}
}
if r != 0x7d {
return s
}
if i > p {
buf.WriteString(getenv(s[p:i]))
}
} else {
p := i
for ; i < len(rs); i++ {
r := rs[i]
if r == '\\' {
i++
if i == len(rs) {
return s
}
continue
}
if !unicode.IsLetter(r) && r != '_' && !unicode.IsDigit(r) {
break
}
}
if i > p {
buf.WriteString(getenv(s[p:i]))
i--
} else {
buf.WriteString(s[p:])
}
}
} else {
buf.WriteRune(r)
}
}
return buf.String()
}
type Parser struct {
ParseEnv bool
ParseBacktick bool
Position int
Dir string
// If ParseEnv is true, use this for getenv.
// If nil, use os.Getenv.
Getenv func(string) string
}
func NewParser() *Parser {
return &Parser{
ParseEnv: ParseEnv,
ParseBacktick: ParseBacktick,
Position: 0,
Dir: "",
}
}
type argType int
const (
argNo argType = iota
argSingle
argQuoted
)
func (p *Parser) Parse(line string) ([]string, error) {
args := []string{}
buf := ""
var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
backtick := ""
pos := -1
got := argNo
i := -1
loop:
for _, r := range line {
i++
if escaped {
buf += string(r)
escaped = false
got = argSingle
continue
}
if r == '\\' {
if singleQuoted {
buf += string(r)
} else {
escaped = true
}
continue
}
if isSpace(r) {
if singleQuoted || doubleQuoted || backQuote || dollarQuote {
buf += string(r)
backtick += string(r)
} else if got != argNo {
if p.ParseEnv {
if got == argSingle {
parser := &Parser{ParseEnv: false, ParseBacktick: false, Position: 0, Dir: p.Dir}
strs, err := parser.Parse(replaceEnv(p.Getenv, buf))
if err != nil {
return nil, err
}
args = append(args, strs...)
} else {
args = append(args, replaceEnv(p.Getenv, buf))
}
} else {
args = append(args, buf)
}
buf = ""
got = argNo
}
continue
}
switch r {
case '`':
if !singleQuoted && !doubleQuoted && !dollarQuote {
if p.ParseBacktick {
if backQuote {
out, err := shellRun(backtick, p.Dir)
if err != nil {
return nil, err
}
buf = buf[:len(buf)-len(backtick)] + out
}
backtick = ""
backQuote = !backQuote
continue
}
backtick = ""
backQuote = !backQuote
}
case ')':
if !singleQuoted && !doubleQuoted && !backQuote {
if p.ParseBacktick {
if dollarQuote {
out, err := shellRun(backtick, p.Dir)
if err != nil {
return nil, err
}
buf = buf[:len(buf)-len(backtick)-2] + out
}
backtick = ""
dollarQuote = !dollarQuote
continue
}
backtick = ""
dollarQuote = !dollarQuote
}
case '(':
if !singleQuoted && !doubleQuoted && !backQuote {
if !dollarQuote && strings.HasSuffix(buf, "$") {
dollarQuote = true
buf += "("
continue
} else {
return nil, errors.New("invalid command line string")
}
}
case '"':
if !singleQuoted && !dollarQuote {
if doubleQuoted {
got = argQuoted
}
doubleQuoted = !doubleQuoted
continue
}
case '\'':
if !doubleQuoted && !dollarQuote {
if singleQuoted {
got = argQuoted
}
singleQuoted = !singleQuoted
continue
}
case ';', '&', '|', '<', '>':
if !(escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote) {
if r == '>' && len(buf) > 0 {
if c := buf[0]; '0' <= c && c <= '9' {
i -= 1
got = argNo
}
}
pos = i
break loop
}
}
got = argSingle
buf += string(r)
if backQuote || dollarQuote {
backtick += string(r)
}
}
if got != argNo {
if p.ParseEnv {
if got == argSingle {
parser := &Parser{ParseEnv: false, ParseBacktick: false, Position: 0, Dir: p.Dir}
strs, err := parser.Parse(replaceEnv(p.Getenv, buf))
if err != nil {
return nil, err
}
args = append(args, strs...)
} else {
args = append(args, replaceEnv(p.Getenv, buf))
}
} else {
args = append(args, buf)
}
}
if escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote {
return nil, errors.New("invalid command line string")
}
p.Position = pos
return args, nil
}
func (p *Parser) ParseWithEnvs(line string) (envs []string, args []string, err error) {
_args, err := p.Parse(line)
if err != nil {
return nil, nil, err
}
envs = []string{}
args = []string{}
parsingEnv := true
for _, arg := range _args {
if parsingEnv && isEnv(arg) {
envs = append(envs, arg)
} else {
if parsingEnv {
parsingEnv = false
}
args = append(args, arg)
}
}
return envs, args, nil
}
func isEnv(arg string) bool {
return len(strings.Split(arg, "=")) == 2
}
func Parse(line string) ([]string, error) {
return NewParser().Parse(line)
}
func ParseWithEnvs(line string) (envs []string, args []string, err error) {
return NewParser().ParseWithEnvs(line)
}