mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 10:03:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			318 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			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)
 | 
						|
}
 |