1
0
mirror of https://github.com/go-task/task.git synced 2025-01-12 04:34:11 +02:00

Vendor github.com/mvdan/sh

This commit is contained in:
Andrey Nering 2017-04-24 09:47:10 -03:00
parent 6bc27baa96
commit 25134279f4
17 changed files with 6400 additions and 0 deletions

27
vendor/github.com/mvdan/sh/LICENSE generated vendored Normal file
View File

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

171
vendor/github.com/mvdan/sh/interp/arith.go generated vendored Normal file
View File

@ -0,0 +1,171 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package interp
import (
"strconv"
"github.com/mvdan/sh/syntax"
)
func (r *Runner) arithm(expr syntax.ArithmExpr) int {
switch x := expr.(type) {
case *syntax.Word:
str := r.loneWord(x)
// recursively fetch vars
for {
val := r.getVar(str)
if val == "" {
break
}
str = val
}
// default to 0
return atoi(str)
case *syntax.ParenArithm:
return r.arithm(x.X)
case *syntax.UnaryArithm:
switch x.Op {
case syntax.Inc, syntax.Dec:
name := x.X.(*syntax.Word).Parts[0].(*syntax.Lit).Value
old := atoi(r.getVar(name))
val := old
if x.Op == syntax.Inc {
val++
} else {
val--
}
r.setVar(name, strconv.Itoa(val))
if x.Post {
return old
}
return val
}
val := r.arithm(x.X)
switch x.Op {
case syntax.Not:
return oneIf(val == 0)
case syntax.Plus:
return val
default: // syntax.Minus
return -val
}
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 r.assgnArit(x)
case syntax.Quest: // Colon can't happen here
cond := r.arithm(x.X)
b2 := x.Y.(*syntax.BinaryArithm) // must have Op==Colon
if cond == 1 {
return r.arithm(b2.X)
}
return r.arithm(b2.Y)
}
return binArit(x.Op, r.arithm(x.X), r.arithm(x.Y))
default:
r.errf("unexpected arithm expr: %T", x)
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) assgnArit(b *syntax.BinaryArithm) int {
name := b.X.(*syntax.Word).Parts[0].(*syntax.Lit).Value
val := atoi(r.getVar(name))
arg := r.arithm(b.Y)
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)
default: // syntax.ShrAssgn
val >>= uint(arg)
}
r.setVar(name, strconv.Itoa(val))
return val
}
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
}
}

179
vendor/github.com/mvdan/sh/interp/builtin.go generated vendored Normal file
View File

@ -0,0 +1,179 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package interp
import (
"os"
"path/filepath"
"strconv"
"github.com/mvdan/sh/syntax"
)
func (r *Runner) builtin(pos syntax.Pos, name string, args []string) bool {
exit := 0
switch name {
case "true", ":":
case "false":
exit = 1
case "exit":
switch len(args) {
case 0:
r.lastExit()
case 1:
if n, err := strconv.Atoi(args[0]); err != nil {
r.runErr(pos, "invalid exit code: %q", args[0])
} else {
exit = n
r.err = ExitCode(n)
}
default:
r.runErr(pos, "exit cannot take multiple arguments")
}
case "set":
r.args = args
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")
exit = 2
}
if len(r.args) < n {
n = len(r.args)
}
r.args = r.args[n:]
case "unset":
for _, arg := range args {
r.delVar(arg)
}
case "echo":
newline := true
opts:
for len(args) > 0 {
switch args[0] {
case "-n":
newline = false
case "-e", "-E":
// TODO: what should be our default?
// exactly what is the difference in
// what we write?
default:
break opts
}
args = args[1:]
}
for i, arg := range args {
if i > 0 {
r.outf(" ")
}
r.outf("%s", arg)
}
if newline {
r.outf("\n")
}
case "printf":
if len(args) == 0 {
r.errf("usage: printf format [arguments]\n")
exit = 2
break
}
var a []interface{}
for _, arg := range args[1:] {
a = append(a, arg)
}
r.outf(args[0], a...)
case "break":
if !r.inLoop {
r.errf("break is only useful in a loop")
break
}
switch len(args) {
case 0:
r.breakEnclosing = 1
case 1:
if n, err := strconv.Atoi(args[0]); err == nil {
r.breakEnclosing = n
break
}
fallthrough
default:
r.errf("usage: break [n]\n")
exit = 2
}
case "continue":
if !r.inLoop {
r.errf("continue is only useful in a loop")
break
}
switch len(args) {
case 0:
r.contnEnclosing = 1
case 1:
if n, err := strconv.Atoi(args[0]); err == nil {
r.contnEnclosing = n
break
}
fallthrough
default:
r.errf("usage: continue [n]\n")
exit = 2
}
case "pwd":
r.outf("%s\n", r.getVar("PWD"))
case "cd":
if len(args) > 1 {
r.errf("usage: cd [dir]\n")
exit = 2
break
}
var dir string
if len(args) == 0 {
dir = r.getVar("HOME")
} else {
dir = args[0]
}
if !filepath.IsAbs(dir) {
dir = filepath.Join(r.Dir, dir)
}
_, err := os.Stat(dir)
if err != nil {
exit = 1
break
}
r.Dir = dir
case "wait":
if len(args) > 0 {
r.errf("wait with args not handled yet")
break
}
r.bgShells.Wait()
case "builtin":
if len(args) < 1 {
break
}
// TODO: pos
if !r.builtin(0, args[0], args[1:]) {
exit = 1
}
case "trap", "type", "source", "command", "pushd", "popd",
"umask", "alias", "unalias", "fg", "bg", "getopts":
r.errf("unhandled builtin: %s", name)
// TODO(mvdan): we rely on the binary versions of these, we
// should eventually implement them as builtins like Bash for
// portability
// case "[", "test":
default:
return false
}
r.exit = exit
return true
}

9
vendor/github.com/mvdan/sh/interp/doc.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
// 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.
//
// This package is a work in progress and EXPERIMENTAL; its API is not
// subject to the 1.x backwards compatibility guarantee.
package interp

653
vendor/github.com/mvdan/sh/interp/interp.go generated vendored Normal file
View File

