Update vendor/

This commit is contained in:
Ettore Di Giacinto
2019-10-29 17:37:49 +01:00
committed by Ettore Di Giacinto
parent 8ca6051a04
commit 9d0dc601b7
374 changed files with 103281 additions and 37 deletions

27
vendor/mvdan.cc/sh/LICENSE vendored Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2016, Daniel Martí. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

202
vendor/mvdan.cc/sh/expand/arith.go vendored Normal file
View File

@@ -0,0 +1,202 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package expand
import (
"fmt"
"strconv"
"mvdan.cc/sh/syntax"
)
func Arithm(cfg *Config, expr syntax.ArithmExpr) (int, error) {
switch x := expr.(type) {
case *syntax.Word:
str, err := Literal(cfg, x)
if err != nil {
return 0, err
}
// recursively fetch vars
i := 0
for str != "" && syntax.ValidName(str) {
val := cfg.envGet(str)
if val == "" {
break
}
if i++; i >= maxNameRefDepth {
break
}
str = val
}
// default to 0
return atoi(str), nil
case *syntax.ParenArithm:
return Arithm(cfg, x.X)
case *syntax.UnaryArithm:
switch x.Op {
case syntax.Inc, syntax.Dec:
name := x.X.(*syntax.Word).Lit()
old := atoi(cfg.envGet(name))
val := old
if x.Op == syntax.Inc {
val++
} else {
val--
}
cfg.envSet(name, strconv.Itoa(val))
if x.Post {
return old, nil
}
return val, nil
}
val, err := Arithm(cfg, x.X)
if err != nil {
return 0, err
}
switch x.Op {
case syntax.Not:
return oneIf(val == 0), nil
case syntax.Plus:
return val, nil
default: // syntax.Minus
return -val, nil
}
case *syntax.BinaryArithm:
switch x.Op {
case syntax.Assgn, syntax.AddAssgn, syntax.SubAssgn,
syntax.MulAssgn, syntax.QuoAssgn, syntax.RemAssgn,
syntax.AndAssgn, syntax.OrAssgn, syntax.XorAssgn,
syntax.ShlAssgn, syntax.ShrAssgn:
return cfg.assgnArit(x)
case syntax.Quest: // Colon can't happen here
cond, err := Arithm(cfg, x.X)
if err != nil {
return 0, err
}
b2 := x.Y.(*syntax.BinaryArithm) // must have Op==Colon
if cond == 1 {
return Arithm(cfg, b2.X)
}
return Arithm(cfg, b2.Y)
}
left, err := Arithm(cfg, x.X)
if err != nil {
return 0, err
}
right, err := Arithm(cfg, x.Y)
if err != nil {
return 0, err
}
return binArit(x.Op, left, right), nil
default:
panic(fmt.Sprintf("unexpected arithm expr: %T", x))
}
}
func oneIf(b bool) int {
if b {
return 1
}
return 0
}
// atoi is just a shorthand for strconv.Atoi that ignores the error,
// just like shells do.
func atoi(s string) int {
n, _ := strconv.Atoi(s)
return n
}
func (cfg *Config) assgnArit(b *syntax.BinaryArithm) (int, error) {
name := b.X.(*syntax.Word).Lit()
val := atoi(cfg.envGet(name))
arg, err := Arithm(cfg, b.Y)
if err != nil {
return 0, err
}
switch b.Op {
case syntax.Assgn:
val = arg
case syntax.AddAssgn:
val += arg
case syntax.SubAssgn:
val -= arg
case syntax.MulAssgn:
val *= arg
case syntax.QuoAssgn:
val /= arg
case syntax.RemAssgn:
val %= arg
case syntax.AndAssgn:
val &= arg
case syntax.OrAssgn:
val |= arg
case syntax.XorAssgn:
val ^= arg
case syntax.ShlAssgn:
val <<= uint(arg)
case syntax.ShrAssgn:
val >>= uint(arg)
}
cfg.envSet(name, strconv.Itoa(val))
return val, nil
}
func intPow(a, b int) int {
p := 1
for b > 0 {
if b&1 != 0 {
p *= a
}
b >>= 1
a *= a
}
return p
}
func binArit(op syntax.BinAritOperator, x, y int) int {
switch op {
case syntax.Add:
return x + y
case syntax.Sub:
return x - y
case syntax.Mul:
return x * y
case syntax.Quo:
return x / y
case syntax.Rem:
return x % y
case syntax.Pow:
return intPow(x, y)
case syntax.Eql:
return oneIf(x == y)
case syntax.Gtr:
return oneIf(x > y)
case syntax.Lss:
return oneIf(x < y)
case syntax.Neq:
return oneIf(x != y)
case syntax.Leq:
return oneIf(x <= y)
case syntax.Geq:
return oneIf(x >= y)
case syntax.And:
return x & y
case syntax.Or:
return x | y
case syntax.Xor:
return x ^ y
case syntax.Shr:
return x >> uint(y)
case syntax.Shl:
return x << uint(y)
case syntax.AndArit:
return oneIf(x != 0 && y != 0)
case syntax.OrArit:
return oneIf(x != 0 || y != 0)
default: // syntax.Comma
// x is executed but its result discarded
return y
}
}

24
vendor/mvdan.cc/sh/expand/braces.go vendored Normal file
View File

@@ -0,0 +1,24 @@
// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package expand
import "mvdan.cc/sh/syntax"
// Braces performs Bash brace expansion on words. For example, passing it a
// literal word "foo{bar,baz}" will return two literal words, "foobar" and
// "foobaz".
//
// It does not return an error; malformed brace expansions are simply skipped.
// For example, "a{b{c,d}" results in the words "a{bc" and "a{bd".
//
// Note that the resulting words may have more word parts than necessary, such
// as contiguous *syntax.Lit nodes, and that these parts may be shared between
// words.
func Braces(words ...*syntax.Word) []*syntax.Word {
var res []*syntax.Word
for _, word := range words {
res = append(res, syntax.ExpandBraces(word)...)
}
return res
}

5
vendor/mvdan.cc/sh/expand/doc.go vendored Normal file
View File

@@ -0,0 +1,5 @@
// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
// Package expand contains code to perform various shell expansions.
package expand

195
vendor/mvdan.cc/sh/expand/environ.go vendored Normal file
View File

@@ -0,0 +1,195 @@
// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package expand
import (
"runtime"
"sort"
"strings"
)
// Environ is the base interface for a shell's environment, allowing it to fetch
// variables by name and to iterate over all the currently set variables.
type Environ interface {
// Get retrieves a variable by its name. To check if the variable is
// set, use Variable.IsSet.
Get(name string) Variable
// Each iterates over all the currently set variables, calling the
// supplied function on each variable. Iteration is stopped if the
// function returns false.
//
// The names used in the calls aren't required to be unique or sorted.
// If a variable name appears twice, the latest occurrence takes
// priority.
//
// Each is required to forward exported variables when executing
// programs.
Each(func(name string, vr Variable) bool)
}
// WriteEnviron is an extension on Environ that supports modifying and deleting
// variables.
type WriteEnviron interface {
Environ
// Set sets a variable by name. If !vr.IsSet(), the variable is being
// unset; otherwise, the variable is being replaced.
//
// It is the implementation's responsibility to handle variable
// attributes correctly. For example, changing an exported variable's
// value does not unexport it, and overwriting a name reference variable
// should modify its target.
Set(name string, vr Variable)
}
// Variable describes a shell variable, which can have a number of attributes
// and a value.
//
// A Variable is unset if its Value field is untyped nil, which can be checked
// via Variable.IsSet. The zero value of a Variable is thus a valid unset
// variable.
//
// If a variable is set, its Value field will be a []string if it is an indexed
// array, a map[string]string if it's an associative array, or a string
// otherwise.
type Variable struct {
Local bool
Exported bool
ReadOnly bool
NameRef bool // if true, Value must be string
Value interface{} // string, []string, or map[string]string
}
// IsSet returns whether the variable is set. An empty variable is set, but an
// undeclared variable is not.
func (v Variable) IsSet() bool {
return v.Value != nil
}
// String returns the variable's value as a string. In general, this only makes
// sense if the variable has a string value or no value at all.
func (v Variable) String() string {
switch x := v.Value.(type) {
case string:
return x
case []string:
if len(x) > 0 {
return x[0]
}
case map[string]string:
// nothing to do
}
return ""
}
// maxNameRefDepth defines the maximum number of times to follow references when
// resolving a variable. Otherwise, simple name reference loops could crash a
// program quite easily.
const maxNameRefDepth = 100
// Resolve follows a number of nameref variables, returning the last reference
// name that was followed and the variable that it points to.
func (v Variable) Resolve(env Environ) (string, Variable) {
name := ""
for i := 0; i < maxNameRefDepth; i++ {
if !v.NameRef {
return name, v
}
name = v.Value.(string)
v = env.Get(name)
}
return name, Variable{}
}
// FuncEnviron wraps a function mapping variable names to their string values,
// and implements Environ. Empty strings returned by the function will be
// treated as unset variables. All variables will be exported.
//
// Note that the returned Environ's Each method will be a no-op.
func FuncEnviron(fn func(string) string) Environ {
return funcEnviron(fn)
}
type funcEnviron func(string) string
func (f funcEnviron) Get(name string) Variable {
value := f(name)
if value == "" {
return Variable{}
}
return Variable{Exported: true, Value: value}
}
func (f funcEnviron) Each(func(name string, vr Variable) bool) {}
// ListEnviron returns an Environ with the supplied variables, in the form
// "key=value". All variables will be exported.
//
// On Windows, where environment variable names are case-insensitive, the
// resulting variable names will all be uppercase.
func ListEnviron(pairs ...string) Environ {
return listEnvironWithUpper(runtime.GOOS == "windows", pairs...)
}
// listEnvironWithUpper implements ListEnviron, but letting the tests specify
// whether to uppercase all names or not.
func listEnvironWithUpper(upper bool, pairs ...string) Environ {
list := append([]string{}, pairs...)
if upper {
// Uppercase before sorting, so that we can remove duplicates
// without the need for linear search nor a map.
for i, s := range list {
if sep := strings.IndexByte(s, '='); sep > 0 {
list[i] = strings.ToUpper(s[:sep]) + s[sep:]
}
}
}
sort.Strings(list)
last := ""
for i := 0; i < len(list); {
s := list[i]
sep := strings.IndexByte(s, '=')
if sep <= 0 {
// invalid element; remove it
list = append(list[:i], list[i+1:]...)
continue
}
name := s[:sep]
if last == name {
// duplicate; the last one wins
list = append(list[:i-1], list[i:]...)
continue
}
last = name
i++
}
return listEnviron(list)
}
type listEnviron []string
func (l listEnviron) Get(name string) Variable {
// TODO: binary search
prefix := name + "="
for _, pair := range l {
if val := strings.TrimPrefix(pair, prefix); val != pair {
return Variable{Exported: true, Value: val}
}
}
return Variable{}
}
func (l listEnviron) Each(fn func(name string, vr Variable) bool) {
for _, pair := range l {
i := strings.IndexByte(pair, '=')
if i < 0 {
// can't happen; see above
panic("expand.listEnviron: did not expect malformed name-value pair: " + pair)
}
name, value := pair[:i], pair[i+1:]
if !fn(name, Variable{Exported: true, Value: value}) {
return
}
}
}

799
vendor/mvdan.cc/sh/expand/expand.go vendored Normal file
View File

