mirror of
https://github.com/mudler/luet.git
synced 2025-09-21 03:00:57 +00:00
Update vendor/
This commit is contained in:
committed by
Ettore Di Giacinto
parent
8ca6051a04
commit
9d0dc601b7
27
vendor/mvdan.cc/sh/LICENSE
vendored
Normal file
27
vendor/mvdan.cc/sh/LICENSE
vendored
Normal 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
202
vendor/mvdan.cc/sh/expand/arith.go
vendored
Normal 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
24
vendor/mvdan.cc/sh/expand/braces.go
vendored
Normal 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
5
vendor/mvdan.cc/sh/expand/doc.go
vendored
Normal 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
195
vendor/mvdan.cc/sh/expand/environ.go
vendored
Normal 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
799
vendor/mvdan.cc/sh/expand/expand.go
vendored
Normal 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
348
vendor/mvdan.cc/sh/expand/param.go
vendored
Normal 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
676
vendor/mvdan.cc/sh/interp/builtin.go
vendored
Normal 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
7
vendor/mvdan.cc/sh/interp/doc.go
vendored
Normal 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
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
164
vendor/mvdan.cc/sh/interp/module.go
vendored
Normal 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
49
vendor/mvdan.cc/sh/interp/perm_unix.go
vendored
Normal 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
|
||||
}
|
11
vendor/mvdan.cc/sh/interp/perm_windows.go
vendored
Normal file
11
vendor/mvdan.cc/sh/interp/perm_windows.go
vendored
Normal 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
184
vendor/mvdan.cc/sh/interp/test.go
vendored
Normal 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))
|
||||
}
|
||||
}
|
196
vendor/mvdan.cc/sh/interp/test_classic.go
vendored
Normal file
196
vendor/mvdan.cc/sh/interp/test_classic.go
vendored
Normal 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
321
vendor/mvdan.cc/sh/interp/vars.go
vendored
Normal 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
14
vendor/mvdan.cc/sh/shell/doc.go
vendored
Normal 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
63
vendor/mvdan.cc/sh/shell/expand.go
vendored
Normal 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
56
vendor/mvdan.cc/sh/shell/source.go
vendored
Normal 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
37
vendor/mvdan.cc/sh/syntax/canonical.sh
vendored
Normal 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
6
vendor/mvdan.cc/sh/syntax/doc.go
vendored
Normal 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
282
vendor/mvdan.cc/sh/syntax/expand.go
vendored
Normal 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
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
885
vendor/mvdan.cc/sh/syntax/nodes.go
vendored
Normal 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
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
173
vendor/mvdan.cc/sh/syntax/pattern.go
vendored
Normal 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
1314
vendor/mvdan.cc/sh/syntax/printer.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
35
vendor/mvdan.cc/sh/syntax/quotestate_string.go
vendored
Normal file
35
vendor/mvdan.cc/sh/syntax/quotestate_string.go
vendored
Normal 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
245
vendor/mvdan.cc/sh/syntax/simplify.go
vendored
Normal 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
|
||||
}
|
16
vendor/mvdan.cc/sh/syntax/token_string.go
vendored
Normal file
16
vendor/mvdan.cc/sh/syntax/token_string.go
vendored
Normal 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
346
vendor/mvdan.cc/sh/syntax/tokens.go
vendored
Normal 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
309
vendor/mvdan.cc/sh/syntax/walk.go
vendored
Normal 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())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user