@ -0,0 +1,653 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package interp
import (
"bytes"
"context"
"fmt"
"io"
"os"
"os/exec"
"os/user"
"path"
"strconv"
"strings"
"sync"
"syscall"
"github.com/mvdan/sh/syntax"
)
// A Runner interprets shell programs. It cannot be reused once a
// program has been interpreted.
//
// Note that writes to Stdout and Stderr may not be sequential. If
// you plan on using an io.Writer implementation that isn't safe for
// concurrent use, consider a workaround like hiding writes behind a
// mutex.
type Runner struct {
// TODO: syntax.Node instead of *syntax.File?
File *syntax.File
// Env specifies the environment of the interpreter.
// If Env is nil, Run uses the current process's environment.
Env []string
// envMap is just Env as a map, to simplify and speed up its use
envMap map[string]string
// Dir specifies the working directory of the command. If Dir is
// the empty string, Run runs the command in the calling
// process's current directory.
Dir string
// Separate maps, note that bash allows a name to be both a var
// and a func simultaneously
vars map[string]varValue
funcs map[string]*syntax.Stmt
// like vars, but local to a cmd i.e. "foo=bar prog args..."
cmdVars map[string]varValue
// Current arguments, if executing a function
args []string
// >0 to break or continue out of N enclosing loops
breakEnclosing, contnEnclosing int
inLoop bool
err error // current fatal error
exit int // current (last) exit code
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
bgShells sync.WaitGroup
// Context can be used to cancel the interpreter before it finishes
Context context.Context
}
// varValue can hold a string, an indexed array ([]string) or an
// associative array (map[string]string)
// TODO: implement associative arrays
type varValue interface{}
func varStr(v varValue) string {
switch x := v.(type) {
case string:
return x
case []string:
if len(x) > 0 {
return x[0]
}
}
return ""
}
func (r *Runner) varInd(v varValue, e syntax.ArithmExpr) string {
switch x := v.(type) {
case string:
i := r.arithm(e)
if i == 0 {
return x
}
case []string:
// TODO: @ between double quotes
if w, ok := e.(*syntax.Word); ok {
if lit, ok := w.Parts[0].(*syntax.Lit); ok {
switch lit.Value {
case "@", "*":
return strings.Join(x, " ")
}
}
}
i := r.arithm(e)
if len(x) > 0 {
return x[i]
}
}
return ""
}
type ExitCode uint8
func (e ExitCode) Error() string { return fmt.Sprintf("exit status %d", e) }
type RunError struct {
syntax.Position
Text string
}
func (e RunError) Error() string {
return fmt.Sprintf("%s: %s", e.Position.String(), e.Text)
}
func (r *Runner) runErr(pos syntax.Pos, format string, a ...interface{}) {
if r.err == nil {
r.err = RunError{
Position: r.File.Position(pos),
Text: fmt.Sprintf(format, a...),
}
}
}
func (r *Runner) lastExit() {
if r.err == nil {
r.err = ExitCode(r.exit)
}
}
func (r *Runner) setVar(name string, val varValue) {
if r.vars == nil {
r.vars = make(map[string]varValue, 4)
}
r.vars[name] = val
}
func (r *Runner) lookupVar(name string) (varValue, bool) {
switch name {
case "PWD":
return r.Dir, true
case "HOME":
u, _ := user.Current()
return u.HomeDir, true
}
if val, e := r.cmdVars[name]; e {
return val, true
}
if val, e := r.vars[name]; e {
return val, true
}
str, e := r.envMap[name]
return str, e
}
func (r *Runner) getVar(name string) string {
val, _ := r.lookupVar(name)
return varStr(val)
}
func (r *Runner) delVar(name string) {
delete(r.vars, name)
delete(r.envMap, name)
}
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
}
// Run starts the interpreter and returns any error.
func (r *Runner) Run() error {
if r.Context == nil {
r.Context = context.Background()
}
if r.Env == nil {
r.Env = os.Environ()
}
r.envMap = make(map[string]string, len(r.Env))
for _, kv := range r.Env {
i := strings.IndexByte(kv, '=')
if i < 0 {
return fmt.Errorf("env not in the form key=value: %q", kv)
}
name, val := kv[:i], kv[i+1:]
r.envMap[name] = val
}
if r.Dir == "" {
dir, err := os.Getwd()
if err != nil {
return fmt.Errorf("could not get current dir: %v", err)
}
r.Dir = dir
}
r.stmts(r.File.Stmts)
r.lastExit()
if r.err == ExitCode(0) {
r.err = nil
}
return r.err
}
func (r *Runner) outf(format string, a ...interface{}) {
fmt.Fprintf(r.Stdout, format, a...)
}
func (r *Runner) errf(format string, a ...interface{}) {
fmt.Fprintf(r.Stderr, format, a...)
}
func (r *Runner) fields(words []*syntax.Word) []string {
fields := make([]string, 0, len(words))
for _, word := range words {
fields = append(fields, r.wordParts(word.Parts, false)...)
}
return fields
}
func (r *Runner) loneWord(word *syntax.Word) string {
return strings.Join(r.wordParts(word.Parts, false), "")
}
func (r *Runner) stop() bool {
if r.err != nil {
return true
}
if err := r.Context.Err(); err != nil {
r.err = err
return true
}
return false
}
func (r *Runner) stmt(st *syntax.Stmt) {
if r.stop() {
return
}
if st.Background {
r.bgShells.Add(1)
r2 := *r
r2.bgShells = sync.WaitGroup{}
go func() {
r2.stmtSync(st)
r.bgShells.Done()
}()
} else {
r.stmtSync(st)
}
}
func (r *Runner) assignValue(word *syntax.Word) varValue {
if word == nil {
return nil
}
ae, ok := word.Parts[0].(*syntax.ArrayExpr)
if !ok {
return r.loneWord(word)
}
strs := make([]string, len(ae.List))
for i, w := range ae.List {
strs[i] = r.loneWord(w)
}
return strs
}
func (r *Runner) stmtSync(st *syntax.Stmt) {
oldVars := r.cmdVars
for _, as := range st.Assigns {
name := as.Name.Value
val := r.assignValue(as.Value)
if st.Cmd == nil {
r.setVar(name, val)
continue
}
if r.cmdVars == nil {
r.cmdVars = make(map[string]varValue, len(st.Assigns))
}
r.cmdVars[name] = val
}
oldIn, oldOut, oldErr := r.Stdin, r.Stdout, r.Stderr
for _, rd := range st.Redirs {
cls, err := r.redir(rd)
if err != nil {
r.exit = 1
return
}
if cls != nil {
defer cls.Close()
}
}
if st.Cmd == nil {
r.exit = 0
} else {
r.cmd(st.Cmd)
}
if st.Negated {
r.exit = oneIf(r.exit == 0)
}
r.cmdVars = oldVars
r.Stdin, r.Stdout, r.Stderr = oldIn, oldOut, oldErr
}
func oneIf(b bool) int {
if b {
return 1
}
return 0
}
func (r *Runner) cmd(cm syntax.Command) {
if r.stop() {
return
}
switch x := cm.(type) {
case *syntax.Block:
r.stmts(x.Stmts)
case *syntax.Subshell:
r2 := *r
r2.stmts(x.Stmts)
r.exit = r2.exit
case *syntax.CallExpr:
fields := r.fields(x.Args)
r.call(x.Args[0].Pos(), fields[0], fields[1:])
case *syntax.BinaryCmd:
switch x.Op {
case syntax.AndStmt:
r.stmt(x.X)
if r.exit == 0 {
r.stmt(x.Y)
}
case syntax.OrStmt:
r.stmt(x.X)
if r.exit != 0 {
r.stmt(x.Y)
}
case syntax.Pipe, syntax.PipeAll:
pr, pw := io.Pipe()
r2 := *r
r2.Stdin = r.Stdin
r2.Stdout = pw
if x.Op == syntax.PipeAll {
r2.Stderr = pw
} else {
r2.Stderr = r.Stderr
}
r.Stdin = pr
go func() {
r2.stmt(x.X)
pw.Close()
}()
r.stmt(x.Y)
pr.Close()
}
case *syntax.IfClause:
r.stmts(x.CondStmts)
if r.exit == 0 {
r.stmts(x.ThenStmts)
return
}
for _, el := range x.Elifs {
r.stmts(el.CondStmts)
if r.exit == 0 {
r.stmts(el.ThenStmts)
return
}
}
r.stmts(x.ElseStmts)
if len(x.Elifs)+len(x.ElseStmts) == 0 {
r.exit = 0
}
case *syntax.WhileClause:
for r.err == nil {
r.stmts(x.CondStmts)
if r.exit != 0 {
r.exit = 0
break
}
if r.loopStmtsBroken(x.DoStmts) {
break
}
}
case *syntax.UntilClause:
for r.err == nil {
r.stmts(x.CondStmts)
if r.exit == 0 {
break
}
r.exit = 0
if r.loopStmtsBroken(x.DoStmts) {
break
}
}
case *syntax.ForClause:
switch y := x.Loop.(type) {
case *syntax.WordIter:
name := y.Name.Value
for _, field := range r.fields(y.List) {
r.setVar(name, field)
if r.loopStmtsBroken(x.DoStmts) {
break
}
}
case *syntax.CStyleLoop:
r.arithm(y.Init)
for r.arithm(y.Cond) != 0 {
if r.loopStmtsBroken(x.DoStmts) {
break
}
r.arithm(y.Post)
}
}
case *syntax.FuncDecl:
r.setFunc(x.Name.Value, x.Body)
case *syntax.ArithmCmd:
if r.arithm(x.X) == 0 {
r.exit = 1
}
case *syntax.LetClause:
var val int
for _, expr := range x.Exprs {
val = r.arithm(expr)
}
if val == 0 {
r.exit = 1
}
case *syntax.CaseClause:
str := r.loneWord(x.Word)
for _, pl := range x.List {
for _, word := range pl.Patterns {
pat := r.loneWord(word)
// TODO: error?
matched, _ := path.Match(pat, str)
if matched {
r.stmts(pl.Stmts)
return
}
}
}
case *syntax.TestClause:
if r.bashTest(x.X) == "" && r.exit == 0 {
r.exit = 1
}
default:
r.errf("unhandled command node: %T", x)
}
}
func (r *Runner) stmts(stmts []*syntax.Stmt) {
for _, stmt := range stmts {
r.stmt(stmt)
}
}
func (r *Runner) redir(rd *syntax.Redirect) (io.Closer, error) {
if rd.Hdoc != nil {
hdoc := r.loneWord(rd.Hdoc)
r.Stdin = strings.NewReader(hdoc)
return nil, nil
}
orig := &r.Stdout
if rd.N != nil {
switch rd.N.Value {
case "1":
case "2":
orig = &r.Stderr
}
}
arg := r.loneWord(rd.Word)
switch rd.Op {
case syntax.WordHdoc:
r.Stdin = strings.NewReader(arg + "\n")
return nil, nil
case syntax.DplOut:
switch arg {
case "1":
*orig = r.Stdout
case "2":
*orig = r.Stderr
}
return nil, nil
case syntax.DplIn:
r.errf("unhandled redirect op: %v", rd.Op)
}
mode := os.O_RDONLY
switch rd.Op {
case syntax.AppOut, syntax.AppAll:
mode = os.O_RDWR | os.O_CREATE | os.O_APPEND
case syntax.RdrOut, syntax.RdrAll:
mode = os.O_RDWR | os.O_CREATE | os.O_TRUNC
}
f, err := os.OpenFile(arg, mode, 0644)
if err != nil {
// TODO: print to stderr?
return nil, err
}
switch rd.Op {
case syntax.RdrIn:
r.Stdin = f
case syntax.RdrOut, syntax.AppOut:
*orig = f
case syntax.RdrAll, syntax.AppAll:
r.Stdout = f
r.Stderr = f
default:
r.errf("unhandled redirect op: %v", rd.Op)
}
return f, nil
}
func (r *Runner) loopStmtsBroken(stmts []*syntax.Stmt) bool {
r.inLoop = true
defer func() { r.inLoop = false }()
for _, stmt := range stmts {
r.stmt(stmt)
if r.contnEnclosing > 0 {
r.contnEnclosing--
return r.contnEnclosing > 0
}
if r.breakEnclosing > 0 {
r.breakEnclosing--
return true
}
}
return false
}
func (r *Runner) wordParts(wps []syntax.WordPart, quoted bool) []string {
var parts []string
var curBuf bytes.Buffer
flush := func() {
if curBuf.Len() == 0 {
return
}
parts = append(parts, curBuf.String())
curBuf.Reset()
}
splitAdd := func(val string) {
// TODO: use IFS
for i, field := range strings.Fields(val) {
if i > 0 {
flush()
}
curBuf.WriteString(field)
}
}
for _, wp := range wps {
switch x := wp.(type) {
case *syntax.Lit:
curBuf.WriteString(x.Value)
case *syntax.SglQuoted:
curBuf.WriteString(x.Value)
case *syntax.DblQuoted:
// TODO: @ between double quotes but not alone
if len(x.Parts) == 1 {
pe, ok := x.Parts[0].(*syntax.ParamExp)
if ok && pe.Param.Value == "@" {
for i, arg := range r.args {
if i > 0 {
flush()
}
curBuf.WriteString(arg)
}
continue
}
}
for _, str := range r.wordParts(x.Parts, true) {
curBuf.WriteString(str)
}
case *syntax.ParamExp:
val := r.paramExp(x)
if quoted {
curBuf.WriteString(val)
} else {
splitAdd(val)
}
case *syntax.CmdSubst:
r2 := *r
var outBuf bytes.Buffer
r2.Stdout = &outBuf
r2.stmts(x.Stmts)
val := strings.TrimRight(outBuf.String(), "\n")
if quoted {
curBuf.WriteString(val)
} else {
splitAdd(val)
}
case *syntax.ArithmExp:
curBuf.WriteString(strconv.Itoa(r.arithm(x.X)))
default:
r.errf("unhandled word part: %T", x)
}
}
flush()
return parts
}
func (r *Runner) call(pos syntax.Pos, name string, args []string) {
if body := r.funcs[name]; body != nil {
// stack them to support nested func calls
oldArgs := r.args
r.args = args
r.stmt(body)
r.args = oldArgs
return
}
if r.builtin(pos, name, args) {
return
}
cmd := exec.CommandContext(r.Context, name, args...)
cmd.Env = r.Env
for name, val := range r.cmdVars {
cmd.Env = append(cmd.Env, name+"="+varStr(val))
}
cmd.Dir = r.Dir
cmd.Stdin = r.Stdin
cmd.Stdout = r.Stdout
cmd.Stderr = r.Stderr
err := cmd.Run()
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 {
r.exit = status.ExitStatus()
} else {
r.exit = 1
}
case *exec.Error:
// did not start
// TODO: can this be anything other than
// "command not found"?
r.exit = 127
// TODO: print something?
default:
r.exit = 0
}
}