@@ -0,0 +1,799 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package expand
import (
"bytes"
"fmt"
"io"
"os"
"os/user"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"mvdan.cc/sh/syntax"
)
// A Config specifies details about how shell expansion should be performed. The
// zero value is a valid configuration.
type Config struct {
// Env is used to get and set environment variables when performing
// shell expansions. Some special parameters are also expanded via this
// interface, such as:
//
// * "#", "@", "*", "0"-"9" for the shell's parameters
// * "?", "$", "PPID" for the shell's status and process
// * "HOME foo" to retrieve user foo's home directory (if unset,
// os/user.Lookup will be used)
//
// If nil, there are no environment variables set. Use
// ListEnviron(os.Environ()...) to use the system's environment
// variables.
Env Environ
// TODO(mvdan): consider replacing NoGlob==true with ReadDir==nil.
// NoGlob corresponds to the shell option that disables globbing.
NoGlob bool
// GlobStar corresponds to the shell option that allows globbing with
// "**".
GlobStar bool
// CmdSubst expands a command substitution node, writing its standard
// output to the provided io.Writer.
//
// If nil, encountering a command substitution will result in an
// UnexpectedCommandError.
CmdSubst func(io.Writer, *syntax.CmdSubst) error
// ReadDir is used for file path globbing. If nil, globbing is disabled.
// Use ioutil.ReadDir to use the filesystem directly.
ReadDir func(string) ([]os.FileInfo, error)
bufferAlloc bytes.Buffer
fieldAlloc [4]fieldPart
fieldsAlloc [4][]fieldPart
ifs string
// A pointer to a parameter expansion node, if we're inside one.
// Necessary for ${LINENO}.
curParam *syntax.ParamExp
}
// UnexpectedCommandError is returned if a command substitution is encountered
// when Config.CmdSubst is nil.
type UnexpectedCommandError struct {
Node *syntax.CmdSubst
}
func (u UnexpectedCommandError) Error() string {
return fmt.Sprintf("unexpected command substitution at %s", u.Node.Pos())
}
var zeroConfig = &Config{}
func prepareConfig(cfg *Config) *Config {
if cfg == nil {
cfg = zeroConfig
}
if cfg.Env == nil {
cfg.Env = FuncEnviron(func(string) string { return "" })
}
cfg.ifs = " \t\n"
if vr := cfg.Env.Get("IFS"); vr.IsSet() {
cfg.ifs = vr.String()
}
return cfg
}
func (cfg *Config) ifsRune(r rune) bool {
for _, r2 := range cfg.ifs {
if r == r2 {
return true
}
}
return false
}
func (cfg *Config) ifsJoin(strs []string) string {
sep := ""
if cfg.ifs != "" {
sep = cfg.ifs[:1]
}
return strings.Join(strs, sep)
}
func (cfg *Config) strBuilder() *bytes.Buffer {
b := &cfg.bufferAlloc
b.Reset()
return b
}
func (cfg *Config) envGet(name string) string {
return cfg.Env.Get(name).String()
}
func (cfg *Config) envSet(name, value string) {
wenv, ok := cfg.Env.(WriteEnviron)
if !ok {
// TODO: we should probably error here
return
}
wenv.Set(name, Variable{Value: value})
}
// Literal expands a single shell word. It is similar to Fields, but the result
// is a single string. This is the behavior when a word is used as the value in
// a shell variable assignment, for example.
//
// The config specifies shell expansion options; nil behaves the same as an
// empty config.
func Literal(cfg *Config, word *syntax.Word) (string, error) {
if word == nil {
return "", nil
}
cfg = prepareConfig(cfg)
field, err := cfg.wordField(word.Parts, quoteNone)
if err != nil {
return "", err
}
return cfg.fieldJoin(field), nil
}
// Document expands a single shell word as if it were within double quotes. It
// is simlar to Literal, but without brace expansion, tilde expansion, and
// globbing.
//
// The config specifies shell expansion options; nil behaves the same as an
// empty config.
func Document(cfg *Config, word *syntax.Word) (string, error) {
if word == nil {
return "", nil
}
cfg = prepareConfig(cfg)
field, err := cfg.wordField(word.Parts, quoteDouble)
if err != nil {
return "", err
}
return cfg.fieldJoin(field), nil
}
// Pattern expands a single shell word as a pattern, using syntax.QuotePattern
// on any non-quoted parts of the input word. The result can be used on
// syntax.TranslatePattern directly.
//
// The config specifies shell expansion options; nil behaves the same as an
// empty config.
func Pattern(cfg *Config, word *syntax.Word) (string, error) {
cfg = prepareConfig(cfg)
field, err := cfg.wordField(word.Parts, quoteNone)
if err != nil {
return "", err
}
buf := cfg.strBuilder()
for _, part := range field {
if part.quote > quoteNone {
buf.WriteString(syntax.QuotePattern(part.val))
} else {
buf.WriteString(part.val)
}
}
return buf.String(), nil
}
// Format expands a format string with a number of arguments, following the
// shell's format specifications. These include printf(1), among others.
//
// The resulting string is returned, along with the number of arguments used.
//
// The config specifies shell expansion options; nil behaves the same as an
// empty config.
func Format(cfg *Config, format string, args []string) (string, int, error) {
cfg = prepareConfig(cfg)
buf := cfg.strBuilder()
esc := false
var fmts []rune
initialArgs := len(args)
for _, c := range format {
switch {
case esc:
esc = false
switch c {
case 'n':
buf.WriteRune('\n')
case 'r':
buf.WriteRune('\r')
case 't':
buf.WriteRune('\t')
case '\\':
buf.WriteRune('\\')
default:
buf.WriteRune('\\')
buf.WriteRune(c)
}
case len(fmts) > 0:
switch c {
case '%':
buf.WriteByte('%')
fmts = nil
case 'c':
var b byte
if len(args) > 0 {
arg := ""
arg, args = args[0], args[1:]
if len(arg) > 0 {
b = arg[0]
}
}
buf.WriteByte(b)
fmts = nil
case '+', '-', ' ':
if len(fmts) > 1 {
return "", 0, fmt.Errorf("invalid format char: %c", c)
}
fmts = append(fmts, c)
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
fmts = append(fmts, c)
case 's', 'd', 'i', 'u', 'o', 'x':
arg := ""
if len(args) > 0 {
arg, args = args[0], args[1:]
}
var farg interface{} = arg
if c != 's' {
n, _ := strconv.ParseInt(arg, 0, 0)
if c == 'i' || c == 'd' {
farg = int(n)
} else {
farg = uint(n)
}
if c == 'i' || c == 'u' {
c = 'd'
}
}
fmts = append(fmts, c)
fmt.Fprintf(buf, string(fmts), farg)
fmts = nil
default:
return "", 0, fmt.Errorf("invalid format char: %c", c)
}
case c == '\\':
esc = true
case args != nil && c == '%':
// if args == nil, we are not doing format
// arguments
fmts = []rune{c}
default:
buf.WriteRune(c)
}
}
if len(fmts) > 0 {
return "", 0, fmt.Errorf("missing format char")
}
return buf.String(), initialArgs - len(args), nil
}
func (cfg *Config) fieldJoin(parts []fieldPart) string {
switch len(parts) {
case 0:
return ""
case 1: // short-cut without a string copy
return parts[0].val
}
buf := cfg.strBuilder()
for _, part := range parts {
buf.WriteString(part.val)
}
return buf.String()
}
func (cfg *Config) escapedGlobField(parts []fieldPart) (escaped string, glob bool) {
buf := cfg.strBuilder()
for _, part := range parts {
if part.quote > quoteNone {
buf.WriteString(syntax.QuotePattern(part.val))
continue
}
buf.WriteString(part.val)
if syntax.HasPattern(part.val) {
glob = true
}
}
if glob { // only copy the string if it will be used
escaped = buf.String()
}
return escaped, glob
}
// Fields expands a number of words as if they were arguments in a shell
// command. This includes brace expansion, tilde expansion, parameter expansion,
// command substitution, arithmetic expansion, and quote removal.
func Fields(cfg *Config, words ...*syntax.Word) ([]string, error) {
cfg = prepareConfig(cfg)
fields := make([]string, 0, len(words))
dir := cfg.envGet("PWD")
for _, expWord := range Braces(words...) {
wfields, err := cfg.wordFields(expWord.Parts)
if err != nil {
return nil, err
}
for _, field := range wfields {
path, doGlob := cfg.escapedGlobField(field)
var matches []string
abs := filepath.IsAbs(path)
if doGlob && !cfg.NoGlob {
base := ""
if !abs {
base = dir
}
matches, err = cfg.glob(base, path)
if err != nil {
return nil, err
}
}
if len(matches) == 0 {
fields = append(fields, cfg.fieldJoin(field))
continue
}
for _, match := range matches {
if !abs {
match = strings.TrimPrefix(match, dir)
}
fields = append(fields, match)
}
}
}
return fields, nil
}
type fieldPart struct {
val string
quote quoteLevel
}
type quoteLevel uint
const (
quoteNone quoteLevel = iota
quoteDouble
quoteSingle
)
func (cfg *Config) wordField(wps []syntax.WordPart, ql quoteLevel) ([]fieldPart, error) {
var field []fieldPart
for i, wp := range wps {
switch x := wp.(type) {
case *syntax.Lit:
s := x.Value
if i == 0 && ql == quoteNone {
if prefix, rest := cfg.expandUser(s); prefix != "" {
// TODO: return two separate fieldParts,
// like in wordFields?
s = prefix + rest
}
}
if ql == quoteDouble && strings.Contains(s, "\\") {
buf := cfg.strBuilder()
for i := 0; i < len(s); i++ {
b := s[i]
if b == '\\' && i+1 < len(s) {
switch s[i+1] {
case '\n': // remove \\\n
i++
continue
case '"', '\\', '$', '`': // special chars
continue
}
}
buf.WriteByte(b)
}
s = buf.String()
}
field = append(field, fieldPart{val: s})
case *syntax.SglQuoted:
fp := fieldPart{quote: quoteSingle, val: x.Value}
if x.Dollar {
fp.val, _, _ = Format(cfg, fp.val, nil)
}
field = append(field, fp)
case *syntax.DblQuoted:
wfield, err := cfg.wordField(x.Parts, quoteDouble)
if err != nil {
return nil, err
}
for _, part := range wfield {
part.quote = quoteDouble
field = append(field, part)
}
case *syntax.ParamExp:
val, err := cfg.paramExp(x)
if err != nil {
return nil, err
}
field = append(field, fieldPart{val: val})
case *syntax.CmdSubst:
val, err := cfg.cmdSubst(x)
if err != nil {
return nil, err
}
field = append(field, fieldPart{val: val})
case *syntax.ArithmExp:
n, err := Arithm(cfg, x.X)
if err != nil {
return nil, err
}
field = append(field, fieldPart{val: strconv.Itoa(n)})
default:
panic(fmt.Sprintf("unhandled word part: %T", x))
}
}
return field, nil
}
func (cfg *Config) cmdSubst(cs *syntax.CmdSubst) (string, error) {
if cfg.CmdSubst == nil {
return "", UnexpectedCommandError{Node: cs}
}
buf := cfg.strBuilder()
if err := cfg.CmdSubst(buf, cs); err != nil {
return "", err
}
return strings.TrimRight(buf.String(), "\n"), nil
}
func (cfg *Config) wordFields(wps []syntax.WordPart) ([][]fieldPart, error) {
fields := cfg.fieldsAlloc[:0]
curField := cfg.fieldAlloc[:0]
allowEmpty := false
flush := func() {
if len(curField) == 0 {
return
}
fields = append(fields, curField)
curField = nil
}
splitAdd := func(val string) {
for i, field := range strings.FieldsFunc(val, cfg.ifsRune) {
if i > 0 {
flush()
}
curField = append(curField, fieldPart{val: field})
}
}
for i, wp := range wps {
switch x := wp.(type) {
case *syntax.Lit:
s := x.Value
if i == 0 {
prefix, rest := cfg.expandUser(s)
curField = append(curField, fieldPart{
quote: quoteSingle,
val: prefix,
})
s = rest
}
if strings.Contains(s, "\\") {
buf := cfg.strBuilder()
for i := 0; i < len(s); i++ {
b := s[i]
if b == '\\' {
i++
b = s[i]
}
buf.WriteByte(b)
}
s = buf.String()
}
curField = append(curField, fieldPart{val: s})
case *syntax.SglQuoted:
allowEmpty = true
fp := fieldPart{quote: quoteSingle, val: x.Value}
if x.Dollar {
fp.val, _, _ = Format(cfg, fp.val, nil)
}
curField = append(curField, fp)
case *syntax.DblQuoted:
allowEmpty = true
if len(x.Parts) == 1 {
pe, _ := x.Parts[0].(*syntax.ParamExp)
if elems := cfg.quotedElems(pe); elems != nil {
for i, elem := range elems {
if i > 0 {
flush()
}
curField = append(curField, fieldPart{
quote: quoteDouble,
val: elem,
})
}
continue
}
}
wfield, err := cfg.wordField(x.Parts, quoteDouble)
if err != nil {
return nil, err
}
for _, part := range wfield {
part.quote = quoteDouble
curField = append(curField, part)
}
case *syntax.ParamExp:
val, err := cfg.paramExp(x)
if err != nil {
return nil, err
}
splitAdd(val)
case *syntax.CmdSubst:
val, err := cfg.cmdSubst(x)
if err != nil {
return nil, err
}
splitAdd(val)
case *syntax.ArithmExp:
n, err := Arithm(cfg, x.X)
if err != nil {
return nil, err
}
curField = append(curField, fieldPart{val: strconv.Itoa(n)})
default:
panic(fmt.Sprintf("unhandled word part: %T", x))
}
}
flush()
if allowEmpty && len(fields) == 0 {
fields = append(fields, curField)
}
return fields, nil
}
// quotedElems checks if a parameter expansion is exactly ${@} or ${foo[@]}
func (cfg *Config) quotedElems(pe *syntax.ParamExp) []string {
if pe == nil || pe.Excl || pe.Length || pe.Width {
return nil
}
if pe.Param.Value == "@" {
return cfg.Env.Get("@").Value.([]string)
}
if nodeLit(pe.Index) != "@" {
return nil
}
val := cfg.Env.Get(pe.Param.Value).Value
if x, ok := val.([]string); ok {
return x
}
return nil
}
func (cfg *Config) expandUser(field string) (prefix, rest string) {
if len(field) == 0 || field[0] != '~' {
return "", field
}
name := field[1:]
if i := strings.Index(name, "/"); i >= 0 {
rest = name[i:]
name = name[:i]
}
if name == "" {
return cfg.Env.Get("HOME").String(), rest
}
if vr := cfg.Env.Get("HOME " + name); vr.IsSet() {
return vr.String(), rest
}
u, err := user.Lookup(name)
if err != nil {
return "", field
}
return u.HomeDir, rest
}
func findAllIndex(pattern, name string, n int) [][]int {
expr, err := syntax.TranslatePattern(pattern, true)
if err != nil {
return nil
}
rx := regexp.MustCompile(expr)
return rx.FindAllStringIndex(name, n)
}
// TODO: use this again to optimize globbing; see
// https://github.com/mvdan/sh/issues/213
func hasGlob(path string) bool {
magicChars := `*?[`
if runtime.GOOS != "windows" {
magicChars = `*?[\`
}
return strings.ContainsAny(path, magicChars)
}
var rxGlobStar = regexp.MustCompile(".*")
// pathJoin2 is a simpler version of filepath.Join without cleaning the result,
// since that's needed for globbing.
func pathJoin2(elem1, elem2 string) string {
if elem1 == "" {
return elem2
}
if strings.HasSuffix(elem1, string(filepath.Separator)) {
return elem1 + elem2
}
return elem1 + string(filepath.Separator) + elem2
}
// pathSplit splits a file path into its elements, retaining empty ones. Before
// splitting, slashes are replaced with filepath.Separator, so that splitting
// Unix paths on Windows works as well.
func pathSplit(path string) []string {
path = filepath.FromSlash(path)
return strings.Split(path, string(filepath.Separator))
}
func (cfg *Config) glob(base, pattern string) ([]string, error) {
parts := pathSplit(pattern)
matches := []string{""}
if filepath.IsAbs(pattern) {
if parts[0] == "" {
// unix-like
matches[0] = string(filepath.Separator)
} else {
// windows (for some reason it won't work without the
// trailing separator)
matches[0] = parts[0] + string(filepath.Separator)
}
parts = parts[1:]
}
for _, part := range parts {
switch {
case part == "", part == ".", part == "..":
var newMatches []string
for _, dir := range matches {
// TODO(mvdan): reuse the previous ReadDir call
if cfg.ReadDir == nil {
continue // no globbing
} else if _, err := cfg.ReadDir(filepath.Join(base, dir)); err != nil {
continue // not actually a dir
}
newMatches = append(newMatches, pathJoin2(dir, part))
}
matches = newMatches
continue
case part == "**" && cfg.GlobStar:
for i, match := range matches {
// "a/**" should match "a/ a/b a/b/cfg ..."; note
// how the zero-match case has a trailing
// separator.
matches[i] = pathJoin2(match, "")
}
// expand all the possible levels of **
latest := matches
for {
var newMatches []string
for _, dir := range latest {
var err error
newMatches, err = cfg.globDir(base, dir, rxGlobStar, newMatches)
if err != nil {
return nil, err
}
}
if len(newMatches) == 0 {
// not another level of directories to
// try; stop
break
}
matches = append(matches, newMatches...)
latest = newMatches
}
continue
}
expr, err := syntax.TranslatePattern(part, true)
if err != nil {
// If any glob part is not a valid pattern, don't glob.
return nil, nil
}
rx := regexp.MustCompile("^" + expr + "$")
var newMatches []string
for _, dir := range matches {
newMatches, err = cfg.globDir(base, dir, rx, newMatches)
if err != nil {
return nil, err
}
}
matches = newMatches
}
return matches, nil
}
func (cfg *Config) globDir(base, dir string, rx *regexp.Regexp, matches []string) ([]string, error) {
if cfg.ReadDir == nil {
// TODO(mvdan): check this at the beginning of a glob?
return nil, nil
}
infos, err := cfg.ReadDir(filepath.Join(base, dir))
if err != nil {
// Ignore the error, as this might be a file instead of a
// directory. v3 refactored globbing to only use one ReadDir
// call per directory instead of two, so it knows to skip this
// kind of path at the ReadDir call of its parent.
// Instead of backporting that complex rewrite into v2, just
// work around the edge case here. We might ignore other kinds
// of errors, but at least we don't fail on a correct glob.
return matches, nil
}
for _, info := range infos {
name := info.Name()
if !strings.HasPrefix(rx.String(), `^\.`) && name[0] == '.' {
continue
}
if rx.MatchString(name) {
matches = append(matches, pathJoin2(dir, name))
}
}
return matches, nil
}
//
// The config specifies shell expansion options; nil behaves the same as an
// empty config.
func ReadFields(cfg *Config, s string, n int, raw bool) []string {
cfg = prepareConfig(cfg)
type pos struct {
start, end int
}
var fpos []pos
runes := make([]rune, 0, len(s))
infield := false
esc := false
for _, r := range s {
if infield {
if cfg.ifsRune(r) && (raw || !esc) {
fpos[len(fpos)-1].end = len(runes)
infield = false
}
} else {
if !cfg.ifsRune(r) && (raw || !esc) {
fpos = append(fpos, pos{start: len(runes), end: -1})
infield = true
}
}
if r == '\\' {
if raw || esc {
runes = append(runes, r)
}
esc = !esc
continue
}
runes = append(runes, r)
esc = false
}
if len(fpos) == 0 {
return nil
}
if infield {
fpos[len(fpos)-1].end = len(runes)
}
switch {
case n == 1:
// include heading/trailing IFSs
fpos[0].start, fpos[0].end = 0, len(runes)
fpos = fpos[:1]
case n != -1 && n < len(fpos):
// combine to max n fields
fpos[n-1].end = fpos[len(fpos)-1].end
fpos = fpos[:n]
}
var fields = make([]string, len(fpos))
for i, p := range fpos {
fields[i] = string(runes[p.start:p.end])
}
return fields
}

348
vendor/mvdan.cc/sh/expand/param.go vendored Normal file
View File

@@ -0,0 +1,348 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package expand
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"mvdan.cc/sh/syntax"
)
func nodeLit(node syntax.Node) string {
if word, ok := node.(*syntax.Word); ok {
return word.Lit()
}
return ""
}
type UnsetParameterError struct {
Node *syntax.ParamExp
Message string
}
func (u UnsetParameterError) Error() string {
return u.Message
}
func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) {
oldParam := cfg.curParam
cfg.curParam = pe
defer func() { cfg.curParam = oldParam }()
name := pe.Param.Value
index := pe.Index
switch name {
case "@", "*":
index = &syntax.Word{Parts: []syntax.WordPart{
&syntax.Lit{Value: name},
}}
}
var vr Variable
switch name {
case "LINENO":
// This is the only parameter expansion that the environment
// interface cannot satisfy.
line := uint64(cfg.curParam.Pos().Line())
vr.Value = strconv.FormatUint(line, 10)
default:
vr = cfg.Env.Get(name)
}
orig := vr
_, vr = vr.Resolve(cfg.Env)
str, err := cfg.varInd(vr, index)
if err != nil {
return "", err
}
slicePos := func(n int) int {
if n < 0 {
n = len(str) + n
if n < 0 {
n = len(str)
}
} else if n > len(str) {
n = len(str)
}
return n
}
elems := []string{str}
switch nodeLit(index) {
case "@", "*":
switch x := vr.Value.(type) {
case nil:
elems = nil
case []string:
elems = x
}
}
switch {
case pe.Length:
n := len(elems)
switch nodeLit(index) {
case "@", "*":
default:
n = utf8.RuneCountInString(str)
}
str = strconv.Itoa(n)
case pe.Excl:
var strs []string
if pe.Names != 0 {
strs = cfg.namesByPrefix(pe.Param.Value)
} else if orig.NameRef {
strs = append(strs, orig.Value.(string))
} else if x, ok := vr.Value.([]string); ok {
for i, e := range x {
if e != "" {
strs = append(strs, strconv.Itoa(i))
}
}
} else if x, ok := vr.Value.(map[string]string); ok {
for k := range x {
strs = append(strs, k)
}
} else if str != "" {
vr = cfg.Env.Get(str)
strs = append(strs, vr.String())
}
sort.Strings(strs)
str = strings.Join(strs, " ")
case pe.Slice != nil:
if pe.Slice.Offset != nil {
n, err := Arithm(cfg, pe.Slice.Offset)
if err != nil {
return "", err
}
str = str[slicePos(n):]
}
if pe.Slice.Length != nil {
n, err := Arithm(cfg, pe.Slice.Length)
if err != nil {
return "", err
}
str = str[:slicePos(n)]
}
case pe.Repl != nil:
orig, err := Pattern(cfg, pe.Repl.Orig)
if err != nil {
return "", err
}
with, err := Literal(cfg, pe.Repl.With)
if err != nil {
return "", err
}
n := 1
if pe.Repl.All {
n = -1
}
locs := findAllIndex(orig, str, n)
buf := cfg.strBuilder()
last := 0
for _, loc := range locs {
buf.WriteString(str[last:loc[0]])
buf.WriteString(with)
last = loc[1]
}
buf.WriteString(str[last:])
str = buf.String()
case pe.Exp != nil:
arg, err := Literal(cfg, pe.Exp.Word)
if err != nil {
return "", err
}
switch op := pe.Exp.Op; op {
case syntax.SubstColPlus:
if str == "" {
break
}
fallthrough
case syntax.SubstPlus:
if vr.IsSet() {
str = arg
}
case syntax.SubstMinus:
if vr.IsSet() {
break
}
fallthrough
case syntax.SubstColMinus:
if str == "" {
str = arg
}
case syntax.SubstQuest:
if vr.IsSet() {
break
}
fallthrough
case syntax.SubstColQuest:
if str == "" {
return "", UnsetParameterError{
Node: pe,
Message: arg,
}
}
case syntax.SubstAssgn:
if vr.IsSet() {
break
}
fallthrough
case syntax.SubstColAssgn:
if str == "" {
cfg.envSet(name, arg)
str = arg
}
case syntax.RemSmallPrefix, syntax.RemLargePrefix,
syntax.RemSmallSuffix, syntax.RemLargeSuffix:
suffix := op == syntax.RemSmallSuffix ||
op == syntax.RemLargeSuffix
large := op == syntax.RemLargePrefix ||
op == syntax.RemLargeSuffix
for i, elem := range elems {
elems[i] = removePattern(elem, arg, suffix, large)
}
str = strings.Join(elems, " ")
case syntax.UpperFirst, syntax.UpperAll,
syntax.LowerFirst, syntax.LowerAll:
caseFunc := unicode.ToLower
if op == syntax.UpperFirst || op == syntax.UpperAll {
caseFunc = unicode.ToUpper
}
all := op == syntax.UpperAll || op == syntax.LowerAll
// empty string means '?'; nothing to do there
expr, err := syntax.TranslatePattern(arg, false)
if err != nil {
return str, nil
}
rx := regexp.MustCompile(expr)
for i, elem := range elems {
rs := []rune(elem)
for ri, r := range rs {
if rx.MatchString(string(r)) {
rs[ri] = caseFunc(r)
if !all {
break
}
}
}
elems[i] = string(rs)
}
str = strings.Join(elems, " ")
case syntax.OtherParamOps:
switch arg {
case "Q":
str = strconv.Quote(str)
case "E":
tail := str
var rns []rune
for tail != "" {
var rn rune
rn, _, tail, _ = strconv.UnquoteChar(tail, 0)
rns = append(rns, rn)
}
str = string(rns)
case "P", "A", "a":
panic(fmt.Sprintf("unhandled @%s param expansion", arg))
default:
panic(fmt.Sprintf("unexpected @%s param expansion", arg))
}
}
}
return str, nil
}
func removePattern(str, pattern string, fromEnd, greedy bool) string {
expr, err := syntax.TranslatePattern(pattern, greedy)
if err != nil {
return str
}
switch {
case fromEnd && !greedy:
// use .* to get the right-most (shortest) match
expr = ".*(" + expr + ")$"
case fromEnd:
// simple suffix
expr = "(" + expr + ")$"
default:
// simple prefix
expr = "^(" + expr + ")"
}
// no need to check error as TranslatePattern returns one
rx := regexp.MustCompile(expr)
if loc := rx.FindStringSubmatchIndex(str); loc != nil {
// remove the original pattern (the submatch)
str = str[:loc[2]] + str[loc[3]:]
}
return str
}
func (cfg *Config) varInd(vr Variable, idx syntax.ArithmExpr) (string, error) {
if idx == nil {
return vr.String(), nil
}
switch x := vr.Value.(type) {
case string:
n, err := Arithm(cfg, idx)
if err != nil {
return "", err
}
if n == 0 {
return x, nil
}
case []string:
switch nodeLit(idx) {
case "@":
return strings.Join(x, " "), nil
case "*":
return cfg.ifsJoin(x), nil
}
i, err := Arithm(cfg, idx)
if err != nil {
return "", err
}
if len(x) > 0 {
return x[i], nil
}
case map[string]string:
switch lit := nodeLit(idx); lit {
case "@", "*":
var strs []string
keys := make([]string, 0, len(x))
for k := range x {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
strs = append(strs, x[k])
}
if lit == "*" {
return cfg.ifsJoin(strs), nil
}
return strings.Join(strs, " "), nil
}
val, err := Literal(cfg, idx.(*syntax.Word))
if err != nil {
return "", err
}
return x[val], nil
}
return "", nil
}
func (cfg *Config) namesByPrefix(prefix string) []string {
var names []string
cfg.Env.Each(func(name string, vr Variable) bool {
if strings.HasPrefix(name, prefix) {
names = append(names, name)
}
return true
})
return names
}

676
vendor/mvdan.cc/sh/interp/builtin.go vendored Normal file
View File

@@ -0,0 +1,676 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package interp
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"mvdan.cc/sh/expand"
"mvdan.cc/sh/syntax"
)
func isBuiltin(name string) bool {
switch name {
case "true", ":", "false", "exit", "set", "shift", "unset",
"echo", "printf", "break", "continue", "pwd", "cd",
"wait", "builtin", "trap", "type", "source", ".", "command",
"dirs", "pushd", "popd", "umask", "alias", "unalias",
"fg", "bg", "getopts", "eval", "test", "[", "exec",
"return", "read", "shopt":
return true
}
return false
}
func oneIf(b bool) int {
if b {
return 1
}
return 0
}
// atoi is just a shorthand for strconv.Atoi that ignores the error,
// just like shells do.
func atoi(s string) int {
n, _ := strconv.Atoi(s)
return n
}
func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, args []string) int {
switch name {
case "true", ":":
case "false":
return 1
case "exit":
switch len(args) {
case 0:
case 1:
if n, err := strconv.Atoi(args[0]); err != nil {
r.errf("invalid exit status code: %q\n", args[0])
r.exit = 2
} else {
r.exit = n
}
default:
r.errf("exit cannot take multiple arguments\n")
r.exit = 1
}
r.setErr(ShellExitStatus(r.exit))
return 0 // the command's exit status does not matter
case "set":
if err := Params(args...)(r); err != nil {
r.errf("set: %v\n", err)
return 2
}
r.updateExpandOpts()
case "shift":
n := 1
switch len(args) {
case 0:
case 1:
if n2, err := strconv.Atoi(args[0]); err == nil {
n = n2
break
}
fallthrough
default:
r.errf("usage: shift [n]\n")
return 2
}
if n >= len(r.Params) {
r.Params = nil
} else {
r.Params = r.Params[n:]
}
case "unset":
vars := true
funcs := true
unsetOpts:
for i, arg := range args {
switch arg {
case "-v":
funcs = false
case "-f":
vars = false
default:
args = args[i:]
break unsetOpts
}
}
for _, arg := range args {
if vr := r.lookupVar(arg); vr.IsSet() && vars {
r.delVar(arg)
continue
}
if _, ok := r.Funcs[arg]; ok && funcs {
delete(r.Funcs, arg)
}
}
case "echo":
newline, doExpand := true, false
echoOpts:
for len(args) > 0 {
switch args[0] {
case "-n":
newline = false
case "-e":
doExpand = true
case "-E": // default
default:
break echoOpts
}
args = args[1:]
}
for i, arg := range args {
if i > 0 {
r.out(" ")
}
if doExpand {
arg, _, _ = expand.Format(r.ecfg, arg, nil)
}
r.out(arg)
}
if newline {
r.out("\n")
}
case "printf":
if len(args) == 0 {
r.errf("usage: printf format [arguments]\n")
return 2
}
format, args := args[0], args[1:]
for {
s, n, err := expand.Format(r.ecfg, format, args)
if err != nil {
r.errf("%v\n", err)
return 1
}
r.out(s)
args = args[n:]
if n == 0 || len(args) == 0 {
break
}
}
case "break", "continue":
if !r.inLoop {
r.errf("%s is only useful in a loop", name)
break
}
enclosing := &r.breakEnclosing
if name == "continue" {
enclosing = &r.contnEnclosing
}
switch len(args) {
case 0:
*enclosing = 1
case 1:
if n, err := strconv.Atoi(args[0]); err == nil {
*enclosing = n
break
}
fallthrough
default:
r.errf("usage: %s [n]\n", name)
return 2
}
case "pwd":
r.outf("%s\n", r.envGet("PWD"))
case "cd":
var path string
switch len(args) {
case 0:
path = r.envGet("HOME")
case 1:
path = args[0]
default:
r.errf("usage: cd [dir]\n")
return 2
}
return r.changeDir(path)
case "wait":
if len(args) > 0 {
panic("wait with args not handled yet")
}
switch err := r.bgShells.Wait().(type) {
case nil:
case ExitStatus:
case ShellExitStatus:
default:
r.setErr(err)
}
case "builtin":
if len(args) < 1 {
break
}
if !isBuiltin(args[0]) {
return 1
}
return r.builtinCode(ctx, pos, args[0], args[1:])
case "type":
anyNotFound := false
for _, arg := range args {
if _, ok := r.Funcs[arg]; ok {
r.outf("%s is a function\n", arg)
continue
}
if isBuiltin(arg) {
r.outf("%s is a shell builtin\n", arg)
continue
}
if path, err := exec.LookPath(arg); err == nil {
r.outf("%s is %s\n", arg, path)
continue
}
r.errf("type: %s: not found\n", arg)
anyNotFound = true
}
if anyNotFound {
return 1
}
case "eval":
src := strings.Join(args, " ")
p := syntax.NewParser()
file, err := p.Parse(strings.NewReader(src), "")
if err != nil {
r.errf("eval: %v\n", err)
return 1
}
r.stmts(ctx, file.StmtList)
return r.exit
case "source", ".":
if len(args) < 1 {
r.errf("%v: source: need filename\n", pos)
return 2
}
f, err := r.open(ctx, r.relPath(args[0]), os.O_RDONLY, 0, false)
if err != nil {
r.errf("source: %v\n", err)
return 1
}
defer f.Close()
p := syntax.NewParser()
file, err := p.Parse(f, args[0])
if err != nil {
r.errf("source: %v\n", err)
return 1
}
oldParams := r.Params
r.Params = args[1:]
oldInSource := r.inSource
r.inSource = true
r.stmts(ctx, file.StmtList)
r.Params = oldParams
r.inSource = oldInSource
if code, ok := r.err.(returnStatus); ok {
r.err = nil
r.exit = int(code)
}
return r.exit
case "[":
if len(args) == 0 || args[len(args)-1] != "]" {
r.errf("%v: [: missing matching ]\n", pos)
return 2
}
args = args[:len(args)-1]
fallthrough
case "test":
parseErr := false
p := testParser{
rem: args,
err: func(err error) {
r.errf("%v: %v\n", pos, err)
parseErr = true
},
}
p.next()
expr := p.classicTest("[", false)
if parseErr {
return 2
}
return oneIf(r.bashTest(ctx, expr, true) == "")
case "exec":
// TODO: Consider syscall.Exec, i.e. actually replacing
// the process. It's in theory what a shell should do,
// but in practice it would kill the entire Go process
// and it's not available on Windows.
if len(args) == 0 {
r.keepRedirs = true
break
}
r.exec(ctx, args)
r.setErr(ShellExitStatus(r.exit))
return 0
case "command":
show := false
for len(args) > 0 && strings.HasPrefix(args[0], "-") {
switch args[0] {
case "-v":
show = true
default:
r.errf("command: invalid option %s\n", args[0])
return 2
}
args = args[1:]
}
if len(args) == 0 {
break
}
if !show {
if isBuiltin(args[0]) {
return r.builtinCode(ctx, pos, args[0], args[1:])
}
r.exec(ctx, args)
return r.exit
}
last := 0
for _, arg := range args {
last = 0
if r.Funcs[arg] != nil || isBuiltin(arg) {
r.outf("%s\n", arg)
} else if path, err := exec.LookPath(arg); err == nil {
r.outf("%s\n", path)
} else {
last = 1
}
}
return last
case "dirs":
for i := len(r.dirStack) - 1; i >= 0; i-- {
r.outf("%s", r.dirStack[i])
if i > 0 {
r.out(" ")
}
}
r.out("\n")
case "pushd":
change := true
if len(args) > 0 && args[0] == "-n" {
change = false
args = args[1:]
}
swap := func() string {
oldtop := r.dirStack[len(r.dirStack)-1]
top := r.dirStack[len(r.dirStack)-2]
r.dirStack[len(r.dirStack)-1] = top
r.dirStack[len(r.dirStack)-2] = oldtop
return top
}
switch len(args) {
case 0:
if !change {
break
}
if len(r.dirStack) < 2 {
r.errf("pushd: no other directory\n")
return 1
}
newtop := swap()
if code := r.changeDir(newtop); code != 0 {
return code
}
r.builtinCode(ctx, syntax.Pos{}, "dirs", nil)
case 1:
if change {
if code := r.changeDir(args[0]); code != 0 {
return code
}
r.dirStack = append(r.dirStack, r.Dir)
} else {
r.dirStack = append(r.dirStack, args[0])
swap()
}
r.builtinCode(ctx, syntax.Pos{}, "dirs", nil)
default:
r.errf("pushd: too many arguments\n")
return 2
}
case "popd":
change := true
if len(args) > 0 && args[0] == "-n" {
change = false
args = args[1:]
}
switch len(args) {
case 0:
if len(r.dirStack) < 2 {
r.errf("popd: directory stack empty\n")
return 1
}
oldtop := r.dirStack[len(r.dirStack)-1]
r.dirStack = r.dirStack[:len(r.dirStack)-1]
if change {
newtop := r.dirStack[len(r.dirStack)-1]
if code := r.changeDir(newtop); code != 0 {
return code
}
} else {
r.dirStack[len(r.dirStack)-1] = oldtop
}
r.builtinCode(ctx, syntax.Pos{}, "dirs", nil)
default:
r.errf("popd: invalid argument\n")
return 2
}
case "return":
if !r.inFunc && !r.inSource {
r.errf("return: can only be done from a func or sourced script\n")
return 1
}
code := 0
switch len(args) {
case 0:
case 1:
code = atoi(args[0])
default:
r.errf("return: too many arguments\n")
return 2
}
r.setErr(returnStatus(code))
case "read":
raw := false
for len(args) > 0 && strings.HasPrefix(args[0], "-") {
switch args[0] {
case "-r":
raw = true
default:
r.errf("read: invalid option %q\n", args[0])
return 2
}
args = args[1:]
}
for _, name := range args {
if !syntax.ValidName(name) {
r.errf("read: invalid identifier %q\n", name)
return 2
}
}
line, err := r.readLine(raw)
if err != nil {
return 1
}
if len(args) == 0 {
args = append(args, "REPLY")
}
values := expand.ReadFields(r.ecfg, string(line), len(args), raw)
for i, name := range args {
val := ""
if i < len(values) {
val = values[i]
}
r.setVar(name, nil, expand.Variable{Value: val})
}
return 0
case "getopts":
if len(args) < 2 {
r.errf("getopts: usage: getopts optstring name [arg]\n")
return 2
}
optind, _ := strconv.Atoi(r.envGet("OPTIND"))
if optind-1 != r.optState.argidx {
if optind < 1 {
optind = 1
}
r.optState = getopts{argidx: optind - 1}
}
optstr := args[0]
name := args[1]
if !syntax.ValidName(name) {
r.errf("getopts: invalid identifier: %q\n", name)
return 2
}
args = args[2:]
if len(args) == 0 {
args = r.Params
}
diagnostics := !strings.HasPrefix(optstr, ":")
opt, optarg, done := r.optState.Next(optstr, args)
r.setVarString(name, string(opt))
r.delVar("OPTARG")
switch {
case opt == '?' && diagnostics && !done:
r.errf("getopts: illegal option -- %q\n", optarg)
case opt == ':' && diagnostics:
r.errf("getopts: option requires an argument -- %q\n", optarg)
default:
if optarg != "" {
r.setVarString("OPTARG", optarg)
}
}
if optind-1 != r.optState.argidx {
r.setVarString("OPTIND", strconv.FormatInt(int64(r.optState.argidx+1), 10))
}
return oneIf(done)
case "shopt":
mode := ""
posixOpts := false
for len(args) > 0 && strings.HasPrefix(args[0], "-") {
switch args[0] {
case "-s", "-u":
mode = args[0]
case "-o":
posixOpts = true
case "-p", "-q":
panic(fmt.Sprintf("unhandled shopt flag: %s", args[0]))
default:
r.errf("shopt: invalid option %q\n", args[0])
return 2
}
args = args[1:]
}
if len(args) == 0 {
if !posixOpts {
for i, name := range bashOptsTable {
r.printOptLine(name, r.opts[len(shellOptsTable)+i])
}
break
}
for i, opt := range &shellOptsTable {
r.printOptLine(opt.name, r.opts[i])
}
break
}
for _, arg := range args {
opt := r.optByName(arg, !posixOpts)
if opt == nil {
r.errf("shopt: invalid option name %q\n", arg)
return 1
}
switch mode {
case "-s", "-u":
*opt = mode == "-s"
default: // ""
r.printOptLine(arg, *opt)
}
}
r.updateExpandOpts()
default:
// "trap", "umask", "alias", "unalias", "fg", "bg",
panic(fmt.Sprintf("unhandled builtin: %s", name))
}
return 0
}
func (r *Runner) printOptLine(name string, enabled bool) {
status := "off"
if enabled {
status = "on"
}
r.outf("%s\t%s\n", name, status)
}
func (r *Runner) readLine(raw bool) ([]byte, error) {
var line []byte
esc := false
for {
var buf [1]byte
n, err := r.Stdin.Read(buf[:])
if n > 0 {
b := buf[0]
switch {
case !raw && b == '\\':
line = append(line, b)
esc = !esc
case !raw && b == '\n' && esc:
// line continuation
line = line[len(line)-1:]
esc = false
case b == '\n':
return line, nil
default:
line = append(line, b)
esc = false
}
}
if err == io.EOF && len(line) > 0 {
return line, nil
}
if err != nil {
return nil, err
}
}
}
func (r *Runner) changeDir(path string) int {
path = r.relPath(path)
info, err := r.stat(path)
if err != nil || !info.IsDir() {
return 1
}
if !hasPermissionToDir(info) {
return 1
}
r.Dir = path
r.Vars["OLDPWD"] = r.Vars["PWD"]
r.Vars["PWD"] = expand.Variable{Value: path}
return 0
}
func (r *Runner) relPath(path string) string {
if !filepath.IsAbs(path) {
path = filepath.Join(r.Dir, path)
}
return filepath.Clean(path)
}
type getopts struct {
argidx int
runeidx int
}
func (g *getopts) Next(optstr string, args []string) (opt rune, optarg string, done bool) {
if len(args) == 0 || g.argidx >= len(args) {
return '?', "", true
}
arg := []rune(args[g.argidx])
if len(arg) < 2 || arg[0] != '-' || arg[1] == '-' {
return '?', "", true
}
opts := arg[1:]
opt = opts[g.runeidx]
if g.runeidx+1 < len(opts) {
g.runeidx++
} else {
g.argidx++
g.runeidx = 0
}
i := strings.IndexRune(optstr, opt)
if i < 0 {
// invalid option
return '?', string(opt), false
}
if i+1 < len(optstr) && optstr[i+1] == ':' {
if g.argidx >= len(args) {
// missing argument
return ':', string(opt), false
}
optarg = args[g.argidx]
g.argidx++
g.runeidx = 0
}
return opt, optarg, false
}

7
vendor/mvdan.cc/sh/interp/doc.go vendored Normal file
View File

@@ -0,0 +1,7 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
// Package interp implements an interpreter that executes shell
// programs. It aims to support POSIX, but its support is not complete
// yet. It also supports some Bash features.
package interp

1271
vendor/mvdan.cc/sh/interp/interp.go vendored Normal file

File diff suppressed because it is too large Load Diff

164
vendor/mvdan.cc/sh/interp/module.go vendored Normal file
View File

@@ -0,0 +1,164 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package interp
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"runtime"
"strings"
"syscall"
"time"
"mvdan.cc/sh/expand"
)
// FromModuleContext returns the ModuleCtx value stored in ctx, if any.
func FromModuleContext(ctx context.Context) (ModuleCtx, bool) {
mc, ok := ctx.Value(moduleCtxKey{}).(ModuleCtx)
return mc, ok
}
type moduleCtxKey struct{}
// ModuleCtx is the data passed to all the module functions via a context value.
// It contains some of the current state of the Runner, as well as some fields
// necessary to implement some of the modules.
type ModuleCtx struct {
Env expand.Environ
Dir string
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
KillTimeout time.Duration
}
// UnixPath fixes absolute unix paths on Windows, for example converting
// "C:\\CurDir\\dev\\null" to "/dev/null".
func (mc ModuleCtx) UnixPath(path string) string {
if runtime.GOOS != "windows" {
return path
}
path = strings.TrimPrefix(path, mc.Dir)
return strings.Replace(path, `\`, `/`, -1)
}
// ModuleExec is the module responsible for executing a program. It is
// executed for all CallExpr nodes where the first argument is neither a
// declared function nor a builtin.
//
// Note that the name is included as the first argument. If path is an
// empty string, it means that the executable did not exist or was not
// found in $PATH.
//
// Use a return error of type ExitStatus to set the exit status. A nil error has
// the same effect as ExitStatus(0). If the error is of any other type, the
// interpreter will come to a stop.
type ModuleExec func(ctx context.Context, path string, args []string) error
func (ModuleExec) isModule() {}
var DefaultExec = ModuleExec(func(ctx context.Context, path string, args []string) error {
mc, _ := FromModuleContext(ctx)
if path == "" {
fmt.Fprintf(mc.Stderr, "%q: executable file not found in $PATH\n", args[0])
return ExitStatus(127)
}
cmd := exec.Cmd{
Path: path,
Args: args,
Env: execEnv(mc.Env),
Dir: mc.Dir,
Stdin: mc.Stdin,
Stdout: mc.Stdout,
Stderr: mc.Stderr,
}
err := cmd.Start()
if err == nil {
if done := ctx.Done(); done != nil {
go func() {
<-done
if mc.KillTimeout <= 0 || runtime.GOOS == "windows" {
_ = cmd.Process.Signal(os.Kill)
return
}
// TODO: don't temporarily leak this goroutine
// if the program stops itself with the
// interrupt.
go func() {
time.Sleep(mc.KillTimeout)
_ = cmd.Process.Signal(os.Kill)
}()
_ = cmd.Process.Signal(os.Interrupt)
}()
}
err = cmd.Wait()
}
switch x := err.(type) {
case *exec.ExitError:
// started, but errored - default to 1 if OS
// doesn't have exit statuses
if status, ok := x.Sys().(syscall.WaitStatus); ok {
if status.Signaled() && ctx.Err() != nil {
return ctx.Err()
}
return ExitStatus(status.ExitStatus())
}
return ExitStatus(1)
case *exec.Error:
// did not start
fmt.Fprintf(mc.Stderr, "%v\n", err)
return ExitStatus(127)
default:
return err
}
})
// ModuleOpen is the module responsible for opening a file. It is
// executed for all files that are opened directly by the shell, such as
// in redirects. Files opened by executed programs are not included.
//
// The path parameter is absolute and has been cleaned.
//
// Use a return error of type *os.PathError to have the error printed to
// stderr and the exit status set to 1. If the error is of any other type, the
// interpreter will come to a stop.
//
// TODO: What about stat calls? They are used heavily in the builtin
// test expressions, and also when doing a cd. Should they have a
// separate module?
type ModuleOpen func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error)
func (ModuleOpen) isModule() {}
var DefaultOpen = ModuleOpen(func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
return os.OpenFile(path, flag, perm)
})
func OpenDevImpls(next ModuleOpen) ModuleOpen {
return func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
mc, _ := FromModuleContext(ctx)
switch mc.UnixPath(path) {
case "/dev/null":
return devNull{}, nil
}
return next(ctx, path, flag, perm)
}
}
var _ io.ReadWriteCloser = devNull{}
type devNull struct{}
func (devNull) Read(p []byte) (int, error) { return 0, io.EOF }
func (devNull) Write(p []byte) (int, error) { return len(p), nil }
func (devNull) Close() error { return nil }

49
vendor/mvdan.cc/sh/interp/perm_unix.go vendored Normal file
View File

@@ -0,0 +1,49 @@
// Copyright (c) 2017, Andrey Nering <andrey.nering@gmail.com>
// See LICENSE for licensing information
// +build !windows
package interp
import (
"os"
"os/user"
"strconv"
"syscall"
)
// hasPermissionToDir returns if the OS current user has execute permission
// to the given directory
func hasPermissionToDir(info os.FileInfo) bool {
user, err := user.Current()
if err != nil {
return true
}
uid, _ := strconv.Atoi(user.Uid)
// super-user
if uid == 0 {
return true
}
st, _ := info.Sys().(*syscall.Stat_t)
if st == nil {
return true
}
perm := info.Mode().Perm()
// user (u)
if perm&0100 != 0 && st.Uid == uint32(uid) {
return true
}
gid, _ := strconv.Atoi(user.Gid)
// other users in group (g)
if perm&0010 != 0 && st.Uid != uint32(uid) && st.Gid == uint32(gid) {
return true
}
// remaining users (o)
if perm&0001 != 0 && st.Uid != uint32(uid) && st.Gid != uint32(gid) {
return true
}
return false
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) 2017, Andrey Nering <andrey.nering@gmail.com>
// See LICENSE for licensing information
package interp
import "os"
// hasPermissionToDir is a no-op on Windows.
func hasPermissionToDir(info os.FileInfo) bool {
return true
}

184
vendor/mvdan.cc/sh/interp/test.go vendored Normal file
View File

@@ -0,0 +1,184 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package interp
import (
"context"
"fmt"
"os"
"os/exec"
"regexp"
"golang.org/x/crypto/ssh/terminal"
"mvdan.cc/sh/syntax"
)
// non-empty string is true, empty string is false
func (r *Runner) bashTest(ctx context.Context, expr syntax.TestExpr, classic bool) string {
switch x := expr.(type) {
case *syntax.Word:
return r.document(x)
case *syntax.ParenTest:
return r.bashTest(ctx, x.X, classic)
case *syntax.BinaryTest:
switch x.Op {
case syntax.TsMatch, syntax.TsNoMatch:
str := r.literal(x.X.(*syntax.Word))
yw := x.Y.(*syntax.Word)
if classic { // test, [
lit := r.literal(yw)
if (str == lit) == (x.Op == syntax.TsMatch) {
return "1"
}
} else { // [[
pattern := r.pattern(yw)
if match(pattern, str) == (x.Op == syntax.TsMatch) {
return "1"
}
}
return ""
}
if r.binTest(x.Op, r.bashTest(ctx, x.X, classic), r.bashTest(ctx, x.Y, classic)) {
return "1"
}
return ""
case *syntax.UnaryTest:
if r.unTest(ctx, x.Op, r.bashTest(ctx, x.X, classic)) {
return "1"
}
return ""
}
return ""
}
func (r *Runner) binTest(op syntax.BinTestOperator, x, y string) bool {
switch op {
case syntax.TsReMatch:
re, err := regexp.Compile(y)
if err != nil {
r.exit = 2
return false
}
return re.MatchString(x)
case syntax.TsNewer:
info1, err1 := r.stat(x)
info2, err2 := r.stat(y)
if err1 != nil || err2 != nil {
return false
}
return info1.ModTime().After(info2.ModTime())
case syntax.TsOlder:
info1, err1 := r.stat(x)
info2, err2 := r.stat(y)
if err1 != nil || err2 != nil {
return false
}
return info1.ModTime().Before(info2.ModTime())
case syntax.TsDevIno:
info1, err1 := r.stat(x)
info2, err2 := r.stat(y)
if err1 != nil || err2 != nil {
return false
}
return os.SameFile(info1, info2)
case syntax.TsEql:
return atoi(x) == atoi(y)
case syntax.TsNeq:
return atoi(x) != atoi(y)
case syntax.TsLeq:
return atoi(x) <= atoi(y)
case syntax.TsGeq:
return atoi(x) >= atoi(y)
case syntax.TsLss:
return atoi(x) < atoi(y)
case syntax.TsGtr:
return atoi(x) > atoi(y)
case syntax.AndTest:
return x != "" && y != ""
case syntax.OrTest:
return x != "" || y != ""
case syntax.TsBefore:
return x < y
default: // syntax.TsAfter
return x > y
}
}
func (r *Runner) statMode(name string, mode os.FileMode) bool {
info, err := r.stat(name)
return err == nil && info.Mode()&mode != 0
}
func (r *Runner) unTest(ctx context.Context, op syntax.UnTestOperator, x string) bool {
switch op {
case syntax.TsExists:
_, err := r.stat(x)
return err == nil
case syntax.TsRegFile:
info, err := r.stat(x)
return err == nil && info.Mode().IsRegular()
case syntax.TsDirect:
return r.statMode(x, os.ModeDir)
case syntax.TsCharSp:
return r.statMode(x, os.ModeCharDevice)
case syntax.TsBlckSp:
info, err := r.stat(x)
return err == nil && info.Mode()&os.ModeDevice != 0 &&
info.Mode()&os.ModeCharDevice == 0
case syntax.TsNmPipe:
return r.statMode(x, os.ModeNamedPipe)
case syntax.TsSocket:
return r.statMode(x, os.ModeSocket)
case syntax.TsSmbLink:
info, err := os.Lstat(r.relPath(x))
return err == nil && info.Mode()&os.ModeSymlink != 0
case syntax.TsSticky:
return r.statMode(x, os.ModeSticky)
case syntax.TsUIDSet:
return r.statMode(x, os.ModeSetuid)
case syntax.TsGIDSet:
return r.statMode(x, os.ModeSetgid)
//case syntax.TsGrpOwn:
//case syntax.TsUsrOwn:
//case syntax.TsModif:
case syntax.TsRead:
f, err := r.open(ctx, r.relPath(x), os.O_RDONLY, 0, false)
if err == nil {
f.Close()
}
return err == nil
case syntax.TsWrite:
f, err := r.open(ctx, r.relPath(x), os.O_WRONLY, 0, false)
if err == nil {
f.Close()
}
return err == nil
case syntax.TsExec:
_, err := exec.LookPath(r.relPath(x))
return err == nil
case syntax.TsNoEmpty:
info, err := r.stat(x)
return err == nil && info.Size() > 0
case syntax.TsFdTerm:
return terminal.IsTerminal(atoi(x))
case syntax.TsEmpStr:
return x == ""
case syntax.TsNempStr:
return x != ""
case syntax.TsOptSet:
if opt := r.optByName(x, false); opt != nil {
return *opt
}
return false
case syntax.TsVarSet:
return r.lookupVar(x).IsSet()
case syntax.TsRefVar:
return r.lookupVar(x).NameRef
case syntax.TsNot:
return x == ""
default:
panic(fmt.Sprintf("unhandled unary test op: %v", op))
}
}

View File

@@ -0,0 +1,196 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package interp
import (
"fmt"
"mvdan.cc/sh/syntax"
)
const illegalTok = 0
type testParser struct {
eof bool
val string
rem []string
err func(err error)
}
func (p *testParser) errf(format string, a ...interface{}) {
p.err(fmt.Errorf(format, a...))
}
func (p *testParser) next() {
if p.eof || len(p.rem) == 0 {
p.eof = true
p.val = ""
return
}
p.val = p.rem[0]
p.rem = p.rem[1:]
}
func (p *testParser) followWord(fval string) *syntax.Word {
if p.eof {
p.errf("%s must be followed by a word", fval)
}
w := &syntax.Word{Parts: []syntax.WordPart{
&syntax.Lit{Value: p.val},
}}
p.next()
return w
}
func (p *testParser) classicTest(fval string, pastAndOr bool) syntax.TestExpr {
var left syntax.TestExpr
if pastAndOr {
left = p.testExprBase(fval)
} else {
left = p.classicTest(fval, true)
}
if left == nil || p.eof {
return left
}
opStr := p.val
op := testBinaryOp(p.val)
if op == illegalTok {
p.errf("not a valid test operator: %s", p.val)
}
b := &syntax.BinaryTest{
Op: op,
X: left,
}
p.next()
switch b.Op {
case syntax.AndTest, syntax.OrTest:
if b.Y = p.classicTest(opStr, false); b.Y == nil {
p.errf("%s must be followed by an expression", opStr)
}
default:
b.Y = p.followWord(opStr)
}
return b
}
func (p *testParser) testExprBase(fval string) syntax.TestExpr {
if p.eof {
return nil
}
op := testUnaryOp(p.val)
switch op {
case syntax.TsNot:
u := &syntax.UnaryTest{Op: op}
p.next()
u.X = p.classicTest(op.String(), false)
return u
case illegalTok:
return p.followWord(fval)
default:
u := &syntax.UnaryTest{Op: op}
p.next()
if p.eof {
// make [ -e ] fall back to [ -n -e ], i.e. use
// the operator as an argument
return &syntax.Word{Parts: []syntax.WordPart{
&syntax.Lit{Value: op.String()},
}}
}
u.X = p.followWord(op.String())
return u
}
}
// testUnaryOp is an exact copy of syntax's.
func testUnaryOp(val string) syntax.UnTestOperator {
switch val {
case "!":
return syntax.TsNot
case "-e", "-a":
return syntax.TsExists
case "-f":
return syntax.TsRegFile
case "-d":
return syntax.TsDirect
case "-c":
return syntax.TsCharSp
case "-b":
return syntax.TsBlckSp
case "-p":
return syntax.TsNmPipe
case "-S":
return syntax.TsSocket
case "-L", "-h":
return syntax.TsSmbLink
case "-k":
return syntax.TsSticky
case "-g":
return syntax.TsGIDSet
case "-u":
return syntax.TsUIDSet
case "-G":
return syntax.TsGrpOwn
case "-O":
return syntax.TsUsrOwn
case "-N":
return syntax.TsModif
case "-r":
return syntax.TsRead
case "-w":
return syntax.TsWrite
case "-x":
return syntax.TsExec
case "-s":
return syntax.TsNoEmpty
case "-t":
return syntax.TsFdTerm
case "-z":
return syntax.TsEmpStr
case "-n":
return syntax.TsNempStr
case "-o":
return syntax.TsOptSet
case "-v":
return syntax.TsVarSet
case "-R":
return syntax.TsRefVar
default:
return illegalTok
}
}
// testBinaryOp is like syntax's, but with -a and -o, and without =~.
func testBinaryOp(val string) syntax.BinTestOperator {
switch val {
case "-a":
return syntax.AndTest
case "-o":
return syntax.OrTest
case "==", "=":
return syntax.TsMatch
case "!=":
return syntax.TsNoMatch
case "-nt":
return syntax.TsNewer
case "-ot":
return syntax.TsOlder
case "-ef":
return syntax.TsDevIno
case "-eq":
return syntax.TsEql
case "-ne":
return syntax.TsNeq
case "-le":
return syntax.TsLeq
case "-ge":
return syntax.TsGeq
case "-lt":
return syntax.TsLss
case "-gt":
return syntax.TsGtr
default:
return illegalTok
}
}

321
vendor/mvdan.cc/sh/interp/vars.go vendored Normal file
View File

@@ -0,0 +1,321 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package interp
import (
"os"
"runtime"
"strconv"
"strings"
"mvdan.cc/sh/expand"
"mvdan.cc/sh/syntax"
)
type overlayEnviron struct {
parent expand.Environ
values map[string]expand.Variable
}
func (o overlayEnviron) Get(name string) expand.Variable {
if vr, ok := o.values[name]; ok {
return vr
}
return o.parent.Get(name)
}
func (o overlayEnviron) Set(name string, vr expand.Variable) {
o.values[name] = vr
}
func (o overlayEnviron) Each(f func(name string, vr expand.Variable) bool) {
o.parent.Each(f)
for name, vr := range o.values {
if !f(name, vr) {
return
}
}
}
func execEnv(env expand.Environ) []string {
list := make([]string, 0, 32)
env.Each(func(name string, vr expand.Variable) bool {
if vr.Exported {
list = append(list, name+"="+vr.String())
}
return true
})
return list
}
func (r *Runner) lookupVar(name string) expand.Variable {
if name == "" {
panic("variable name must not be empty")
}
var value interface{}
switch name {
case "#":
value = strconv.Itoa(len(r.Params))
case "@", "*":
value = r.Params
case "?":
value = strconv.Itoa(r.exit)
case "$":
value = strconv.Itoa(os.Getpid())
case "PPID":
value = strconv.Itoa(os.Getppid())
case "DIRSTACK":
value = r.dirStack
case "0":
if r.filename != "" {
value = r.filename
} else {
value = "gosh"
}
case "1", "2", "3", "4", "5", "6", "7", "8", "9":
i := int(name[0] - '1')
if i < len(r.Params) {
value = r.Params[i]
} else {
value = ""
}
}
if value != nil {
return expand.Variable{Value: value}
}
if value, e := r.cmdVars[name]; e {
return expand.Variable{Value: value}
}
if vr, e := r.funcVars[name]; e {
vr.Local = true
return vr
}
if vr, e := r.Vars[name]; e {
return vr
}
if vr := r.Env.Get(name); vr.IsSet() {
return vr
}
if runtime.GOOS == "windows" {
upper := strings.ToUpper(name)
if vr := r.Env.Get(upper); vr.IsSet() {
return vr
}
}
if r.opts[optNoUnset] {
r.errf("%s: unbound variable\n", name)
r.setErr(ShellExitStatus(1))
}
return expand.Variable{}
}
func (r *Runner) envGet(name string) string {
return r.lookupVar(name).String()
}
func (r *Runner) delVar(name string) {
vr := r.lookupVar(name)
if vr.ReadOnly {
r.errf("%s: readonly variable\n", name)
r.exit = 1
return
}
if vr.Local {
// don't overwrite a non-local var with the same name
r.funcVars[name] = expand.Variable{}
} else {
r.Vars[name] = expand.Variable{} // to not query r.Env
}
}
func (r *Runner) setVarString(name, value string) {
r.setVar(name, nil, expand.Variable{Value: value})
}
func (r *Runner) setVarInternal(name string, vr expand.Variable) {
if _, ok := vr.Value.(string); ok {
if r.opts[optAllExport] {
vr.Exported = true
}
} else {
vr.Exported = false
}
if vr.Local {
if r.funcVars == nil {
r.funcVars = make(map[string]expand.Variable)
}
r.funcVars[name] = vr
} else {
r.Vars[name] = vr
}
}
func (r *Runner) setVar(name string, index syntax.ArithmExpr, vr expand.Variable) {
cur := r.lookupVar(name)
if cur.ReadOnly {
r.errf("%s: readonly variable\n", name)
r.exit = 1
return
}
if name2, var2 := cur.Resolve(r.Env); name2 != "" {
name = name2
cur = var2
vr.NameRef = false
cur.NameRef = false
}
_, isIndexArray := cur.Value.([]string)
_, isAssocArray := cur.Value.(map[string]string)
if _, ok := vr.Value.(string); ok && index == nil {
// When assigning a string to an array, fall back to the
// zero value for the index.
if isIndexArray {
index = &syntax.Word{Parts: []syntax.WordPart{
&syntax.Lit{Value: "0"},
}}
} else if isAssocArray {
index = &syntax.Word{Parts: []syntax.WordPart{
&syntax.DblQuoted{},
}}
}
}
if index == nil {
r.setVarInternal(name, vr)
return
}
// from the syntax package, we know that value must be a string if index
// is non-nil; nested arrays are forbidden.
valStr := vr.Value.(string)
// if the existing variable is already an AssocArray, try our best
// to convert the key to a string
if isAssocArray {
amap := cur.Value.(map[string]string)
w, ok := index.(*syntax.Word)
if !ok {
return
}
k := r.literal(w)
amap[k] = valStr
cur.Value = amap
r.setVarInternal(name, cur)
return
}
var list []string
switch x := cur.Value.(type) {
case string:
list = append(list, x)
case []string:
list = x
case map[string]string: // done above
}
k := r.arithm(index)
for len(list) < k+1 {
list = append(list, "")
}
list[k] = valStr
cur.Value = list
r.setVarInternal(name, cur)
}
func (r *Runner) setFunc(name string, body *syntax.Stmt) {
if r.Funcs == nil {
r.Funcs = make(map[string]*syntax.Stmt, 4)
}
r.Funcs[name] = body
}
func stringIndex(index syntax.ArithmExpr) bool {
w, ok := index.(*syntax.Word)
if !ok || len(w.Parts) != 1 {
return false
}
switch w.Parts[0].(type) {
case *syntax.DblQuoted, *syntax.SglQuoted:
return true
}
return false
}
func (r *Runner) assignVal(as *syntax.Assign, valType string) interface{} {
prev := r.lookupVar(as.Name.Value)
if as.Naked {
return prev.Value
}
if as.Value != nil {
s := r.literal(as.Value)
if !as.Append || !prev.IsSet() {
return s
}
switch x := prev.Value.(type) {
case string:
return x + s
case []string:
if len(x) == 0 {
x = append(x, "")
}
x[0] += s
return x
case map[string]string:
// TODO
}
return s
}
if as.Array == nil {
// don't return nil, as that's an unset variable
return ""
}
elems := as.Array.Elems
if valType == "" {
if len(elems) == 0 || !stringIndex(elems[0].Index) {
valType = "-a" // indexed
} else {
valType = "-A" // associative
}
}
if valType == "-A" {
// associative array
amap := make(map[string]string, len(elems))
for _, elem := range elems {
k := r.literal(elem.Index.(*syntax.Word))
amap[k] = r.literal(elem.Value)
}
if !as.Append || !prev.IsSet() {
return amap
}
// TODO
return amap
}
// indexed array
maxIndex := len(elems) - 1
indexes := make([]int, len(elems))
for i, elem := range elems {
if elem.Index == nil {
indexes[i] = i
continue
}
k := r.arithm(elem.Index)
indexes[i] = k
if k > maxIndex {
maxIndex = k
}
}
strs := make([]string, maxIndex+1)
for i, elem := range elems {
strs[indexes[i]] = r.literal(elem.Value)
}
if !as.Append || !prev.IsSet() {
return strs
}
switch x := prev.Value.(type) {
case string:
return append([]string{x}, strs...)
case []string:
return append(x, strs...)
case map[string]string:
// TODO
}
return strs
}

14
vendor/mvdan.cc/sh/shell/doc.go vendored Normal file
View File

@@ -0,0 +1,14 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
// Package shell contains high-level features that use the syntax, expand, and
// interp packages under the hood.
//
// Please note that this package uses POSIX Shell syntax. As such, path names on
// Windows need to use double backslashes or be within single quotes when given
// to functions like Fields. For example:
//
// shell.Fields("echo /foo/bar") // on Unix-like
// shell.Fields("echo C:\\foo\\bar") // on Windows
// shell.Fields("echo 'C:\foo\bar'") // on Windows, with quotes
package shell

63
vendor/mvdan.cc/sh/shell/expand.go vendored Normal file
View File

@@ -0,0 +1,63 @@
// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package shell
import (
"os"
"strings"
"mvdan.cc/sh/expand"
"mvdan.cc/sh/syntax"
)
// Expand performs shell expansion on s as if it were within double quotes,
// using env to resolve variables. This includes parameter expansion, arithmetic
// expansion, and quote removal.
//
// If env is nil, the current environment variables are used. Empty variables
// are treated as unset; to support variables which are set but empty, use the
// expand package directly.
//
// Command subsitutions like $(echo foo) aren't supported to avoid running
// arbitrary code. To support those, use an interpreter with the expand package.
//
// An error will be reported if the input string had invalid syntax.
func Expand(s string, env func(string) string) (string, error) {
p := syntax.NewParser()
word, err := p.Document(strings.NewReader(s))
if err != nil {
return "", err
}
if env == nil {
env = os.Getenv
}
cfg := &expand.Config{Env: expand.FuncEnviron(env)}
return expand.Document(cfg, word)
}
// Fields performs shell expansion on s as if it were a command's arguments,
// using env to resolve variables. It is similar to Expand, but includes brace
// expansion, tilde expansion, and globbing.
//
// If env is nil, the current environment variables are used. Empty variables
// are treated as unset; to support variables which are set but empty, use the
// expand package directly.
//
// An error will be reported if the input string had invalid syntax.
func Fields(s string, env func(string) string) ([]string, error) {
p := syntax.NewParser()
var words []*syntax.Word
err := p.Words(strings.NewReader(s), func(w *syntax.Word) bool {
words = append(words, w)
return true
})
if err != nil {
return nil, err
}
if env == nil {
env = os.Getenv
}
cfg := &expand.Config{Env: expand.FuncEnviron(env)}
return expand.Fields(cfg, words...)
}

56
vendor/mvdan.cc/sh/shell/source.go vendored Normal file
View File

@@ -0,0 +1,56 @@
// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package shell
import (
"context"
"fmt"
"os"
"mvdan.cc/sh/expand"
"mvdan.cc/sh/interp"
"mvdan.cc/sh/syntax"
)
// SourceFile sources a shell file from disk and returns the variables
// declared in it. It is a convenience function that uses a default shell
// parser, parses a file from disk, and calls SourceNode.
//
// This function should be used with caution, as it can interpret arbitrary
// code. Untrusted shell programs shoudn't be sourced outside of a sandbox
// environment.
func SourceFile(ctx context.Context, path string) (map[string]expand.Variable, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("could not open: %v", err)
}
defer f.Close()
file, err := syntax.NewParser().Parse(f, path)
if err != nil {
return nil, fmt.Errorf("could not parse: %v", err)
}
return SourceNode(ctx, file)
}
// SourceNode sources a shell program from a node and returns the
// variables declared in it. It accepts the same set of node types that
// interp/Runner.Run does.
//
// This function should be used with caution, as it can interpret arbitrary
// code. Untrusted shell programs shoudn't be sourced outside of a sandbox
// environment.
func SourceNode(ctx context.Context, node syntax.Node) (map[string]expand.Variable, error) {
r, _ := interp.New()
if err := r.Run(ctx, node); err != nil {
return nil, fmt.Errorf("could not run: %v", err)
}
// delete the internal shell vars that the user is not
// interested in
delete(r.Vars, "PWD")
delete(r.Vars, "HOME")
delete(r.Vars, "PATH")
delete(r.Vars, "IFS")
delete(r.Vars, "OPTIND")
return r.Vars, nil
}

37
vendor/mvdan.cc/sh/syntax/canonical.sh vendored Normal file
View File

@@ -0,0 +1,37 @@
#!/bin/bash
# separate comment
! foo bar >a &
foo() { bar; }
{
var1="some long value" # var1 comment
var2=short # var2 comment
}
if foo; then bar; fi
for foo in a b c; do
bar
done
case $foo in
a) A ;;
b)
B
;;
esac
foo | bar
foo &&
$(bar) &&
(more)
foo 2>&1
foo <<-EOF
bar
EOF
$((3 + 4))

6
vendor/mvdan.cc/sh/syntax/doc.go vendored Normal file
View File

@@ -0,0 +1,6 @@
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
// Package syntax implements parsing and formatting of shell programs.
// It supports both POSIX Shell and Bash.
package syntax

282
vendor/mvdan.cc/sh/syntax/expand.go vendored Normal file
View File

@@ -0,0 +1,282 @@
// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import "strconv"
// TODO(v3): Consider making these special syntax nodes.
// Among other things, we can make use of Word.Lit.
type brace struct {
seq bool // {x..y[..incr]} instead of {x,y[,...]}
chars bool // sequence is of chars, not numbers
elems []*braceWord
}
// braceWord is like Word, but with braceWordPart.
type braceWord struct {
parts []braceWordPart
}
// braceWordPart contains any WordPart or a brace.
type braceWordPart interface{}
var (
litLeftBrace = &Lit{Value: "{"}
litComma = &Lit{Value: ","}
litDots = &Lit{Value: ".."}
litRightBrace = &Lit{Value: "}"}
)
func splitBraces(word *Word) (*braceWord, bool) {
any := false
top := &braceWord{}
acc := top
var cur *brace
open := []*brace{}
pop := func() *brace {
old := cur
open = open[:len(open)-1]
if len(open) == 0 {
cur = nil
acc = top
} else {
cur = open[len(open)-1]
acc = cur.elems[len(cur.elems)-1]
}
return old
}
addLit := func(lit *Lit) {
acc.parts = append(acc.parts, lit)
}
addParts := func(parts ...braceWordPart) {
acc.parts = append(acc.parts, parts...)
}
for _, wp := range word.Parts {
lit, ok := wp.(*Lit)
if !ok {
addParts(wp)
continue
}
last := 0
for j := 0; j < len(lit.Value); j++ {
addlitidx := func() {
if last == j {
return // empty lit
}
l2 := *lit
l2.Value = l2.Value[last:j]
addLit(&l2)
}
switch lit.Value[j] {
case '{':
addlitidx()
acc = &braceWord{}
cur = &brace{elems: []*braceWord{acc}}
open = append(open, cur)
case ',':
if cur == nil {
continue
}
addlitidx()
acc = &braceWord{}
cur.elems = append(cur.elems, acc)
case '.':
if cur == nil {
continue
}
if j+1 >= len(lit.Value) || lit.Value[j+1] != '.' {
continue
}
addlitidx()
cur.seq = true
acc = &braceWord{}
cur.elems = append(cur.elems, acc)
j++
case '}':
if cur == nil {
continue
}
any = true
addlitidx()
br := pop()
if len(br.elems) == 1 {
// return {x} to a non-brace
addLit(litLeftBrace)
addParts(br.elems[0].parts...)
addLit(litRightBrace)
break
}
if !br.seq {
addParts(br)
break
}
var chars [2]bool
broken := false
for i, elem := range br.elems[:2] {
val := braceWordLit(elem)
if _, err := strconv.Atoi(val); err == nil {
} else if len(val) == 1 &&
'a' <= val[0] && val[0] <= 'z' {
chars[i] = true
} else {
broken = true
}
}
if len(br.elems) == 3 {
// increment must be a number
val := braceWordLit(br.elems[2])
if _, err := strconv.Atoi(val); err != nil {
broken = true
}
}
// are start and end both chars or
// non-chars?
if chars[0] != chars[1] {
broken = true
}
if !broken {
br.chars = chars[0]
addParts(br)
break
}
// return broken {x..y[..incr]} to a non-brace
addLit(litLeftBrace)
for i, elem := range br.elems {
if i > 0 {
addLit(litDots)
}
addParts(elem.parts...)
}
addLit(litRightBrace)
default:
continue
}
last = j + 1
}
if last == 0 {
addLit(lit)
} else {
left := *lit
left.Value = left.Value[last:]
addLit(&left)
}
}
// open braces that were never closed fall back to non-braces
for acc != top {
br := pop()
addLit(litLeftBrace)
for i, elem := range br.elems {
if i > 0 {
if br.seq {
addLit(litDots)
} else {
addLit(litComma)
}
}
addParts(elem.parts...)
}
}
return top, any
}
func braceWordLit(v interface{}) string {
word, _ := v.(*braceWord)
if word == nil || len(word.parts) != 1 {
return ""
}
lit, ok := word.parts[0].(*Lit)
if !ok {
return ""
}
return lit.Value
}
func expandRec(bw *braceWord) []*Word {
var all []*Word
var left []WordPart
for i, wp := range bw.parts {
br, ok := wp.(*brace)
if !ok {
left = append(left, wp.(WordPart))
continue
}
if br.seq {
var from, to int
if br.chars {
from = int(braceWordLit(br.elems[0])[0])
to = int(braceWordLit(br.elems[1])[0])
} else {
from, _ = strconv.Atoi(braceWordLit(br.elems[0]))
to, _ = strconv.Atoi(braceWordLit(br.elems[1]))
}
upward := from <= to
incr := 1
if !upward {
incr = -1
}
if len(br.elems) > 2 {
val := braceWordLit(br.elems[2])
n, _ := strconv.Atoi(val)
if n != 0 && n > 0 == upward {
incr = n
}
}
n := from
for {
if upward && n > to {
break
}
if !upward && n < to {
break
}
next := *bw
next.parts = next.parts[i+1:]
lit := &Lit{}
if br.chars {
lit.Value = string(n)
} else {
lit.Value = strconv.Itoa(n)
}
next.parts = append([]braceWordPart{lit}, next.parts...)
exp := expandRec(&next)
for _, w := range exp {
w.Parts = append(left, w.Parts...)
}
all = append(all, exp...)
n += incr
}
return all
}
for _, elem := range br.elems {
next := *bw
next.parts = next.parts[i+1:]
next.parts = append(elem.parts, next.parts...)
exp := expandRec(&next)
for _, w := range exp {
w.Parts = append(left, w.Parts...)
}
all = append(all, exp...)
}
return all
}
return []*Word{{Parts: left}}
}
// TODO(v3): remove
// ExpandBraces performs Bash brace expansion on a word. For example,
// passing it a single-literal word "foo{bar,baz}" will return two
// single-literal words, "foobar" and "foobaz".
//
// Deprecated: use mvdan.cc/sh/expand.Braces instead.
func ExpandBraces(word *Word) []*Word {
topBrace, any := splitBraces(word)
if !any {
return []*Word{word}
}
return expandRec(topBrace)
}

1100
vendor/mvdan.cc/sh/syntax/lexer.go vendored Normal file

File diff suppressed because it is too large Load Diff

885
vendor/mvdan.cc/sh/syntax/nodes.go vendored Normal file
View File

@@ -0,0 +1,885 @@
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import (
"fmt"
"strings"
)
// Node represents a syntax tree node.
type Node interface {
// Pos returns the position of the first character of the node. Comments
// are ignored, except if the node is a *File.
Pos() Pos
// End returns the position of the character immediately after the node.
// If the character is a newline, the line number won't cross into the
// next line. Comments are ignored, except if the node is a *File.
End() Pos
}
// File represents a shell source file.
type File struct {
Name string
StmtList
}
// StmtList is a list of statements with any number of trailing comments. Both
// lists can be empty.
type StmtList struct {
Stmts []*Stmt
Last []Comment
}
func (s StmtList) pos() Pos {
if len(s.Stmts) > 0 {
s := s.Stmts[0]
sPos := s.Pos()
if len(s.Comments) > 0 {
if cPos := s.Comments[0].Pos(); sPos.After(cPos) {
return cPos
}
}
return sPos
}
if len(s.Last) > 0 {
return s.Last[0].Pos()
}
return Pos{}
}
func (s StmtList) end() Pos {
if len(s.Last) > 0 {
return s.Last[len(s.Last)-1].End()
}
if len(s.Stmts) > 0 {
s := s.Stmts[len(s.Stmts)-1]
sEnd := s.End()
if len(s.Comments) > 0 {
if cEnd := s.Comments[0].End(); cEnd.After(sEnd) {
return cEnd
}
}
return sEnd
}
return Pos{}
}
func (s StmtList) empty() bool {
return len(s.Stmts) == 0 && len(s.Last) == 0
}
// Pos is a position within a shell source file.
type Pos struct {
offs uint32
line, col uint16
}
// Offset returns the byte offset of the position in the original source file.
// Byte offsets start at 0.
func (p Pos) Offset() uint { return uint(p.offs) }
// Line returns the line number of the position, starting at 1.
func (p Pos) Line() uint { return uint(p.line) }
// Col returns the column number of the position, starting at 1. It counts in
// bytes.
func (p Pos) Col() uint { return uint(p.col) }
func (p Pos) String() string {
return fmt.Sprintf("%d:%d", p.Line(), p.Col())
}
// IsValid reports whether the position is valid. All positions in nodes
// returned by Parse are valid.
func (p Pos) IsValid() bool { return p.line > 0 }
// After reports whether the position p is after p2. It is a more expressive
// version of p.Offset() > p2.Offset().
func (p Pos) After(p2 Pos) bool { return p.offs > p2.offs }
func (f *File) Pos() Pos { return f.StmtList.pos() }
func (f *File) End() Pos { return f.StmtList.end() }
func posAddCol(p Pos, n int) Pos {
p.col += uint16(n)
p.offs += uint32(n)
return p
}
func posMax(p1, p2 Pos) Pos {
if p2.After(p1) {
return p2
}
return p1
}
// Comment represents a single comment on a single line.
type Comment struct {
Hash Pos
Text string
}
func (c *Comment) Pos() Pos { return c.Hash }
func (c *Comment) End() Pos { return posAddCol(c.Hash, 1+len(c.Text)) }
// Stmt represents a statement, also known as a "complete command". It is
// compromised of a command and other components that may come before or after
// it.
type Stmt struct {
Comments []Comment
Cmd Command
Position Pos
Semicolon Pos // position of ';', '&', or '|&', if any
Negated bool // ! stmt
Background bool // stmt &
Coprocess bool // mksh's |&
Redirs []*Redirect // stmt >a <b
}
func (s *Stmt) Pos() Pos { return s.Position }
func (s *Stmt) End() Pos {
if s.Semicolon.IsValid() {
end := posAddCol(s.Semicolon, 1) // ';' or '&'
if s.Coprocess {
end = posAddCol(end, 1) // '|&'
}
return end
}
end := s.Position
if s.Negated {
end = posAddCol(end, 1)
}
if s.Cmd != nil {
end = s.Cmd.End()
}
if len(s.Redirs) > 0 {
end = posMax(end, s.Redirs[len(s.Redirs)-1].End())
}
return end
}
// Command represents all nodes that are simple or compound commands, including
// function declarations.
//
// These are *CallExpr, *IfClause, *WhileClause, *ForClause, *CaseClause,
// *Block, *Subshell, *BinaryCmd, *FuncDecl, *ArithmCmd, *TestClause,
// *DeclClause, *LetClause, *TimeClause, and *CoprocClause.
type Command interface {
Node
commandNode()
}
func (*CallExpr) commandNode() {}
func (*IfClause) commandNode() {}
func (*WhileClause) commandNode() {}
func (*ForClause) commandNode() {}
func (*CaseClause) commandNode() {}
func (*Block) commandNode() {}
func (*Subshell) commandNode() {}
func (*BinaryCmd) commandNode() {}
func (*FuncDecl) commandNode() {}
func (*ArithmCmd) commandNode() {}
func (*TestClause) commandNode() {}
func (*DeclClause) commandNode() {}
func (*LetClause) commandNode() {}
func (*TimeClause) commandNode() {}
func (*CoprocClause) commandNode() {}
// Assign represents an assignment to a variable.
//
// Here and elsewhere, Index can mean either an index expression into an indexed
// array, or a string key into an associative array.
//
// If Index is non-nil, the value will be a word and not an array as nested
// arrays are not allowed.
//
// If Naked is true and Name is nil, the assignment is part of a DeclClause and
// the assignment expression (in the Value field) will be evaluated at run-time.
type Assign struct {
Append bool // +=
Naked bool // without '='
Name *Lit
Index ArithmExpr // [i], ["k"]
Value *Word // =val
Array *ArrayExpr // =(arr)
}
func (a *Assign) Pos() Pos {
if a.Name == nil {
return a.Value.Pos()
}
return a.Name.Pos()
}
func (a *Assign) End() Pos {
if a.Value != nil {
return a.Value.End()
}
if a.Array != nil {
return a.Array.End()
}
if a.Index != nil {
return posAddCol(a.Index.End(), 2)
}
if a.Naked {
return a.Name.End()
}
return posAddCol(a.Name.End(), 1)
}
// Redirect represents an input/output redirection.
type Redirect struct {
OpPos Pos
Op RedirOperator
N *Lit // fd>, or {varname}> in Bash
Word *Word // >word
Hdoc *Word // here-document body
}
func (r *Redirect) Pos() Pos {
if r.N != nil {
return r.N.Pos()
}
return r.OpPos
}
func (r *Redirect) End() Pos {
if r.Hdoc != nil {
return r.Hdoc.End()
}
return r.Word.End()
}
// CallExpr represents a command execution or function call, otherwise known as
// a "simple command".
//
// If Args is empty, Assigns apply to the shell environment. Otherwise, they are
// variables that cannot be arrays and which only apply to the call.
type CallExpr struct {
Assigns []*Assign // a=x b=y args
Args []*Word
}
func (c *CallExpr) Pos() Pos {
if len(c.Assigns) > 0 {
return c.Assigns[0].Pos()
}
return c.Args[0].Pos()
}
func (c *CallExpr) End() Pos {
if len(c.Args) == 0 {
return c.Assigns[len(c.Assigns)-1].End()
}
return c.Args[len(c.Args)-1].End()
}
// Subshell represents a series of commands that should be executed in a nested
// shell environment.
type Subshell struct {
Lparen, Rparen Pos
StmtList
}
func (s *Subshell) Pos() Pos { return s.Lparen }
func (s *Subshell) End() Pos { return posAddCol(s.Rparen, 1) }
// Block represents a series of commands that should be executed in a nested
// scope.
type Block struct {
Lbrace, Rbrace Pos
StmtList
}
func (b *Block) Pos() Pos { return b.Lbrace }
func (b *Block) End() Pos { return posAddCol(b.Rbrace, 1) }
// TODO(v3): Refactor and simplify elif/else. For example, we could likely make
// Else an *IfClause, remove ElsePos, make IfPos also do opening "else"
// positions, and join the comment slices as Last []Comment.
// IfClause represents an if statement.
type IfClause struct {
Elif bool // whether this IfClause begins with "elif"
IfPos Pos // position of the starting "if" or "elif" token
ThenPos Pos
ElsePos Pos // position of a following "else" or "elif", if any
FiPos Pos // position of "fi", empty if Elif == true
Cond StmtList
Then StmtList
Else StmtList
ElseComments []Comment // comments on the "else"
FiComments []Comment // comments on the "fi"
}
func (c *IfClause) Pos() Pos { return c.IfPos }
func (c *IfClause) End() Pos {
if !c.FiPos.IsValid() {
return posAddCol(c.ElsePos, 4)
}
return posAddCol(c.FiPos, 2)
}
// FollowedByElif reports whether this IfClause is followed by an "elif"
// IfClause in its Else branch. This is true if Else.Stmts has exactly one
// statement with an IfClause whose Elif field is true.
func (c *IfClause) FollowedByElif() bool {
if len(c.Else.Stmts) != 1 {
return false
}
ic, _ := c.Else.Stmts[0].Cmd.(*IfClause)
return ic != nil && ic.Elif
}
func (c *IfClause) bodyEndPos() Pos {
if c.ElsePos.IsValid() {
return c.ElsePos
}
return c.FiPos
}
// WhileClause represents a while or an until clause.
type WhileClause struct {
WhilePos, DoPos, DonePos Pos
Until bool
Cond StmtList
Do StmtList
}
func (w *WhileClause) Pos() Pos { return w.WhilePos }
func (w *WhileClause) End() Pos { return posAddCol(w.DonePos, 4) }
// ForClause represents a for or a select clause. The latter is only present in
// Bash.
type ForClause struct {
ForPos, DoPos, DonePos Pos
Select bool
Loop Loop
Do StmtList
}
func (f *ForClause) Pos() Pos { return f.ForPos }
func (f *ForClause) End() Pos { return posAddCol(f.DonePos, 4) }
// Loop holds either *WordIter or *CStyleLoop.
type Loop interface {
Node
loopNode()
}
func (*WordIter) loopNode() {}
func (*CStyleLoop) loopNode() {}
// WordIter represents the iteration of a variable over a series of words in a
// for clause. If InPos is an invalid position, the "in" token was missing, so
// the iteration is over the shell's positional parameters.
type WordIter struct {
Name *Lit
InPos Pos // position of "in"
Items []*Word
}
func (w *WordIter) Pos() Pos { return w.Name.Pos() }
func (w *WordIter) End() Pos {
if len(w.Items) > 0 {
return wordLastEnd(w.Items)
}
return posMax(w.Name.End(), posAddCol(w.InPos, 2))
}
// CStyleLoop represents the behaviour of a for clause similar to the C
// language.
//
// This node will only appear with LangBash.
type CStyleLoop struct {
Lparen, Rparen Pos
Init, Cond, Post ArithmExpr
}
func (c *CStyleLoop) Pos() Pos { return c.Lparen }
func (c *CStyleLoop) End() Pos { return posAddCol(c.Rparen, 2) }
// BinaryCmd represents a binary expression between two statements.
type BinaryCmd struct {
OpPos Pos
Op BinCmdOperator
X, Y *Stmt
}
func (b *BinaryCmd) Pos() Pos { return b.X.Pos() }
func (b *BinaryCmd) End() Pos { return b.Y.End() }
// FuncDecl represents the declaration of a function.
type FuncDecl struct {
Position Pos
RsrvWord bool // non-posix "function f()" style
Name *Lit
Body *Stmt
}
func (f *FuncDecl) Pos() Pos { return f.Position }
func (f *FuncDecl) End() Pos { return f.Body.End() }
// Word represents a shell word, containing one or more word parts contiguous to
// each other. The word is delimeted by word boundaries, such as spaces,
// newlines, semicolons, or parentheses.
type Word struct {
Parts []WordPart
}
func (w *Word) Pos() Pos { return w.Parts[0].Pos() }
func (w *Word) End() Pos { return w.Parts[len(w.Parts)-1].End() }
// Lit returns the word as a literal value, if the word consists of *syntax.Lit
// nodes only. An empty string is returned otherwise. Words with multiple
// literals, which can appear in some edge cases, are handled properly.
//
// For example, the word "foo" will return "foo", but the word "foo${bar}" will
// return "".
func (w *Word) Lit() string {
// In the usual case, we'll have either a single part that's a literal,
// or one of the parts being a non-literal. Using strings.Join instead
// of a strings.Builder avoids extra work in these cases, since a single
// part is a shortcut, and many parts don't incur string copies.
lits := make([]string, 0, 1)
for _, part := range w.Parts {
lit, ok := part.(*Lit)
if !ok {
return ""
}
lits = append(lits, lit.Value)
}
return strings.Join(lits, "")
}
// WordPart represents all nodes that can form part of a word.
//
// These are *Lit, *SglQuoted, *DblQuoted, *ParamExp, *CmdSubst, *ArithmExp,
// *ProcSubst, and *ExtGlob.
type WordPart interface {
Node
wordPartNode()
}
func (*Lit) wordPartNode() {}
func (*SglQuoted) wordPartNode() {}
func (*DblQuoted) wordPartNode() {}
func (*ParamExp) wordPartNode() {}
func (*CmdSubst) wordPartNode() {}
func (*ArithmExp) wordPartNode() {}
func (*ProcSubst) wordPartNode() {}
func (*ExtGlob) wordPartNode() {}
// Lit represents a string literal.
//
// Note that a parsed string literal may not appear as-is in the original source
// code, as it is possible to split literals by escaping newlines. The splitting
// is lost, but the end position is not.
type Lit struct {
ValuePos, ValueEnd Pos
Value string
}
func (l *Lit) Pos() Pos { return l.ValuePos }
func (l *Lit) End() Pos { return l.ValueEnd }
// SglQuoted represents a string within single quotes.
type SglQuoted struct {
Left, Right Pos
Dollar bool // $''
Value string
}
func (q *SglQuoted) Pos() Pos { return q.Left }
func (q *SglQuoted) End() Pos { return posAddCol(q.Right, 1) }
// DblQuoted represents a list of nodes within double quotes.
type DblQuoted struct {
Position Pos
Dollar bool // $""
Parts []WordPart
}
func (q *DblQuoted) Pos() Pos { return q.Position }
func (q *DblQuoted) End() Pos {
if len(q.Parts) == 0 {
if q.Dollar {
return posAddCol(q.Position, 3)
}
return posAddCol(q.Position, 2)
}
return posAddCol(q.Parts[len(q.Parts)-1].End(), 1)
}
// CmdSubst represents a command substitution.
type CmdSubst struct {
Left, Right Pos
StmtList
TempFile bool // mksh's ${ foo;}
ReplyVar bool // mksh's ${|foo;}
}
func (c *CmdSubst) Pos() Pos { return c.Left }
func (c *CmdSubst) End() Pos { return posAddCol(c.Right, 1) }
// ParamExp represents a parameter expansion.
type ParamExp struct {
Dollar, Rbrace Pos
Short bool // $a instead of ${a}
Excl bool // ${!a}
Length bool // ${#a}
Width bool // ${%a}
Param *Lit
Index ArithmExpr // ${a[i]}, ${a["k"]}
Slice *Slice // ${a:x:y}
Repl *Replace // ${a/x/y}
Names ParNamesOperator // ${!prefix*} or ${!prefix@}
Exp *Expansion // ${a:-b}, ${a#b}, etc
}
func (p *ParamExp) Pos() Pos { return p.Dollar }
func (p *ParamExp) End() Pos {
if !p.Short {
return posAddCol(p.Rbrace, 1)
}
if p.Index != nil {
return posAddCol(p.Index.End(), 1)
}
return p.Param.End()
}
func (p *ParamExp) nakedIndex() bool {
return p.Short && p.Index != nil
}
// Slice represents a character slicing expression inside a ParamExp.
//
// This node will only appear in LangBash and LangMirBSDKorn.
type Slice struct {
Offset, Length ArithmExpr
}
// Replace represents a search and replace expression inside a ParamExp.
type Replace struct {
All bool
Orig, With *Word
}
// Expansion represents string manipulation in a ParamExp other than those
// covered by Replace.
type Expansion struct {
Op ParExpOperator
Word *Word
}
// ArithmExp represents an arithmetic expansion.
type ArithmExp struct {
Left, Right Pos
Bracket bool // deprecated $[expr] form
Unsigned bool // mksh's $((# expr))
X ArithmExpr
}
func (a *ArithmExp) Pos() Pos { return a.Left }
func (a *ArithmExp) End() Pos {
if a.Bracket {
return posAddCol(a.Right, 1)
}
return posAddCol(a.Right, 2)
}
// ArithmCmd represents an arithmetic command.
//
// This node will only appear in LangBash and LangMirBSDKorn.
type ArithmCmd struct {
Left, Right Pos
Unsigned bool // mksh's ((# expr))
X ArithmExpr
}
func (a *ArithmCmd) Pos() Pos { return a.Left }
func (a *ArithmCmd) End() Pos { return posAddCol(a.Right, 2) }
// ArithmExpr represents all nodes that form arithmetic expressions.
//
// These are *BinaryArithm, *UnaryArithm, *ParenArithm, and *Word.
type ArithmExpr interface {
Node
arithmExprNode()
}
func (*BinaryArithm) arithmExprNode() {}
func (*UnaryArithm) arithmExprNode() {}
func (*ParenArithm) arithmExprNode() {}
func (*Word) arithmExprNode() {}
// BinaryArithm represents a binary arithmetic expression.
//
// If Op is any assign operator, X will be a word with a single *Lit whose value
// is a valid name.
//
// Ternary operators like "a ? b : c" are fit into this structure. Thus, if
// Op==Quest, Y will be a *BinaryArithm with Op==Colon. Op can only be Colon in
// that scenario.
type BinaryArithm struct {
OpPos Pos
Op BinAritOperator
X, Y ArithmExpr
}
func (b *BinaryArithm) Pos() Pos { return b.X.Pos() }
func (b *BinaryArithm) End() Pos { return b.Y.End() }
// UnaryArithm represents an unary arithmetic expression. The unary opearator
// may come before or after the sub-expression.
//
// If Op is Inc or Dec, X will be a word with a single *Lit whose value is a
// valid name.
type UnaryArithm struct {
OpPos Pos
Op UnAritOperator
Post bool
X ArithmExpr
}
func (u *UnaryArithm) Pos() Pos {
if u.Post {
return u.X.Pos()
}
return u.OpPos
}
func (u *UnaryArithm) End() Pos {
if u.Post {
return posAddCol(u.OpPos, 2)
}
return u.X.End()
}
// ParenArithm represents an arithmetic expression within parentheses.
type ParenArithm struct {
Lparen, Rparen Pos
X ArithmExpr
}
func (p *ParenArithm) Pos() Pos { return p.Lparen }
func (p *ParenArithm) End() Pos { return posAddCol(p.Rparen, 1) }
// CaseClause represents a case (switch) clause.
type CaseClause struct {
Case, Esac Pos
Word *Word
Items []*CaseItem
Last []Comment
}
func (c *CaseClause) Pos() Pos { return c.Case }
func (c *CaseClause) End() Pos { return posAddCol(c.Esac, 4) }
// CaseItem represents a pattern list (case) within a CaseClause.
type CaseItem struct {
Op CaseOperator
OpPos Pos // unset if it was finished by "esac"
Comments []Comment
Patterns []*Word
StmtList
}
func (c *CaseItem) Pos() Pos { return c.Patterns[0].Pos() }
func (c *CaseItem) End() Pos {
if c.OpPos.IsValid() {
return posAddCol(c.OpPos, len(c.Op.String()))
}
return c.StmtList.end()
}
// TestClause represents a Bash extended test clause.
//
// This node will only appear in LangBash and LangMirBSDKorn.
type TestClause struct {
Left, Right Pos
X TestExpr
}
func (t *TestClause) Pos() Pos { return t.Left }
func (t *TestClause) End() Pos { return posAddCol(t.Right, 2) }
// TestExpr represents all nodes that form test expressions.
//
// These are *BinaryTest, *UnaryTest, *ParenTest, and *Word.
type TestExpr interface {
Node
testExprNode()
}
func (*BinaryTest) testExprNode() {}
func (*UnaryTest) testExprNode() {}
func (*ParenTest) testExprNode() {}
func (*Word) testExprNode() {}
// BinaryTest represents a binary test expression.
type BinaryTest struct {
OpPos Pos
Op BinTestOperator
X, Y TestExpr
}
func (b *BinaryTest) Pos() Pos { return b.X.Pos() }
func (b *BinaryTest) End() Pos { return b.Y.End() }
// UnaryTest represents a unary test expression. The unary opearator may come
// before or after the sub-expression.
type UnaryTest struct {
OpPos Pos
Op UnTestOperator
X TestExpr
}
func (u *UnaryTest) Pos() Pos { return u.OpPos }
func (u *UnaryTest) End() Pos { return u.X.End() }
// ParenTest represents a test expression within parentheses.
type ParenTest struct {
Lparen, Rparen Pos
X TestExpr
}
func (p *ParenTest) Pos() Pos { return p.Lparen }
func (p *ParenTest) End() Pos { return posAddCol(p.Rparen, 1) }
// DeclClause represents a Bash declare clause.
//
// This node will only appear with LangBash.
type DeclClause struct {
// Variant is one of "declare", "local", "export", "readonly",
// "typeset", or "nameref".
Variant *Lit
Opts []*Word
Assigns []*Assign
}
func (d *DeclClause) Pos() Pos { return d.Variant.Pos() }
func (d *DeclClause) End() Pos {
if len(d.Assigns) > 0 {
return d.Assigns[len(d.Assigns)-1].End()
}
if len(d.Opts) > 0 {
return wordLastEnd(d.Opts)
}
return d.Variant.End()
}
// ArrayExpr represents a Bash array expression.
//
// This node will only appear with LangBash.
type ArrayExpr struct {
Lparen, Rparen Pos
Elems []*ArrayElem
Last []Comment
}
func (a *ArrayExpr) Pos() Pos { return a.Lparen }
func (a *ArrayExpr) End() Pos { return posAddCol(a.Rparen, 1) }
// ArrayElem represents a Bash array element.
//
// Index can be nil; for example, declare -a x=(value).
// Value can be nil; for example, declare -A x=([index]=).
// Finally, neither can be nil; for example, declare -A x=([index]=value)
type ArrayElem struct {
Index ArithmExpr
Value *Word
Comments []Comment
}
func (a *ArrayElem) Pos() Pos {
if a.Index != nil {
return a.Index.Pos()
}
return a.Value.Pos()
}
func (a *ArrayElem) End() Pos {
if a.Value != nil {
return a.Value.End()
}
return posAddCol(a.Index.Pos(), 1)
}
// ExtGlob represents a Bash extended globbing expression. Note that these are
// parsed independently of whether shopt has been called or not.
//
// This node will only appear in LangBash and LangMirBSDKorn.
type ExtGlob struct {
OpPos Pos
Op GlobOperator
Pattern *Lit
}
func (e *ExtGlob) Pos() Pos { return e.OpPos }
func (e *ExtGlob) End() Pos { return posAddCol(e.Pattern.End(), 1) }
// ProcSubst represents a Bash process substitution.
//
// This node will only appear with LangBash.
type ProcSubst struct {
OpPos, Rparen Pos
Op ProcOperator
StmtList
}
func (s *ProcSubst) Pos() Pos { return s.OpPos }
func (s *ProcSubst) End() Pos { return posAddCol(s.Rparen, 1) }
// TimeClause represents a Bash time clause. PosixFormat corresponds to the -p
// flag.
//
// This node will only appear in LangBash and LangMirBSDKorn.
type TimeClause struct {
Time Pos
PosixFormat bool
Stmt *Stmt
}
func (c *TimeClause) Pos() Pos { return c.Time }
func (c *TimeClause) End() Pos {
if c.Stmt == nil {
return posAddCol(c.Time, 4)
}
return c.Stmt.End()
}
// CoprocClause represents a Bash coproc clause.
//
// This node will only appear with LangBash.
type CoprocClause struct {
Coproc Pos
Name *Lit
Stmt *Stmt
}
func (c *CoprocClause) Pos() Pos { return c.Coproc }
func (c *CoprocClause) End() Pos { return c.Stmt.End() }
// LetClause represents a Bash let clause.
//
// This node will only appear in LangBash and LangMirBSDKorn.
type LetClause struct {
Let Pos
Exprs []ArithmExpr
}
func (l *LetClause) Pos() Pos { return l.Let }
func (l *LetClause) End() Pos { return l.Exprs[len(l.Exprs)-1].End() }
func wordLastEnd(ws []*Word) Pos {
if len(ws) == 0 {
return Pos{}
}
return ws[len(ws)-1].End()
}

2447
vendor/mvdan.cc/sh/syntax/parser.go vendored Normal file

File diff suppressed because it is too large Load Diff

173
vendor/mvdan.cc/sh/syntax/pattern.go vendored Normal file
View File

@@ -0,0 +1,173 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import (
"bytes"
"fmt"
"regexp"
"strings"
)
func charClass(s string) (string, error) {
if strings.HasPrefix(s, "[[.") || strings.HasPrefix(s, "[[=") {
return "", fmt.Errorf("collating features not available")
}
if !strings.HasPrefix(s, "[[:") {
return "", nil
}
name := s[3:]
end := strings.Index(name, ":]]")
if end < 0 {
return "", fmt.Errorf("[[: was not matched with a closing :]]")
}
name = name[:end]
switch name {
case "alnum", "alpha", "ascii", "blank", "cntrl", "digit", "graph",
"lower", "print", "punct", "space", "upper", "word", "xdigit":
default:
return "", fmt.Errorf("invalid character class: %q", name)
}
return s[:len(name)+6], nil
}
// TranslatePattern turns a shell wildcard pattern into a regular expression
// that can be used with regexp.Compile. It will return an error if the input
// pattern was incorrect. Otherwise, the returned expression can be passed to
// regexp.MustCompile.
//
// For example, TranslatePattern(`foo*bar?`, true) returns `foo.*bar.`.
//
// Note that this function (and QuotePattern) should not be directly used with
// file paths if Windows is supported, as the path separator on that platform is
// the same character as the escaping character for shell patterns.
func TranslatePattern(pattern string, greedy bool) (string, error) {
any := false
loop:
for _, r := range pattern {
switch r {
// including those that need escaping since they are
// special chars in regexes
case '*', '?', '[', '\\', '.', '+', '(', ')', '|',
']', '{', '}', '^', '$':
any = true
break loop
}
}
if !any { // short-cut without a string copy
return pattern, nil
}
var buf bytes.Buffer
for i := 0; i < len(pattern); i++ {
switch c := pattern[i]; c {
case '*':
buf.WriteString(".*")
if !greedy {
buf.WriteByte('?')
}
case '?':
buf.WriteString(".")
case '\\':
if i++; i >= len(pattern) {
return "", fmt.Errorf(`\ at end of pattern`)
}
buf.WriteString(regexp.QuoteMeta(string(pattern[i])))
case '[':
name, err := charClass(pattern[i:])
if err != nil {
return "", err
}
if name != "" {
buf.WriteString(name)
i += len(name) - 1
break
}
buf.WriteByte(c)
if i++; i >= len(pattern) {
return "", fmt.Errorf("[ was not matched with a closing ]")
}
switch c = pattern[i]; c {
case '!', '^':
buf.WriteByte('^')
i++
c = pattern[i]
}
buf.WriteByte(c)
last := c
rangeStart := byte(0)
for {
if i++; i >= len(pattern) {
return "", fmt.Errorf("[ was not matched with a closing ]")
}
last, c = c, pattern[i]
buf.WriteByte(c)
if c == ']' {
break
}
if rangeStart != 0 && rangeStart > c {
return "", fmt.Errorf("invalid range: %c-%c", rangeStart, c)
}
if c == '-' {
rangeStart = last
} else {
rangeStart = 0
}
}
default:
buf.WriteString(regexp.QuoteMeta(string(c)))
}
}
return buf.String(), nil
}
// HasPattern returns whether a string contains any unescaped wildcard
// characters: '*', '?', or '['. When the function returns false, the given
// pattern can only match at most one string.
//
// For example, HasPattern(`foo\*bar`) returns false, but HasPattern(`foo*bar`)
// returns true.
//
// This can be useful to avoid extra work, like TranslatePattern. Note that this
// function cannot be used to avoid QuotePattern, as backslashes are quoted by
// that function but ignored here.
func HasPattern(pattern string) bool {
for i := 0; i < len(pattern); i++ {
switch pattern[i] {
case '\\':
i++
case '*', '?', '[':
return true
}
}
return false
}
// QuotePattern returns a string that quotes all special characters in the given
// wildcard pattern. The returned string is a pattern that matches the literal
// string.
//
// For example, QuotePattern(`foo*bar?`) returns `foo\*bar\?`.
func QuotePattern(pattern string) string {
any := false
loop:
for _, r := range pattern {
switch r {
case '*', '?', '[', '\\':
any = true
break loop
}
}
if !any { // short-cut without a string copy
return pattern
}
var buf bytes.Buffer
for _, r := range pattern {
switch r {
case '*', '?', '[', '\\':
buf.WriteByte('\\')
}
buf.WriteRune(r)
}
return buf.String()
}

1314
vendor/mvdan.cc/sh/syntax/printer.go vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
// Code generated by "stringer -type=quoteState"; DO NOT EDIT.
package syntax
import "strconv"
const _quoteState_name = "noStatesubCmdsubCmdBckquodblQuoteshdocWordhdocBodyhdocBodyTabsarithmExprarithmExprLetarithmExprCmdarithmExprBracktestRegexpswitchCaseparamExpNameparamExpSliceparamExpReplparamExpExparrayElems"
var _quoteState_map = map[quoteState]string{
1: _quoteState_name[0:7],
2: _quoteState_name[7:13],
4: _quoteState_name[13:25],
8: _quoteState_name[25:34],
16: _quoteState_name[34:42],
32: _quoteState_name[42:50],
64: _quoteState_name[50:62],
128: _quoteState_name[62:72],
256: _quoteState_name[72:85],
512: _quoteState_name[85:98],
1024: _quoteState_name[98:113],
2048: _quoteState_name[113:123],
4096: _quoteState_name[123:133],
8192: _quoteState_name[133:145],
16384: _quoteState_name[145:158],
32768: _quoteState_name[158:170],
65536: _quoteState_name[170:181],
131072: _quoteState_name[181:191],
}
func (i quoteState) String() string {
if str, ok := _quoteState_map[i]; ok {
return str
}
return "quoteState(" + strconv.FormatInt(int64(i), 10) + ")"
}

245
vendor/mvdan.cc/sh/syntax/simplify.go vendored Normal file
View File

@@ -0,0 +1,245 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import "bytes"
// Simplify simplifies a given program and returns whether any changes
// were made.
//
// The changes currently applied are:
//
// Remove clearly useless parentheses $(( (expr) ))
// Remove dollars from vars in exprs (($var))
// Remove duplicate subshells $( (stmts) )
// Remove redundant quotes [[ "$var" == str ]]
// Merge negations with unary operators [[ ! -n $var ]]
// Use single quotes to shorten literals "\$foo"
func Simplify(n Node) bool {
s := simplifier{}
Walk(n, s.visit)
return s.modified
}
type simplifier struct {
modified bool
}
func (s *simplifier) visit(node Node) bool {
switch x := node.(type) {
case *Assign:
x.Index = s.removeParensArithm(x.Index)
// Don't inline params, as x[i] and x[$i] mean
// different things when x is an associative
// array; the first means "i", the second "$i".
case *ParamExp:
x.Index = s.removeParensArithm(x.Index)
// don't inline params - same as above.
if x.Slice == nil {
break
}
x.Slice.Offset = s.removeParensArithm(x.Slice.Offset)
x.Slice.Offset = s.inlineSimpleParams(x.Slice.Offset)
x.Slice.Length = s.removeParensArithm(x.Slice.Length)
x.Slice.Length = s.inlineSimpleParams(x.Slice.Length)
case *ArithmExp:
x.X = s.removeParensArithm(x.X)
x.X = s.inlineSimpleParams(x.X)
case *ArithmCmd:
x.X = s.removeParensArithm(x.X)
x.X = s.inlineSimpleParams(x.X)
case *ParenArithm:
x.X = s.removeParensArithm(x.X)
x.X = s.inlineSimpleParams(x.X)
case *BinaryArithm:
x.X = s.inlineSimpleParams(x.X)
x.Y = s.inlineSimpleParams(x.Y)
case *CmdSubst:
x.Stmts = s.inlineSubshell(x.Stmts)
case *Subshell:
x.Stmts = s.inlineSubshell(x.Stmts)
case *Word:
x.Parts = s.simplifyWord(x.Parts)
case *TestClause:
x.X = s.removeParensTest(x.X)
x.X = s.removeNegateTest(x.X)
case *ParenTest:
x.X = s.removeParensTest(x.X)
x.X = s.removeNegateTest(x.X)
case *BinaryTest:
x.X = s.unquoteParams(x.X)
x.X = s.removeNegateTest(x.X)
switch x.Op {
case TsMatch, TsNoMatch:
// unquoting enables globbing
default:
x.Y = s.unquoteParams(x.Y)
}
x.Y = s.removeNegateTest(x.Y)
case *UnaryTest:
x.X = s.unquoteParams(x.X)
}
return true
}
func (s *simplifier) simplifyWord(wps []WordPart) []WordPart {
parts:
for i, wp := range wps {
dq, _ := wp.(*DblQuoted)
if dq == nil || len(dq.Parts) != 1 {
break
}
lit, _ := dq.Parts[0].(*Lit)
if lit == nil {
break
}
var buf bytes.Buffer
escaped := false
for _, r := range lit.Value {
switch r {
case '\\':
escaped = !escaped
if escaped {
continue
}
case '\'':
continue parts
case '$', '"', '`':
escaped = false
default:
if escaped {
continue parts
}
escaped = false
}
buf.WriteRune(r)
}
newVal := buf.String()
if newVal == lit.Value {
break
}
s.modified = true
wps[i] = &SglQuoted{
Left: dq.Pos(),
Right: dq.End(),
Dollar: dq.Dollar,
Value: newVal,
}
}
return wps
}
func (s *simplifier) removeParensArithm(x ArithmExpr) ArithmExpr {
for {
par, _ := x.(*ParenArithm)
if par == nil {
return x
}
s.modified = true
x = par.X
}
}
func (s *simplifier) inlineSimpleParams(x ArithmExpr) ArithmExpr {
w, _ := x.(*Word)
if w == nil || len(w.Parts) != 1 {
return x
}
pe, _ := w.Parts[0].(*ParamExp)
if pe == nil || !ValidName(pe.Param.Value) {
return x
}
if pe.Excl || pe.Length || pe.Width || pe.Slice != nil ||
pe.Repl != nil || pe.Exp != nil {
return x
}
if pe.Index != nil {
s.modified = true
pe.Short = true
return w
}
s.modified = true
return &Word{Parts: []WordPart{pe.Param}}
}
func (s *simplifier) inlineSubshell(stmts []*Stmt) []*Stmt {
for len(stmts) == 1 {
st := stmts[0]
if st.Negated || st.Background || st.Coprocess ||
len(st.Redirs) > 0 {
break
}
sub, _ := st.Cmd.(*Subshell)
if sub == nil {
break
}
s.modified = true
stmts = sub.Stmts
}
return stmts
}
func (s *simplifier) unquoteParams(x TestExpr) TestExpr {
w, _ := x.(*Word)
if w == nil || len(w.Parts) != 1 {
return x
}
dq, _ := w.Parts[0].(*DblQuoted)
if dq == nil || len(dq.Parts) != 1 {
return x
}
if _, ok := dq.Parts[0].(*ParamExp); !ok {
return x
}
s.modified = true
w.Parts = dq.Parts
return w
}
func (s *simplifier) removeParensTest(x TestExpr) TestExpr {
for {
par, _ := x.(*ParenTest)
if par == nil {
return x
}
s.modified = true
x = par.X
}
}
func (s *simplifier) removeNegateTest(x TestExpr) TestExpr {
u, _ := x.(*UnaryTest)
if u == nil || u.Op != TsNot {
return x
}
switch y := u.X.(type) {
case *UnaryTest:
switch y.Op {
case TsEmpStr:
y.Op = TsNempStr
s.modified = true
return y
case TsNempStr:
y.Op = TsEmpStr
s.modified = true
return y
case TsNot:
s.modified = true
return y.X
}
case *BinaryTest:
switch y.Op {
case TsMatch:
y.Op = TsNoMatch
s.modified = true
return y
case TsNoMatch:
y.Op = TsMatch
s.modified = true
return y
}
}
return x
}