198
vendor/github.com/mvdan/sh/interp/param.go generated vendored Normal file
View File

@ -0,0 +1,198 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package interp
import (
"path"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"github.com/mvdan/sh/syntax"
)
func (r *Runner) paramExp(pe *syntax.ParamExp) string {
name := pe.Param.Value
var val varValue
set := false
switch name {
case "#":
val = strconv.Itoa(len(r.args))
case "*", "@":
val = strings.Join(r.args, " ")
case "?":
val = strconv.Itoa(r.exit)
default:
if n, err := strconv.Atoi(name); err == nil {
if i := n - 1; i < len(r.args) {
val, set = r.args[i], true
}
} else {
val, set = r.lookupVar(name)
}
}
str := varStr(val)
if pe.Ind != nil {
str = r.varInd(val, pe.Ind.Expr)
}
switch {
case pe.Length:
str = strconv.Itoa(utf8.RuneCountInString(str))
case pe.Excl:
val, set = r.lookupVar(str)
str = varStr(val)
}
slicePos := func(expr syntax.ArithmExpr) int {
p := r.arithm(expr)
if p < 0 {
p = len(str) + p
if p < 0 {
p = len(str)
}
} else if p > len(str) {
p = len(str)
}
return p
}
if pe.Slice != nil {
if pe.Slice.Offset != nil {
offset := slicePos(pe.Slice.Offset)
str = str[offset:]
}
if pe.Slice.Length != nil {
length := slicePos(pe.Slice.Length)
str = str[:length]
}
}
if pe.Repl != nil {
orig := r.loneWord(pe.Repl.Orig)
with := r.loneWord(pe.Repl.With)
n := 1
if pe.Repl.All {
n = -1
}
str = strings.Replace(str, orig, with, n)
}
if pe.Exp != nil {
arg := r.loneWord(pe.Exp.Word)
switch pe.Exp.Op {
case syntax.SubstColPlus:
if str == "" {
break
}
fallthrough
case syntax.SubstPlus:
if set {
str = arg
}
case syntax.SubstMinus:
if set {
break
}
fallthrough
case syntax.SubstColMinus:
if str == "" {
str = arg
}
case syntax.SubstQuest:
if set {
break
}
fallthrough
case syntax.SubstColQuest:
if str == "" {
r.errf("%s", arg)
r.exit = 1
r.lastExit()
}
case syntax.SubstAssgn:
if set {
break
}
fallthrough
case syntax.SubstColAssgn:
if str == "" {
r.setVar(name, arg)
str = arg
}
case syntax.RemSmallPrefix:
str = removePattern(str, arg, false, false)
case syntax.RemLargePrefix:
str = removePattern(str, arg, false, true)
case syntax.RemSmallSuffix:
str = removePattern(str, arg, true, false)
case syntax.RemLargeSuffix:
str = removePattern(str, arg, true, true)
case syntax.UpperFirst:
rs := []rune(str)
if len(rs) > 0 {
rs[0] = unicode.ToUpper(rs[0])
}
str = string(rs)
case syntax.UpperAll:
str = strings.ToUpper(str)
case syntax.LowerFirst:
rs := []rune(str)
if len(rs) > 0 {
rs[0] = unicode.ToLower(rs[0])
}
str = string(rs)
case syntax.LowerAll:
str = strings.ToLower(str)
default: // 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":
r.errf("unhandled @%s param expansion", arg)
default:
r.errf("unexpected @%s param expansion", arg)
}
}
}
return str
}
func removePattern(str, pattern string, fromEnd, longest bool) string {
// TODO: really slow to not re-implement path.Match.
last := str
s := str
i := len(str)
if fromEnd {
i = 0
}
for {
if m, _ := path.Match(pattern, s); m {
last = str[i:]
if fromEnd {
last = str[:i]
}
if longest {
return last
}
}
if fromEnd {
if i++; i >= len(str) {
break
}
s = str[i:]
} else {
if i--; i < 1 {
break
}
s = str[:i]
}
}
return last
}

160
vendor/github.com/mvdan/sh/interp/test.go generated vendored Normal file
View File

@ -0,0 +1,160 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package interp
import (
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"github.com/mvdan/sh/syntax"
)
// non-empty string is true, empty string is false
func (r *Runner) bashTest(expr syntax.TestExpr) string {
switch x := expr.(type) {
case *syntax.Word:
return r.loneWord(x)
case *syntax.ParenTest:
return r.bashTest(x.X)
case *syntax.BinaryTest:
if r.binTest(x.Op, r.bashTest(x.X), r.bashTest(x.Y)) {
return "1"
}
return ""
case *syntax.UnaryTest:
if r.unTest(x.Op, r.bashTest(x.X)) {
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:
i1, i2 := stat(x), stat(y)
if i1 == nil || i2 == nil {
return false
}
return i1.ModTime().After(i2.ModTime())
case syntax.TsOlder:
i1, i2 := stat(x), stat(y)
if i1 == nil || i2 == nil {
return false
}
return i1.ModTime().Before(i2.ModTime())
case syntax.TsDevIno:
i1, i2 := stat(x), stat(y)
return os.SameFile(i1, i2)
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.TsEqual:
m, _ := path.Match(y, x)
return m
case syntax.TsNequal:
m, _ := path.Match(y, x)
return !m
case syntax.TsBefore:
return x < y
default: // syntax.TsAfter
return x > y
}
}
func stat(name string) os.FileInfo {
info, _ := os.Stat(name)
return info
}
func statMode(name string, mode os.FileMode) bool {
info := stat(name)
return info != nil && info.Mode()&mode != 0
}
func (r *Runner) unTest(op syntax.UnTestOperator, x string) bool {
switch op {
case syntax.TsExists:
return stat(x) != nil
case syntax.TsRegFile:
info := stat(x)
return info != nil && info.Mode().IsRegular()
case syntax.TsDirect:
return statMode(x, os.ModeDir)
//case syntax.TsCharSp:
//case syntax.TsBlckSp:
case syntax.TsNmPipe:
return statMode(x, os.ModeNamedPipe)
case syntax.TsSocket:
return statMode(x, os.ModeSocket)
case syntax.TsSmbLink:
info, _ := os.Lstat(x)
return info != nil && info.Mode()&os.ModeSymlink != 0
case syntax.TsSticky:
return statMode(x, os.ModeSticky)
case syntax.TsUIDSet:
return statMode(x, os.ModeSetuid)
case syntax.TsGIDSet:
return statMode(x, os.ModeSetgid)
//case syntax.TsGrpOwn:
//case syntax.TsUsrOwn:
//case syntax.TsModif:
case syntax.TsRead:
f, err := os.OpenFile(x, os.O_RDONLY, 0)
if err == nil {
f.Close()
}
return err == nil
case syntax.TsWrite:
f, err := os.OpenFile(x, os.O_WRONLY, 0)
if err == nil {
f.Close()
}
return err == nil
case syntax.TsExec:
// use an absolute path to not use $PATH
_, err := exec.LookPath(filepath.Join(r.Dir, x))
return err == nil
case syntax.TsNoEmpty:
info := stat(x)
return info != nil && info.Size() > 0
//case syntax.TsFdTerm:
case syntax.TsEmpStr:
return x == ""
case syntax.TsNempStr:
return x != ""
//case syntax.TsOptSet:
//case syntax.TsVarSet:
//case syntax.TsRefVar:
case syntax.TsNot:
return x == ""
default:
r.errf("unhandled unary test op: %v", op)
return false
}
}

37
vendor/github.com/mvdan/sh/syntax/canonical.sh generated vendored Normal file
View File

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

6
vendor/github.com/mvdan/sh/syntax/doc.go generated vendored Normal file
View File

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

1005
vendor/github.com/mvdan/sh/syntax/lexer.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

731
vendor/github.com/mvdan/sh/syntax/nodes.go generated vendored Normal file
View File

@ -0,0 +1,731 @@
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import "fmt"
// Node represents an AST node.
type Node interface {
// Pos returns the first character of the node
Pos() Pos
// End returns the character immediately after the node
End() Pos
}
// File is a shell program.
type File struct {
Name string
Stmts []*Stmt
Comments []*Comment
lines []Pos
}
// Pos is the internal representation of a position within a source
// file.
type Pos uint32
// IsValid reports whether the position is valid. All positions in nodes
// returned by Parse are valid.
func (p Pos) IsValid() bool { return p > 0 }
const maxPos = Pos(^uint32(0))
// Position describes a position within a source file including the line
// and column location. A Position is valid if the line number is > 0.
type Position struct {
Filename string // if any
Offset int // byte offset, starting at 0
Line int // line number, starting at 1
Column int // column number, starting at 1 (in bytes)
}
// IsValid reports whether the position is valid. All positions in nodes
// returned by Parse are valid.
func (p Position) IsValid() bool { return p.Line > 0 }
// String returns the position in the "file:line:column" form, or
// "line:column" if there is no filename available.
func (p Position) String() string {
prefix := ""
if p.Filename != "" {
prefix = p.Filename + ":"
}
return fmt.Sprintf("%s%d:%d", prefix, p.Line, p.Column)
}
func (f *File) Pos() Pos {
if len(f.Stmts) == 0 {
return 0
}
return f.Stmts[0].Pos()
}
func (f *File) End() Pos {
if len(f.Stmts) == 0 {
return 0
}
return f.Stmts[len(f.Stmts)-1].End()
}
func (f *File) Position(p Pos) (pos Position) {
pos.Filename = f.Name
pos.Offset = int(p) - 1
if i := searchPos(f.lines, p); i >= 0 {
pos.Line, pos.Column = i+1, int(p-f.lines[i])
}
return
}
func searchPos(a []Pos, x Pos) int {
i, j := 0, len(a)
for i < j {
h := i + (j-i)/2
if a[h] <= x {
i = h + 1
} else {
j = h
}
}
return i - 1
}
func posMax(p1, p2 Pos) Pos {
if p2 > 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 c.Hash + Pos(len(c.Text)) }
// Stmt represents a statement, otherwise known as a compound command.
// It is compromised of a command and other components that may come
// before or after it.
type Stmt struct {
Cmd Command
Position Pos
Semicolon Pos
Negated bool
Background bool
Assigns []*Assign
Redirs []*Redirect
}
func (s *Stmt) Pos() Pos { return s.Position }
func (s *Stmt) End() Pos {
if s.Semicolon.IsValid() {
return s.Semicolon + 1
}
end := s.Position
if s.Negated {
end++
}
if s.Cmd != nil {
end = s.Cmd.End()
}
if len(s.Assigns) > 0 {
end = posMax(end, s.Assigns[len(s.Assigns)-1].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 commands, which are
// directly placed in a Stmt.
type Command interface {
Node
commandNode()
}
func (*CallExpr) commandNode() {}
func (*IfClause) commandNode() {}
func (*WhileClause) commandNode() {}
func (*UntilClause) 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 (*EvalClause) commandNode() {}
func (*LetClause) commandNode() {}
func (*CoprocClause) commandNode() {}
// Assign represents an assignment to a variable.
type Assign struct {
Append bool
Name *Lit
Value *Word
}
func (a *Assign) Pos() Pos {
if a.Name != nil {
return a.Name.Pos()
}
return a.Value.Pos()
}
func (a *Assign) End() Pos {
if a.Value != nil {
return a.Value.End()
}
return a.Name.End() + 1
}
// Redirect represents an input/output redirection.
type Redirect struct {
OpPos Pos
Op RedirOperator
N *Lit
Word, Hdoc *Word
}
func (r *Redirect) Pos() Pos {
if r.N != nil {
return r.N.Pos()
}
return r.OpPos
}
func (r *Redirect) End() Pos { return r.Word.End() }
// CallExpr represents a command execution or function call.
type CallExpr struct {
Args []*Word
}
func (c *CallExpr) Pos() Pos { return c.Args[0].Pos() }
func (c *CallExpr) End() Pos { 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
Stmts []*Stmt
}
func (s *Subshell) Pos() Pos { return s.Lparen }
func (s *Subshell) End() Pos { return s.Rparen + 1 }
// Block represents a series of commands that should be executed in a
// nested scope.
type Block struct {
Lbrace, Rbrace Pos
Stmts []*Stmt
}
func (b *Block) Pos() Pos { return b.Rbrace }
func (b *Block) End() Pos { return b.Rbrace + 1 }
// IfClause represents an if statement.
type IfClause struct {
If, Then, Else, Fi Pos
CondStmts []*Stmt
ThenStmts []*Stmt
Elifs []*Elif
ElseStmts []*Stmt
}
func (c *IfClause) Pos() Pos { return c.If }
func (c *IfClause) End() Pos { return c.Fi + 2 }
// Elif represents an "else if" case in an if clause.
type Elif struct {
Elif, Then Pos
CondStmts []*Stmt
ThenStmts []*Stmt
}
// WhileClause represents a while clause.
type WhileClause struct {
While, Do, Done Pos
CondStmts []*Stmt
DoStmts []*Stmt
}
func (w *WhileClause) Pos() Pos { return w.While }
func (w *WhileClause) End() Pos { return w.Done + 4 }
// UntilClause represents an until clause.
type UntilClause struct {
Until, Do, Done Pos
CondStmts []*Stmt
DoStmts []*Stmt
}
func (u *UntilClause) Pos() Pos { return u.Until }
func (u *UntilClause) End() Pos { return u.Done + 4 }
// ForClause represents a for clause.
type ForClause struct {
For, Do, Done Pos
Loop Loop
DoStmts []*Stmt
}
func (f *ForClause) Pos() Pos { return f.For }
func (f *ForClause) End() Pos { return f.Done + 4 }
// Loop represents all nodes that can be loops in a for clause.
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.
type WordIter struct {
Name *Lit
List []*Word
}
func (w *WordIter) Pos() Pos { return w.Name.Pos() }
func (w *WordIter) End() Pos { return posMax(w.Name.End(), wordLastEnd(w.List)) }
// CStyleLoop represents the behaviour of a for clause similar to the C
// language.
//
// This node will never appear when in PosixConformant mode.
type CStyleLoop struct {
Lparen, Rparen Pos
Init, Cond, Post ArithmExpr
}
func (c *CStyleLoop) Pos() Pos { return c.Lparen }
func (c *CStyleLoop) End() Pos { return 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
BashStyle bool
Name *Lit
Body *Stmt
}
func (f *FuncDecl) Pos() Pos { return f.Position }
func (f *FuncDecl) End() Pos { return f.Body.End() }
// Word represents a non-empty list of nodes that are contiguous to each
// other. The word is delimeted by word boundaries.
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() }
// WordPart represents all nodes that can form a word.
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 (*ArrayExpr) wordPartNode() {}
func (*ExtGlob) wordPartNode() {}
// Lit represents an unquoted string consisting of characters that were
// not tokenized.
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 {
Position Pos
Dollar bool
Value string
}
func (q *SglQuoted) Pos() Pos { return q.Position }
func (q *SglQuoted) End() Pos {
end := q.Position + 2 + Pos(len(q.Value))
if q.Dollar {
end++
}
return end
}
// 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 q.Position + 3
}
return q.Position + 2
}
return q.Parts[len(q.Parts)-1].End() + 1
}
// CmdSubst represents a command substitution.
type CmdSubst struct {
Left, Right Pos
Stmts []*Stmt
}
func (c *CmdSubst) Pos() Pos { return c.Left }
func (c *CmdSubst) End() Pos { return c.Right + 1 }
// ParamExp represents a parameter expansion.
type ParamExp struct {
Dollar, Rbrace Pos
Short bool
Length, Excl bool // TODO(mvdan): rename Excl in 2.0 (Indirect, etc)
Param *Lit
Ind *Index
Slice *Slice
Repl *Replace
Exp *Expansion
}
func (p *ParamExp) Pos() Pos { return p.Dollar }
func (p *ParamExp) End() Pos {
if !p.Short {
return p.Rbrace + 1
}
return p.Param.End()
}
// Index represents access to an array via an index inside a ParamExp.
//
// This node will never appear when in PosixConformant mode.
type Index struct {
Expr ArithmExpr
}
// Slice represents character slicing inside a ParamExp.
//
// This node will never appear when in PosixConformant mode.
type Slice struct {
Offset, Length ArithmExpr
}
// Replace represents a search and replace 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
X ArithmExpr
}
func (a *ArithmExp) Pos() Pos { return a.Left }
func (a *ArithmExp) End() Pos {
if a.Bracket {
return a.Right + 1
}
return a.Right + 2
}
// ArithmCmd represents an arithmetic command.
//
// This node will never appear when in PosixConformant mode.
type ArithmCmd struct {
Left, Right Pos
X ArithmExpr
}
func (a *ArithmCmd) Pos() Pos { return a.Left }
func (a *ArithmCmd) End() Pos { return a.Right + 2 }
// ArithmExpr represents all nodes that form arithmetic expressions.
type ArithmExpr interface {
Node
arithmExprNode()
}
func (*BinaryArithm) arithmExprNode() {}
func (*UnaryArithm) arithmExprNode() {}
func (*ParenArithm) arithmExprNode() {}
func (*Word) arithmExprNode() {}
// BinaryArithm represents a binary expression between two 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.
//
// TODO(mvdan): we probably want to split up assigns in 2.0 (X would be
// a *Lit) to simplify the rules here. Perhaps reuse the Assign type?
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 expression over a node, either before
// or after it.
//
// If Op is Inc or Dec, X will be a *Word with a single *Lit whose value
// is a valid name.
//
// TODO(mvdan): consider splitting up Inc/Dec like the assigns above in
// 2.0.
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 u.OpPos + 2
}
return u.X.End()
}
// ParenArithm represents an expression within parentheses inside an
// ArithmExp.
type ParenArithm struct {
Lparen, Rparen Pos
X ArithmExpr
}
func (p *ParenArithm) Pos() Pos { return p.Lparen }
func (p *ParenArithm) End() Pos { return p.Rparen + 1 }
// CaseClause represents a case (switch) clause.
type CaseClause struct {
Case, Esac Pos
Word *Word
List []*PatternList
}
func (c *CaseClause) Pos() Pos { return c.Case }
func (c *CaseClause) End() Pos { return c.Esac + 4 }
// PatternList represents a pattern list (case) within a CaseClause.
type PatternList struct {
Op CaseOperator
OpPos Pos
Patterns []*Word
Stmts []*Stmt
}
// TestClause represents a Bash extended test clause.
//
// This node will never appear when in PosixConformant mode.
type TestClause struct {
Left, Right Pos
X TestExpr
}
func (t *TestClause) Pos() Pos { return t.Left }
func (t *TestClause) End() Pos { return t.Right + 2 }
// TestExpr represents all nodes that form arithmetic expressions.
type TestExpr interface {
Node
testExprNode()
}
func (*BinaryTest) testExprNode() {}
func (*UnaryTest) testExprNode() {}
func (*ParenTest) testExprNode() {}
func (*Word) testExprNode() {}
// BinaryTest represents a binary expression between two arithmetic
// 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 an unary expression over a node, either before
// or after it.
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 an expression within parentheses inside an
// TestExp.
type ParenTest struct {
Lparen, Rparen Pos
X TestExpr
}
func (p *ParenTest) Pos() Pos { return p.Lparen }
func (p *ParenTest) End() Pos { return p.Rparen + 1 }
// DeclClause represents a Bash declare clause.
//
// This node will never appear when in PosixConformant mode.
type DeclClause struct {
Position Pos
Variant string
Opts []*Word
Assigns []*Assign
}
func (d *DeclClause) Pos() Pos { return d.Position }
func (d *DeclClause) End() Pos {
if len(d.Assigns) > 0 {
return d.Assigns[len(d.Assigns)-1].End()
}
return wordLastEnd(d.Opts)
}
// ArrayExpr represents a Bash array expression.
//
// This node will never appear when in PosixConformant mode.
type ArrayExpr struct {
Lparen, Rparen Pos
List []*Word
}
func (a *ArrayExpr) Pos() Pos { return a.Lparen }
func (a *ArrayExpr) End() Pos { return a.Rparen + 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 never appear when in PosixConformant mode.
type ExtGlob struct {
OpPos Pos
Op GlobOperator
Pattern *Lit
}
func (e *ExtGlob) Pos() Pos { return e.OpPos }
func (e *ExtGlob) End() Pos { return e.Pattern.End() + 1 }
// ProcSubst represents a Bash process substitution.
//
// This node will never appear when in PosixConformant mode.
type ProcSubst struct {
OpPos, Rparen Pos
Op ProcOperator
Stmts []*Stmt
}
func (s *ProcSubst) Pos() Pos { return s.OpPos }
func (s *ProcSubst) End() Pos { return s.Rparen + 1 }
// EvalClause represents a Bash eval clause.
//
// This node will never appear when in PosixConformant mode.
//
// TODO(mvdan): EvalClause is actually pointless, as any non-trivial use
// of eval will involve parsing the program at run-time. Remove in 2.0.
type EvalClause struct {
Eval Pos
Stmt *Stmt
}
func (e *EvalClause) Pos() Pos { return e.Eval }
func (e *EvalClause) End() Pos {
if e.Stmt != nil {
return e.Stmt.End()
}
return e.Eval + 4
}
// CoprocClause represents a Bash coproc clause.
//
// This node will never appear when in PosixConformant mode.
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 never appear when in PosixConformant mode.
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 0
}
return ws[len(ws)-1].End()
}