View File

@@ -0,0 +1,16 @@
// Code generated by "stringer -type token -linecomment -trimprefix _"; DO NOT EDIT.
package syntax
import "strconv"
const _token_name = "illegalTokEOFNewlLitLitWordLitRedir'\"`&&&||||&$$'$\"${$[$($(([[[(((}])));;;;&;;&;|!++--***==!=<=>=+=-=*=/=%=&=|=^=<<=>>=>>><<><&>&>|<<<<-<<<&>&>><(>(+:+-:-?:?=:=%%%###^^^,,,@///:-e-f-d-c-b-p-S-L-k-g-u-G-O-N-r-w-x-s-t-z-n-o-v-R=~-nt-ot-ef-eq-ne-le-ge-lt-gt?(*(+(@(!("
var _token_index = [...]uint16{0, 10, 13, 17, 20, 27, 35, 36, 37, 38, 39, 41, 43, 44, 46, 47, 49, 51, 53, 55, 57, 60, 61, 63, 64, 66, 67, 68, 69, 71, 72, 74, 76, 79, 81, 82, 84, 86, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 116, 119, 120, 122, 123, 125, 127, 129, 131, 133, 136, 139, 141, 144, 146, 148, 149, 151, 152, 154, 155, 157, 158, 160, 161, 163, 164, 166, 167, 169, 170, 172, 173, 174, 176, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 256, 258, 260, 262, 264}
func (i token) String() string {
if i >= token(len(_token_index)-1) {
return "token(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _token_name[_token_index[i]:_token_index[i+1]]
}

346
vendor/mvdan.cc/sh/syntax/tokens.go vendored Normal file
View File

@@ -0,0 +1,346 @@
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
//go:generate stringer -type token -linecomment -trimprefix _
type token uint32
// The list of all possible tokens.
const (
illegalTok token = iota
_EOF
_Newl
_Lit
_LitWord
_LitRedir
sglQuote // '
dblQuote // "
bckQuote // `
and // &
andAnd // &&
orOr // ||
or // |
orAnd // |&
dollar // $
dollSglQuote // $'
dollDblQuote // $"
dollBrace // ${
dollBrack // $[
dollParen // $(
dollDblParen // $((
leftBrack // [
dblLeftBrack // [[
leftParen // (
dblLeftParen // ((
rightBrace // }
rightBrack // ]
rightParen // )
dblRightParen // ))
semicolon // ;
dblSemicolon // ;;
semiAnd // ;&
dblSemiAnd // ;;&
semiOr // ;|
exclMark // !
addAdd // ++
subSub // --
star // *
power // **
equal // ==
nequal // !=
lequal // <=
gequal // >=
addAssgn // +=
subAssgn // -=
mulAssgn // *=
quoAssgn // /=
remAssgn // %=
andAssgn // &=
orAssgn // |=
xorAssgn // ^=
shlAssgn // <<=
shrAssgn // >>=
rdrOut // >
appOut // >>
rdrIn // <
rdrInOut // <>
dplIn // <&
dplOut // >&
clbOut // >|
hdoc // <<
dashHdoc // <<-
wordHdoc // <<<
rdrAll // &>
appAll // &>>
cmdIn // <(
cmdOut // >(
plus // +
colPlus // :+
minus // -
colMinus // :-
quest // ?
colQuest // :?
assgn // =
colAssgn // :=
perc // %
dblPerc // %%
hash // #
dblHash // ##
caret // ^
dblCaret // ^^
comma // ,
dblComma // ,,
at // @
slash // /
dblSlash // //
colon // :
tsExists // -e
tsRegFile // -f
tsDirect // -d
tsCharSp // -c
tsBlckSp // -b
tsNmPipe // -p
tsSocket // -S
tsSmbLink // -L
tsSticky // -k
tsGIDSet // -g
tsUIDSet // -u
tsGrpOwn // -G
tsUsrOwn // -O
tsModif // -N
tsRead // -r
tsWrite // -w
tsExec // -x
tsNoEmpty // -s
tsFdTerm // -t
tsEmpStr // -z
tsNempStr // -n
tsOptSet // -o
tsVarSet // -v
tsRefVar // -R
tsReMatch // =~
tsNewer // -nt
tsOlder // -ot
tsDevIno // -ef
tsEql // -eq
tsNeq // -ne
tsLeq // -le
tsGeq // -ge
tsLss // -lt
tsGtr // -gt
globQuest // ?(
globStar // *(
globPlus // +(
globAt // @(
globExcl // !(
)
type RedirOperator token
const (
RdrOut = RedirOperator(rdrOut) + iota
AppOut
RdrIn
RdrInOut
DplIn
DplOut
ClbOut
Hdoc
DashHdoc
WordHdoc
RdrAll
AppAll
)
type ProcOperator token
const (
CmdIn = ProcOperator(cmdIn) + iota
CmdOut
)
type GlobOperator token
const (
GlobQuest = GlobOperator(globQuest) + iota
GlobStar
GlobPlus
GlobAt
GlobExcl
)
type BinCmdOperator token
const (
AndStmt = BinCmdOperator(andAnd) + iota
OrStmt
Pipe
PipeAll
)
type CaseOperator token
const (
Break = CaseOperator(dblSemicolon) + iota
Fallthrough
Resume
ResumeKorn
)
type ParNamesOperator token
const (
NamesPrefix = ParNamesOperator(star)
NamesPrefixWords = ParNamesOperator(at)
)
type ParExpOperator token
const (
SubstPlus = ParExpOperator(plus) + iota
SubstColPlus
SubstMinus
SubstColMinus
SubstQuest
SubstColQuest
SubstAssgn
SubstColAssgn
RemSmallSuffix
RemLargeSuffix
RemSmallPrefix
RemLargePrefix
UpperFirst
UpperAll
LowerFirst
LowerAll
OtherParamOps
)
type UnAritOperator token
const (
Not = UnAritOperator(exclMark) + iota
Inc
Dec
Plus = UnAritOperator(plus)
Minus = UnAritOperator(minus)
)
type BinAritOperator token
const (
Add = BinAritOperator(plus)
Sub = BinAritOperator(minus)
Mul = BinAritOperator(star)
Quo = BinAritOperator(slash)
Rem = BinAritOperator(perc)
Pow = BinAritOperator(power)
Eql = BinAritOperator(equal)
Gtr = BinAritOperator(rdrOut)
Lss = BinAritOperator(rdrIn)
Neq = BinAritOperator(nequal)
Leq = BinAritOperator(lequal)
Geq = BinAritOperator(gequal)
And = BinAritOperator(and)
Or = BinAritOperator(or)
Xor = BinAritOperator(caret)
Shr = BinAritOperator(appOut)
Shl = BinAritOperator(hdoc)
AndArit = BinAritOperator(andAnd)
OrArit = BinAritOperator(orOr)
Comma = BinAritOperator(comma)
Quest = BinAritOperator(quest)
Colon = BinAritOperator(colon)
Assgn = BinAritOperator(assgn)
AddAssgn = BinAritOperator(addAssgn)
SubAssgn = BinAritOperator(subAssgn)
MulAssgn = BinAritOperator(mulAssgn)
QuoAssgn = BinAritOperator(quoAssgn)
RemAssgn = BinAritOperator(remAssgn)
AndAssgn = BinAritOperator(andAssgn)
OrAssgn = BinAritOperator(orAssgn)
XorAssgn = BinAritOperator(xorAssgn)
ShlAssgn = BinAritOperator(shlAssgn)
ShrAssgn = BinAritOperator(shrAssgn)
)
type UnTestOperator token
const (
TsExists = UnTestOperator(tsExists) + iota
TsRegFile
TsDirect
TsCharSp
TsBlckSp
TsNmPipe
TsSocket
TsSmbLink
TsSticky
TsGIDSet
TsUIDSet
TsGrpOwn
TsUsrOwn
TsModif
TsRead
TsWrite
TsExec
TsNoEmpty
TsFdTerm
TsEmpStr
TsNempStr
TsOptSet
TsVarSet
TsRefVar
TsNot = UnTestOperator(exclMark)
)
type BinTestOperator token
const (
TsReMatch = BinTestOperator(tsReMatch) + iota
TsNewer
TsOlder
TsDevIno
TsEql
TsNeq
TsLeq
TsGeq
TsLss
TsGtr
AndTest = BinTestOperator(andAnd)
OrTest = BinTestOperator(orOr)
TsMatch = BinTestOperator(equal)
TsNoMatch = BinTestOperator(nequal)
TsBefore = BinTestOperator(rdrIn)
TsAfter = BinTestOperator(rdrOut)
)
func (o RedirOperator) String() string { return token(o).String() }
func (o ProcOperator) String() string { return token(o).String() }
func (o GlobOperator) String() string { return token(o).String() }
func (o BinCmdOperator) String() string { return token(o).String() }
func (o CaseOperator) String() string { return token(o).String() }
func (o ParNamesOperator) String() string { return token(o).String() }
func (o ParExpOperator) String() string { return token(o).String() }
func (o UnAritOperator) String() string { return token(o).String() }
func (o BinAritOperator) String() string { return token(o).String() }
func (o UnTestOperator) String() string { return token(o).String() }
func (o BinTestOperator) String() string { return token(o).String() }

309
vendor/mvdan.cc/sh/syntax/walk.go vendored Normal file
View File

@@ -0,0 +1,309 @@
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import (
"fmt"
"io"
"reflect"
)
func walkStmts(sl StmtList, f func(Node) bool) {
for _, s := range sl.Stmts {
Walk(s, f)
}
for _, c := range sl.Last {
Walk(&c, f)
}
}
func walkWords(words []*Word, f func(Node) bool) {
for _, w := range words {
Walk(w, f)
}
}
// Walk traverses a syntax tree in depth-first order: It starts by calling
// f(node); node must not be nil. If f returns true, Walk invokes f
// recursively for each of the non-nil children of node, followed by
// f(nil).
func Walk(node Node, f func(Node) bool) {
if !f(node) {
return
}
switch x := node.(type) {
case *File:
walkStmts(x.StmtList, f)
case *Comment:
case *Stmt:
for _, c := range x.Comments {
if !x.End().After(c.Pos()) {
defer Walk(&c, f)
break
}
Walk(&c, f)
}
if x.Cmd != nil {
Walk(x.Cmd, f)
}
for _, r := range x.Redirs {
Walk(r, f)
}
case *Assign:
if x.Name != nil {
Walk(x.Name, f)
}
if x.Value != nil {
Walk(x.Value, f)
}
if x.Index != nil {
Walk(x.Index, f)
}
if x.Array != nil {
Walk(x.Array, f)
}
case *Redirect:
if x.N != nil {
Walk(x.N, f)
}
Walk(x.Word, f)
if x.Hdoc != nil {
Walk(x.Hdoc, f)
}
case *CallExpr:
for _, a := range x.Assigns {
Walk(a, f)
}
walkWords(x.Args, f)
case *Subshell:
walkStmts(x.StmtList, f)
case *Block:
walkStmts(x.StmtList, f)
case *IfClause:
walkStmts(x.Cond, f)
walkStmts(x.Then, f)
walkStmts(x.Else, f)
case *WhileClause:
walkStmts(x.Cond, f)
walkStmts(x.Do, f)
case *ForClause:
Walk(x.Loop, f)
walkStmts(x.Do, f)
case *WordIter:
Walk(x.Name, f)
walkWords(x.Items, f)
case *CStyleLoop:
if x.Init != nil {
Walk(x.Init, f)
}
if x.Cond != nil {
Walk(x.Cond, f)
}
if x.Post != nil {
Walk(x.Post, f)
}
case *BinaryCmd:
Walk(x.X, f)
Walk(x.Y, f)
case *FuncDecl:
Walk(x.Name, f)
Walk(x.Body, f)
case *Word:
for _, wp := range x.Parts {
Walk(wp, f)
}
case *Lit:
case *SglQuoted:
case *DblQuoted:
for _, wp := range x.Parts {
Walk(wp, f)
}
case *CmdSubst:
walkStmts(x.StmtList, f)
case *ParamExp:
Walk(x.Param, f)
if x.Index != nil {
Walk(x.Index, f)
}
if x.Repl != nil {
if x.Repl.Orig != nil {
Walk(x.Repl.Orig, f)
}
if x.Repl.With != nil {
Walk(x.Repl.With, f)
}
}
if x.Exp != nil && x.Exp.Word != nil {
Walk(x.Exp.Word, f)
}
case *ArithmExp:
Walk(x.X, f)
case *ArithmCmd:
Walk(x.X, f)
case *BinaryArithm:
Walk(x.X, f)
Walk(x.Y, f)
case *BinaryTest:
Walk(x.X, f)
Walk(x.Y, f)
case *UnaryArithm:
Walk(x.X, f)
case *UnaryTest:
Walk(x.X, f)
case *ParenArithm:
Walk(x.X, f)
case *ParenTest:
Walk(x.X, f)
case *CaseClause:
Walk(x.Word, f)
for _, ci := range x.Items {
Walk(ci, f)
}
for _, c := range x.Last {
Walk(&c, f)
}
case *CaseItem:
for _, c := range x.Comments {
if c.Pos().After(x.Pos()) {
defer Walk(&c, f)
break
}
Walk(&c, f)
}
walkWords(x.Patterns, f)
walkStmts(x.StmtList, f)
case *TestClause:
Walk(x.X, f)
case *DeclClause:
walkWords(x.Opts, f)
for _, a := range x.Assigns {
Walk(a, f)
}
case *ArrayExpr:
for _, el := range x.Elems {
Walk(el, f)
}
for _, c := range x.Last {
Walk(&c, f)
}
case *ArrayElem:
for _, c := range x.Comments {
if c.Pos().After(x.Pos()) {
defer Walk(&c, f)
break
}
Walk(&c, f)
}
if x.Index != nil {
Walk(x.Index, f)
}
if x.Value != nil {
Walk(x.Value, f)
}
case *ExtGlob:
Walk(x.Pattern, f)
case *ProcSubst:
walkStmts(x.StmtList, f)
case *TimeClause:
if x.Stmt != nil {
Walk(x.Stmt, f)
}
case *CoprocClause:
if x.Name != nil {
Walk(x.Name, f)
}
Walk(x.Stmt, f)
case *LetClause:
for _, expr := range x.Exprs {
Walk(expr, f)
}
default:
panic(fmt.Sprintf("syntax.Walk: unexpected node type %T", x))
}
f(nil)
}
// DebugPrint prints the provided syntax tree, spanning multiple lines and with
// indentation. Can be useful to investigate the content of a syntax tree.
func DebugPrint(w io.Writer, node Node) error {
p := debugPrinter{out: w}
p.print(reflect.ValueOf(node))
return p.err
}
type debugPrinter struct {
out io.Writer
level int
err error
}
func (p *debugPrinter) printf(format string, args ...interface{}) {
_, err := fmt.Fprintf(p.out, format, args...)
if err != nil && p.err == nil {
p.err = err
}
}
func (p *debugPrinter) newline() {
p.printf("\n")
for i := 0; i < p.level; i++ {
p.printf(". ")
}
}
func (p *debugPrinter) print(x reflect.Value) {
switch x.Kind() {
case reflect.Interface:
if x.IsNil() {
p.printf("nil")
return
}
p.print(x.Elem())
case reflect.Ptr:
if x.IsNil() {
p.printf("nil")
return
}
p.printf("*")
p.print(x.Elem())
case reflect.Slice:
p.printf("%s (len = %d) {", x.Type(), x.Len())
if x.Len() > 0 {
p.level++
p.newline()
for i := 0; i < x.Len(); i++ {
p.printf("%d: ", i)
p.print(x.Index(i))
if i == x.Len()-1 {
p.level--
}
p.newline()
}
}
p.printf("}")
case reflect.Struct:
if v, ok := x.Interface().(Pos); ok {
p.printf("%v:%v", v.Line(), v.Col())
return
}
t := x.Type()
p.printf("%s {", t)
p.level++
p.newline()
for i := 0; i < t.NumField(); i++ {
p.printf("%s: ", t.Field(i).Name)
p.print(x.Field(i))
if i == x.NumField()-1 {
p.level--
}
p.newline()
}
p.printf("}")
default:
p.printf("%#v", x.Interface())
}
}