1768
vendor/github.com/mvdan/sh/syntax/parser.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

904
vendor/github.com/mvdan/sh/syntax/printer.go generated vendored Normal file
View File

@ -0,0 +1,904 @@
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import (
"bufio"
"io"
"sync"
)
// PrintConfig controls how the printing of an AST node will behave.
type PrintConfig struct {
Spaces int // 0 (default) for tabs, >0 for number of spaces
}
var printerFree = sync.Pool{
New: func() interface{} {
return &printer{
bufWriter: bufio.NewWriter(nil),
lenPrinter: new(printer),
}
},
}
// Fprint "pretty-prints" the given AST file to the given writer.
func (c PrintConfig) Fprint(w io.Writer, f *File) error {
p := printerFree.Get().(*printer)
p.reset()
p.PrintConfig = c
p.lines, p.comments = f.lines, f.Comments
p.bufWriter.Reset(w)
p.stmts(f.Stmts)
p.commentsUpTo(0)
p.newline(0)
var err error
if flusher, ok := p.bufWriter.(interface {
Flush() error
}); ok {
err = flusher.Flush()
}
printerFree.Put(p)
return err
}
// Fprint "pretty-prints" the given AST file to the given writer. It
// calls PrintConfig.Fprint with its default settings.
func Fprint(w io.Writer, f *File) error {
return PrintConfig{}.Fprint(w, f)
}
type bufWriter interface {
WriteByte(byte) error
WriteString(string) (int, error)
Reset(io.Writer)
}
type printer struct {
bufWriter
PrintConfig
lines []Pos
wantSpace bool
wantNewline bool
wroteSemi bool
commentPadding int
// nline is the position of the next newline
nline Pos
nlineIndex int
// lastLevel is the last level of indentation that was used.
lastLevel int
// level is the current level of indentation.
level int
// levelIncs records which indentation level increments actually
// took place, to revert them once their section ends.
levelIncs []bool
nestedBinary bool
// comments is the list of pending comments to write.
comments []*Comment
// pendingHdocs is the list of pending heredocs to write.
pendingHdocs []*Redirect
// used in stmtLen to align comments
lenPrinter *printer
lenCounter byteCounter
}
func (p *printer) reset() {
p.wantSpace, p.wantNewline = false, false
p.commentPadding = 0
p.nline, p.nlineIndex = 0, 0
p.lastLevel, p.level = 0, 0
p.levelIncs = p.levelIncs[:0]
p.nestedBinary = false
p.pendingHdocs = p.pendingHdocs[:0]
}
func (p *printer) incLine() {
if p.nlineIndex++; p.nlineIndex >= len(p.lines) {
p.nline = maxPos
} else {
p.nline = p.lines[p.nlineIndex]
}
}
func (p *printer) incLines(pos Pos) {
for p.nline < pos {
p.incLine()
}
}
func (p *printer) spaces(n int) {
for i := 0; i < n; i++ {
p.WriteByte(' ')
}
}
func (p *printer) bslashNewl() {
if p.wantSpace {
p.WriteByte(' ')
}
p.WriteString("\\\n")
p.wantSpace = false
p.incLine()
}
func (p *printer) spacedString(s string) {
if p.wantSpace {
p.WriteByte(' ')
}
p.WriteString(s)
p.wantSpace = true
}
func (p *printer) semiOrNewl(s string, pos Pos) {
if p.wantNewline {
p.newline(pos)
p.indent()
} else {
if !p.wroteSemi {
p.WriteByte(';')
}
p.WriteByte(' ')
p.incLines(pos)
}
p.WriteString(s)
p.wantSpace = true
}
func (p *printer) incLevel() {
inc := false
if p.level <= p.lastLevel || len(p.levelIncs) == 0 {
p.level++
inc = true
} else if last := &p.levelIncs[len(p.levelIncs)-1]; *last {
*last = false
inc = true
}
p.levelIncs = append(p.levelIncs, inc)
}
func (p *printer) decLevel() {
if p.levelIncs[len(p.levelIncs)-1] {
p.level--
}
p.levelIncs = p.levelIncs[:len(p.levelIncs)-1]
}
func (p *printer) indent() {
p.lastLevel = p.level
switch {
case p.level == 0:
case p.Spaces == 0:
for i := 0; i < p.level; i++ {
p.WriteByte('\t')
}
case p.Spaces > 0:
p.spaces(p.Spaces * p.level)
}
}
func (p *printer) newline(pos Pos) {
p.wantNewline, p.wantSpace = false, false
p.WriteByte('\n')
if pos > p.nline {
p.incLine()
}
hdocs := p.pendingHdocs
p.pendingHdocs = p.pendingHdocs[:0]
for _, r := range hdocs {
p.word(r.Hdoc)
p.incLines(r.Hdoc.End())
p.unquotedWord(r.Word)
p.WriteByte('\n')
p.incLine()
p.wantSpace = false
}
}
func (p *printer) newlines(pos Pos) {
p.newline(pos)
if pos > p.nline {
// preserve single empty lines
p.WriteByte('\n')
p.incLine()
}
p.indent()
}
func (p *printer) commentsAndSeparate(pos Pos) {
p.commentsUpTo(pos)
if p.wantNewline || pos > p.nline {
p.newlines(pos)
}
}
func (p *printer) sepTok(s string, pos Pos) {
p.level++
p.commentsUpTo(pos)
p.level--
if p.wantNewline || pos > p.nline {
p.newlines(pos)
}
p.WriteString(s)
p.wantSpace = true
}
func (p *printer) semiRsrv(s string, pos Pos, fallback bool) {
p.level++
p.commentsUpTo(pos)
p.level--
if p.wantNewline || pos > p.nline {
p.newlines(pos)
} else if fallback {
if !p.wroteSemi {
p.WriteByte(';')
}
p.WriteByte(' ')
} else if p.wantSpace {
p.WriteByte(' ')
}
p.WriteString(s)
p.wantSpace = true
}
func (p *printer) anyCommentsBefore(pos Pos) bool {
if !pos.IsValid() || len(p.comments) < 1 {
return false
}
return p.comments[0].Hash < pos
}
func (p *printer) commentsUpTo(pos Pos) {
if len(p.comments) < 1 {
return
}
c := p.comments[0]
if pos.IsValid() && c.Hash >= pos {
return
}
p.comments = p.comments[1:]
switch {
case p.nlineIndex == 0:
case c.Hash > p.nline:
p.newlines(c.Hash)
case p.wantSpace:
p.spaces(p.commentPadding + 1)
}
p.incLines(c.Hash)
p.WriteByte('#')
p.WriteString(c.Text)
p.commentsUpTo(pos)
}
func (p *printer) wordPart(wp WordPart) {
switch x := wp.(type) {
case *Lit:
p.WriteString(x.Value)
case *SglQuoted:
if x.Dollar {
p.WriteByte('$')
}
p.WriteByte('\'')
p.WriteString(x.Value)
p.WriteByte('\'')
p.incLines(x.End())
case *DblQuoted:
if x.Dollar {
p.WriteByte('$')
}
p.WriteByte('"')
for i, n := range x.Parts {
p.wordPart(n)
if i == len(x.Parts)-1 {
p.incLines(n.End())
}
}
p.WriteByte('"')
case *CmdSubst:
p.incLines(x.Pos())
p.WriteString("$(")
p.wantSpace = len(x.Stmts) > 0 && startsWithLparen(x.Stmts[0])
p.nestedStmts(x.Stmts, x.Right)
p.sepTok(")", x.Right)
case *ParamExp:
if x.Short {
p.WriteByte('$')
p.WriteString(x.Param.Value)
break
}
p.WriteString("${")
switch {
case x.Length:
p.WriteByte('#')
case x.Excl:
p.WriteByte('!')
}
if x.Param != nil {
p.WriteString(x.Param.Value)
}
if x.Ind != nil {
p.WriteByte('[')
p.arithmExpr(x.Ind.Expr, false)
p.WriteByte(']')
}
if x.Slice != nil {
p.WriteByte(':')
if un, ok := x.Slice.Offset.(*UnaryArithm); ok {
if un.Op == Plus || un.Op == Minus {
// to avoid :+ and :-
p.WriteByte(' ')
}
}
p.arithmExpr(x.Slice.Offset, true)
if x.Slice.Length != nil {
p.WriteByte(':')
p.arithmExpr(x.Slice.Length, true)
}
} else if x.Repl != nil {
if x.Repl.All {
p.WriteByte('/')
}
p.WriteByte('/')
p.word(x.Repl.Orig)
p.WriteByte('/')
p.word(x.Repl.With)
} else if x.Exp != nil {
p.WriteString(x.Exp.Op.String())
p.word(x.Exp.Word)
}
p.WriteByte('}')
case *ArithmExp:
p.WriteString("$((")
p.arithmExpr(x.X, false)
p.WriteString("))")
case *ArrayExpr:
p.wantSpace = false
p.WriteByte('(')
p.wordJoin(x.List, false)
p.sepTok(")", x.Rparen)
case *ExtGlob:
p.WriteString(x.Op.String())
p.WriteString(x.Pattern.Value)
p.WriteByte(')')
case *ProcSubst:
// avoid conflict with << and others
if p.wantSpace {
p.WriteByte(' ')
p.wantSpace = false
}
p.WriteString(x.Op.String())
p.nestedStmts(x.Stmts, 0)
p.WriteByte(')')
}
}
func (p *printer) loop(loop Loop) {
switch x := loop.(type) {
case *WordIter:
p.WriteString(x.Name.Value)
if len(x.List) > 0 {
p.spacedString(" in")
p.wordJoin(x.List, true)
}
case *CStyleLoop:
p.WriteString("((")
if x.Init == nil {
p.WriteByte(' ')
}
p.arithmExpr(x.Init, false)
p.WriteString("; ")
p.arithmExpr(x.Cond, false)
p.WriteString("; ")
p.arithmExpr(x.Post, false)
p.WriteString("))")
}
}
func (p *printer) arithmExpr(expr ArithmExpr, compact bool) {
switch x := expr.(type) {
case *Word:
p.word(x)
case *BinaryArithm:
if compact {
p.arithmExpr(x.X, compact)
p.WriteString(x.Op.String())
p.arithmExpr(x.Y, compact)
} else {
p.arithmExpr(x.X, compact)
if x.Op != Comma {
p.WriteByte(' ')
}
p.WriteString(x.Op.String())
p.WriteByte(' ')
p.arithmExpr(x.Y, compact)
}
case *UnaryArithm:
if x.Post {
p.arithmExpr(x.X, compact)
p.WriteString(x.Op.String())
} else {
p.WriteString(x.Op.String())
p.arithmExpr(x.X, compact)
}
case *ParenArithm:
p.WriteByte('(')
p.arithmExpr(x.X, false)
p.WriteByte(')')
}
}
func (p *printer) testExpr(expr TestExpr) {
switch x := expr.(type) {
case *Word:
p.word(x)
case *BinaryTest:
p.testExpr(x.X)
p.WriteByte(' ')
p.WriteString(x.Op.String())
p.WriteByte(' ')
p.testExpr(x.Y)
case *UnaryTest:
p.WriteString(x.Op.String())
p.WriteByte(' ')
p.testExpr(x.X)
case *ParenTest:
p.WriteByte('(')
p.testExpr(x.X)
p.WriteByte(')')
}
}
func (p *printer) word(w *Word) {
for _, n := range w.Parts {
p.wordPart(n)
}
p.wantSpace = true
}
func (p *printer) unquotedWord(w *Word) {
for _, wp := range w.Parts {
switch x := wp.(type) {
case *SglQuoted:
p.WriteString(x.Value)
case *DblQuoted:
for _, qp := range x.Parts {
p.wordPart(qp)
}
case *Lit:
for i := 0; i < len(x.Value); i++ {
if b := x.Value[i]; b == '\\' {
if i++; i < len(x.Value) {
p.WriteByte(x.Value[i])
}
} else {
p.WriteByte(b)
}
}
}
}
}
func (p *printer) wordJoin(ws []*Word, backslash bool) {
anyNewline := false
for _, w := range ws {
if pos := w.Pos(); pos > p.nline {
p.commentsUpTo(pos)
if backslash {
p.bslashNewl()
} else {
p.WriteByte('\n')
p.incLine()
}
if !anyNewline {
p.incLevel()
anyNewline = true
}
p.indent()
} else if p.wantSpace {
p.WriteByte(' ')
p.wantSpace = false
}
p.word(w)
}
if anyNewline {
p.decLevel()
}
}
func (p *printer) stmt(s *Stmt) {
if s.Negated {
p.spacedString("!")
}
p.assigns(s.Assigns)
var startRedirs int
if s.Cmd != nil {
startRedirs = p.command(s.Cmd, s.Redirs)
}
anyNewline := false
for _, r := range s.Redirs[startRedirs:] {
if r.OpPos > p.nline {
p.bslashNewl()
if !anyNewline {
p.incLevel()
anyNewline = true
}
p.indent()
}
p.commentsAndSeparate(r.OpPos)
if p.wantSpace {
p.WriteByte(' ')
}
if r.N != nil {
p.WriteString(r.N.Value)
}
p.WriteString(r.Op.String())
p.word(r.Word)
if r.Op == Hdoc || r.Op == DashHdoc {
p.pendingHdocs = append(p.pendingHdocs, r)
}
}
p.wroteSemi = false
if s.Semicolon.IsValid() && s.Semicolon > p.nline {
p.incLevel()
p.bslashNewl()
p.indent()
p.decLevel()
p.WriteByte(';')
p.wroteSemi = true
} else if s.Background {
p.WriteString(" &")
}
if anyNewline {
p.decLevel()
}
}
func (p *printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
if p.wantSpace {
p.WriteByte(' ')
p.wantSpace = false
}
switch x := cmd.(type) {
case *CallExpr:
if len(x.Args) <= 1 {
p.wordJoin(x.Args, true)
return 0
}
p.wordJoin(x.Args[:1], true)
for _, r := range redirs {
if r.Pos() > x.Args[1].Pos() || r.Op == Hdoc || r.Op == DashHdoc {
break
}
if p.wantSpace {
p.WriteByte(' ')
}
if r.N != nil {
p.WriteString(r.N.Value)
}
p.WriteString(r.Op.String())
p.word(r.Word)
startRedirs++
}
p.wordJoin(x.Args[1:], true)
case *Block:
p.WriteByte('{')
p.wantSpace = true
p.nestedStmts(x.Stmts, x.Rbrace)
p.semiRsrv("}", x.Rbrace, true)
case *IfClause:
p.spacedString("if")
p.nestedStmts(x.CondStmts, 0)
p.semiOrNewl("then", x.Then)
p.nestedStmts(x.ThenStmts, 0)
for _, el := range x.Elifs {
p.semiRsrv("elif", el.Elif, true)
p.nestedStmts(el.CondStmts, 0)
p.semiOrNewl("then", el.Then)
p.nestedStmts(el.ThenStmts, 0)
}
if len(x.ElseStmts) > 0 {
p.semiRsrv("else", x.Else, true)
p.nestedStmts(x.ElseStmts, 0)
} else if x.Else.IsValid() {
p.incLines(x.Else)
}
p.semiRsrv("fi", x.Fi, true)
case *Subshell:
p.WriteByte('(')
p.wantSpace = len(x.Stmts) > 0 && startsWithLparen(x.Stmts[0])
p.nestedStmts(x.Stmts, x.Rparen)
p.sepTok(")", x.Rparen)
case *WhileClause:
p.spacedString("while")
p.nestedStmts(x.CondStmts, 0)
p.semiOrNewl("do", x.Do)
p.nestedStmts(x.DoStmts, 0)
p.semiRsrv("done", x.Done, true)
case *ForClause:
p.WriteString("for ")
p.loop(x.Loop)
p.semiOrNewl("do", x.Do)
p.nestedStmts(x.DoStmts, 0)
p.semiRsrv("done", x.Done, true)
case *BinaryCmd:
p.stmt(x.X)
indent := !p.nestedBinary
if indent {
p.incLevel()
}
_, p.nestedBinary = x.Y.Cmd.(*BinaryCmd)
if len(p.pendingHdocs) == 0 && x.Y.Pos() > p.nline {
p.bslashNewl()
p.indent()
}
p.spacedString(x.Op.String())
if p.anyCommentsBefore(x.Y.Pos()) {
p.wantSpace = false
p.WriteByte('\n')
p.indent()
p.incLines(p.comments[0].Pos())
p.commentsUpTo(x.Y.Pos())
p.WriteByte('\n')
p.indent()
}
p.incLines(x.Y.Pos())
p.stmt(x.Y)
if indent {
p.decLevel()
}
p.nestedBinary = false
case *FuncDecl:
if x.BashStyle {
p.WriteString("function ")
}
p.WriteString(x.Name.Value)
p.WriteString("() ")
p.incLines(x.Body.Pos())
p.stmt(x.Body)
case *CaseClause:
p.WriteString("case ")
p.word(x.Word)
p.WriteString(" in")
p.incLevel()
for _, pl := range x.List {
p.commentsAndSeparate(pl.Patterns[0].Pos())
for i, w := range pl.Patterns {
if i > 0 {
p.spacedString("|")
}
if p.wantSpace {
p.WriteByte(' ')
}
p.word(w)
}
p.WriteByte(')')
p.wantSpace = true
sep := len(pl.Stmts) > 1 || (len(pl.Stmts) > 0 && pl.Stmts[0].Pos() > p.nline)
p.nestedStmts(pl.Stmts, 0)
p.level++
if sep {
p.commentsUpTo(pl.OpPos)
p.newlines(pl.OpPos)
}
p.spacedString(pl.Op.String())
p.incLines(pl.OpPos)
p.level--
if sep || pl.OpPos == x.Esac {
p.wantNewline = true
}
}
p.decLevel()
p.semiRsrv("esac", x.Esac, len(x.List) == 0)
case *UntilClause:
p.spacedString("until")
p.nestedStmts(x.CondStmts, 0)
p.semiOrNewl("do", x.Do)
p.nestedStmts(x.DoStmts, 0)
p.semiRsrv("done", x.Done, true)
case *ArithmCmd:
p.WriteString("((")
p.arithmExpr(x.X, false)
p.WriteString("))")
case *TestClause:
p.WriteString("[[ ")
p.testExpr(x.X)
p.spacedString("]]")
case *DeclClause:
name := x.Variant
if name == "" {
name = "declare"
}
p.spacedString(name)
for _, w := range x.Opts {
p.WriteByte(' ')
p.word(w)
}
p.assigns(x.Assigns)
case *EvalClause:
p.spacedString("eval")
if x.Stmt != nil {
p.stmt(x.Stmt)
}
case *CoprocClause:
p.spacedString("coproc")
if x.Name != nil {
p.WriteByte(' ')
p.WriteString(x.Name.Value)
}
p.stmt(x.Stmt)
case *LetClause:
p.spacedString("let")
for _, n := range x.Exprs {
p.WriteByte(' ')
p.arithmExpr(n, true)
}
}
return startRedirs
}
func startsWithLparen(s *Stmt) bool {
switch x := s.Cmd.(type) {
case *Subshell:
return true
case *BinaryCmd:
return startsWithLparen(x.X)
}
return false
}
func (p *printer) hasInline(pos, npos, nline Pos) bool {
for _, c := range p.comments {
if c.Hash > nline {
return false
}
if c.Hash > pos && (npos == 0 || c.Hash < npos) {
return true
}
}
return false
}
func (p *printer) stmts(stmts []*Stmt) {
switch len(stmts) {
case 0:
return
case 1:
s := stmts[0]
pos := s.Pos()
p.commentsUpTo(pos)
if pos <= p.nline {
p.stmt(s)
} else {
if p.nlineIndex > 0 {
p.newlines(pos)
}
p.incLines(pos)
p.stmt(s)
p.wantNewline = true
}
return
}
inlineIndent := 0
for i, s := range stmts {
pos := s.Pos()
ind := p.nlineIndex
p.commentsUpTo(pos)
if p.nlineIndex > 0 {
p.newlines(pos)
}
p.incLines(pos)
p.stmt(s)
var npos Pos
if i+1 < len(stmts) {
npos = stmts[i+1].Pos()
}
if !p.hasInline(pos, npos, p.nline) {
inlineIndent = 0
p.commentPadding = 0
continue
}
if ind < len(p.lines)-1 && s.End() > p.lines[ind+1] {
inlineIndent = 0
}
if inlineIndent == 0 {
ind2 := p.nlineIndex
nline2 := p.nline
follow := stmts[i:]
for j, s2 := range follow {
pos2 := s2.Pos()
var npos2 Pos
if j+1 < len(follow) {
npos2 = follow[j+1].Pos()
}
if pos2 > nline2 || !p.hasInline(pos2, npos2, nline2) {
break
}
if l := p.stmtLen(s2); l > inlineIndent {
inlineIndent = l
}
if ind2++; ind2 >= len(p.lines) {
nline2 = maxPos
} else {
nline2 = p.lines[ind2]
}
}
if ind2 == p.nlineIndex+1 {
// no inline comments directly after this one
continue
}
}
if inlineIndent > 0 {
p.commentPadding = inlineIndent - p.stmtLen(s)
}
}
p.wantNewline = true
}
type byteCounter int
func (c *byteCounter) WriteByte(b byte) error {
*c++
return nil
}
func (c *byteCounter) WriteString(s string) (int, error) {
*c += byteCounter(len(s))
return 0, nil
}
func (c *byteCounter) Reset(io.Writer) { *c = 0 }
func (p *printer) stmtLen(s *Stmt) int {
*p.lenPrinter = printer{bufWriter: &p.lenCounter}
p.lenPrinter.bufWriter.Reset(nil)
p.lenPrinter.incLines(s.Pos())
p.lenPrinter.stmt(s)
return int(p.lenCounter)
}
func (p *printer) nestedStmts(stmts []*Stmt, closing Pos) {
p.incLevel()
if len(stmts) == 1 && closing > p.nline && stmts[0].End() <= p.nline {
p.newline(0)
p.indent()
}
p.stmts(stmts)
p.decLevel()
}
func (p *printer) assigns(assigns []*Assign) {
anyNewline := false
for _, a := range assigns {
if a.Pos() > p.nline {
p.bslashNewl()
if !anyNewline {
p.incLevel()
anyNewline = true
}
p.indent()
} else if p.wantSpace {
p.WriteByte(' ')
}
if a.Name != nil {
p.WriteString(a.Name.Value)
if a.Append {
p.WriteByte('+')
}
p.WriteByte('=')
}
if a.Value != nil {
p.word(a.Value)
}
p.wantSpace = true
}
if anyNewline {
p.decLevel()
}
}

16
vendor/github.com/mvdan/sh/syntax/token_string.go generated vendored Normal file
View File

@ -0,0 +1,16 @@
// Code generated by "stringer -type token"; DO NOT EDIT
package syntax
import "fmt"
const _token_name = "illegalTokEOFLitLitWordLitRedir'\"`&&&||||&$$'$\"${$[$($(([(((}])));;;;&;;&!++--***==!=<=>=+=-=*=/=%=&=|=^=<<=>>=>>><<><&>&>|<<<<-<<<&>&>><(>(+:+-:-?:?=:=%%%###^^^,,,@///:-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, 16, 23, 31, 32, 33, 34, 35, 37, 39, 40, 42, 43, 45, 47, 49, 51, 53, 56, 57, 58, 60, 61, 62, 63, 65, 66, 68, 70, 73, 74, 76, 78, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 108, 111, 112, 114, 115, 117, 119, 121, 123, 125, 128, 131, 133, 136, 138, 140, 141, 143, 144, 146, 147, 149, 150, 152, 153, 155, 156, 158, 159, 161, 162, 164, 165, 166, 168, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 222, 225, 228, 231, 234, 237, 240, 243, 246, 248, 250, 252, 254, 256}
func (i token) String() string {
if i >= token(len(_token_index)-1) {
return fmt.Sprintf("token(%d)", i)
}
return _token_name[_token_index[i]:_token_index[i+1]]
}

340
vendor/github.com/mvdan/sh/syntax/tokens.go generated vendored Normal file
View File

@ -0,0 +1,340 @@
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
type token uint32
// Modified version of golang.org/x/tools/cmd/stringer that gets the
// string value from the inline comment of each constant, if there is
// one. Also removes leading '_'.
//go:generate stringer -type token
// The list of all possible tokens.
const (
illegalTok token = iota
_EOF
_Lit
_LitWord
_LitRedir
sglQuote // '
dblQuote // "
bckQuote // `
and // &
andAnd // &&
orOr // ||
or // |
pipeAll // |&
dollar // $
dollSglQuote // $'
dollDblQuote // $"
dollBrace // ${
dollBrack // $[
dollParen // $(
dollDblParen // $((
leftBrack // [
leftParen // (
dblLeftParen // ((
rightBrace // }
rightBrack // ]
rightParen // )
dblRightParen // ))
semicolon // ;
dblSemicolon // ;;
semiFall // ;&
dblSemiFall // ;;&
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 (
DblSemicolon = CaseOperator(dblSemicolon) + iota
SemiFall
DblSemiFall
)
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)
// TODO(mvdan): == and != are pattern matches; use more
// appropriate names like TsMatch and TsNoMatch in 2.0
TsEqual = BinTestOperator(equal)
TsNequal = BinTestOperator(nequal)
TsBefore = BinTestOperator(rdrIn)
TsAfter = BinTestOperator(rdrOut)
// Deprecated: now parses as TsEqual
TsAssgn = BinTestOperator(assgn) // TODO(mvdan): remove in 2.0
)
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 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() }

184
vendor/github.com/mvdan/sh/syntax/walk.go generated vendored Normal file
View File

@ -0,0 +1,184 @@
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import "fmt"
func walkStmts(stmts []*Stmt, f func(Node) bool) {
for _, s := range stmts {
Walk(s, f)
}
}
func walkWords(words []*Word, f func(Node) bool) {
for _, w := range words {
Walk(w, f)
}
}
// Walk traverses an AST 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.Stmts, f)
case *Stmt:
if x.Cmd != nil {
Walk(x.Cmd, f)
}
for _, a := range x.Assigns {
Walk(a, 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)
}
case *Redirect:
if x.N != nil {
Walk(x.N, f)
}
Walk(x.Word, f)
if x.Hdoc != nil {
Walk(x.Hdoc, f)
}
case *CallExpr:
walkWords(x.Args, f)
case *Subshell:
walkStmts(x.Stmts, f)
case *Block:
walkStmts(x.Stmts, f)
case *IfClause:
walkStmts(x.CondStmts, f)
walkStmts(x.ThenStmts, f)
for _, elif := range x.Elifs {
walkStmts(elif.CondStmts, f)
walkStmts(elif.ThenStmts, f)
}
walkStmts(x.ElseStmts, f)
case *WhileClause:
walkStmts(x.CondStmts, f)
walkStmts(x.DoStmts, f)
case *UntilClause:
walkStmts(x.CondStmts, f)
walkStmts(x.DoStmts, f)
case *ForClause:
Walk(x.Loop, f)
walkStmts(x.DoStmts, f)
case *WordIter:
Walk(x.Name, f)
walkWords(x.List, 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.Stmts, f)
case *ParamExp:
if x.Param != nil {
Walk(x.Param, f)
}
if x.Ind != nil {
Walk(x.Ind.Expr, f)
}
if x.Repl != nil {
Walk(x.Repl.Orig, f)
Walk(x.Repl.With, f)
}
if x.Exp != nil {
Walk(x.Exp.Word, f)
}
case *ArithmExp:
if x.X != nil {
Walk(x.X, f)
}
case *ArithmCmd:
if x.X != nil {
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 _, pl := range x.List {
walkWords(pl.Patterns, f)
walkStmts(pl.Stmts, f)
}
case *TestClause:
Walk(x.X, f)
case *DeclClause:
walkWords(x.Opts, f)
for _, a := range x.Assigns {
Walk(a, f)
}
case *ArrayExpr:
walkWords(x.List, f)
case *ExtGlob:
Walk(x.Pattern, f)
case *ProcSubst:
walkStmts(x.Stmts, f)
case *EvalClause:
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)
}

12
vendor/vendor.json vendored
View File

@ -50,6 +50,18 @@
"revision": "95345c4e1c0ebc9d16a3284177f09360f4d20fab",
"revisionTime": "2017-01-24T11:57:57Z"
},
{
"checksumSHA1": "1ZLAvHVYAS3kxaYI8OQiTBllqNU=",
"path": "github.com/mvdan/sh/interp",
"revision": "17e267b541e30baece16b7ddeae50822cc6a795f",
"revisionTime": "2017-04-24T11:31:08Z"
},
{
"checksumSHA1": "4/7joITdf4wl+uoV8zDXgYqy2aw=",
"path": "github.com/mvdan/sh/syntax",
"revision": "17e267b541e30baece16b7ddeae50822cc6a795f",
"revisionTime": "2017-04-24T11:31:08Z"
},
{
"checksumSHA1": "HUXE+Nrcau8FSaVEvPYHMvDjxOE=",
"path": "github.com/satori/go.uuid",