diff --git a/go.mod b/go.mod index fe204a8f..66fb0d30 100644 --- a/go.mod +++ b/go.mod @@ -35,5 +35,6 @@ require ( golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152 // indirect golang.org/x/sys v0.0.0-20191110163157-d32e6e3b99c4 // indirect gopkg.in/yaml.v2 v2.2.5 - mvdan.cc/sh v2.6.4+incompatible + mvdan.cc/sh v2.6.4+incompatible // indirect + mvdan.cc/sh/v3 v3.0.0-beta1 ) diff --git a/go.sum b/go.sum index 2c8dd79b..fdf31bb8 100644 --- a/go.sum +++ b/go.sum @@ -204,6 +204,7 @@ github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzI github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= github.com/philopon/go-toposort v0.0.0-20170620085441-9be86dbd762f h1:WyCn68lTiytVSkk7W1K9nBiSGTSRlUOdyTnSjwrIlok= github.com/philopon/go-toposort v0.0.0-20170620085441-9be86dbd762f/go.mod h1:/iRjX3DdSK956SzsUdV55J+wIsQ+2IBWmBrB4RvZfk4= +github.com/pkg/diff v0.0.0-20190930165518-531926345625/go.mod h1:kFj35MyHn14a6pIgWhm46KVjJr5CHys3eEYxkuKD1EI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -221,6 +222,7 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.5.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rootless-containers/proto v0.1.0 h1:gS1JOMEtk1YDYHCzBAf/url+olMJbac7MTrgSeP6zh4= github.com/rootless-containers/proto v0.1.0/go.mod h1:vgkUFZbQd0gcE/K/ZwtE4MYjZPu0UNHLXIQxhyqAFh8= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= @@ -302,6 +304,7 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152 h1:ZC1Xn5A1nlpSmQCIva4bZ3ob3lmhYIefc+GU+DLg1Ow= golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -350,6 +353,8 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190913181337-0240832f5c3d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -361,6 +366,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= @@ -377,5 +383,8 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.1.0+incompatible h1:5USw7CrJBYKqjg9R7QlA6jzqZKEAtvW82aNmsxxGPxw= gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +mvdan.cc/editorconfig v0.1.1-0.20191109213504-890940e3f00e/go.mod h1:Ge4atmRUYqueGppvJ7JNrtqpqokoJEFxYbP0Z+WeKS8= mvdan.cc/sh v2.6.4+incompatible h1:eD6tDeh0pw+/TOTI1BBEryZ02rD2nMcFsgcvde7jffM= mvdan.cc/sh v2.6.4+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8= +mvdan.cc/sh/v3 v3.0.0-beta1 h1:UqiwBEXEPzelaGxuvixaOtzc7WzKtrElePJ8HqvW7K8= +mvdan.cc/sh/v3 v3.0.0-beta1/go.mod h1:rBIndNJFYPp8xSppiZcGIk6B5d1g3OEARxEaXjPxwVI= diff --git a/vendor/golang.org/x/xerrors/LICENSE b/vendor/golang.org/x/xerrors/LICENSE new file mode 100644 index 00000000..e4a47e17 --- /dev/null +++ b/vendor/golang.org/x/xerrors/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2019 The Go Authors. 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 Google Inc. 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. diff --git a/vendor/golang.org/x/xerrors/PATENTS b/vendor/golang.org/x/xerrors/PATENTS new file mode 100644 index 00000000..73309904 --- /dev/null +++ b/vendor/golang.org/x/xerrors/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/xerrors/README b/vendor/golang.org/x/xerrors/README new file mode 100644 index 00000000..aac7867a --- /dev/null +++ b/vendor/golang.org/x/xerrors/README @@ -0,0 +1,2 @@ +This repository holds the transition packages for the new Go 1.13 error values. +See golang.org/design/29934-error-values. diff --git a/vendor/golang.org/x/xerrors/adaptor.go b/vendor/golang.org/x/xerrors/adaptor.go new file mode 100644 index 00000000..4317f248 --- /dev/null +++ b/vendor/golang.org/x/xerrors/adaptor.go @@ -0,0 +1,193 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "bytes" + "fmt" + "io" + "reflect" + "strconv" +) + +// FormatError calls the FormatError method of f with an errors.Printer +// configured according to s and verb, and writes the result to s. +func FormatError(f Formatter, s fmt.State, verb rune) { + // Assuming this function is only called from the Format method, and given + // that FormatError takes precedence over Format, it cannot be called from + // any package that supports errors.Formatter. It is therefore safe to + // disregard that State may be a specific printer implementation and use one + // of our choice instead. + + // limitations: does not support printing error as Go struct. + + var ( + sep = " " // separator before next error + p = &state{State: s} + direct = true + ) + + var err error = f + + switch verb { + // Note that this switch must match the preference order + // for ordinary string printing (%#v before %+v, and so on). + + case 'v': + if s.Flag('#') { + if stringer, ok := err.(fmt.GoStringer); ok { + io.WriteString(&p.buf, stringer.GoString()) + goto exit + } + // proceed as if it were %v + } else if s.Flag('+') { + p.printDetail = true + sep = "\n - " + } + case 's': + case 'q', 'x', 'X': + // Use an intermediate buffer in the rare cases that precision, + // truncation, or one of the alternative verbs (q, x, and X) are + // specified. + direct = false + + default: + p.buf.WriteString("%!") + p.buf.WriteRune(verb) + p.buf.WriteByte('(') + switch { + case err != nil: + p.buf.WriteString(reflect.TypeOf(f).String()) + default: + p.buf.WriteString("") + } + p.buf.WriteByte(')') + io.Copy(s, &p.buf) + return + } + +loop: + for { + switch v := err.(type) { + case Formatter: + err = v.FormatError((*printer)(p)) + case fmt.Formatter: + v.Format(p, 'v') + break loop + default: + io.WriteString(&p.buf, v.Error()) + break loop + } + if err == nil { + break + } + if p.needColon || !p.printDetail { + p.buf.WriteByte(':') + p.needColon = false + } + p.buf.WriteString(sep) + p.inDetail = false + p.needNewline = false + } + +exit: + width, okW := s.Width() + prec, okP := s.Precision() + + if !direct || (okW && width > 0) || okP { + // Construct format string from State s. + format := []byte{'%'} + if s.Flag('-') { + format = append(format, '-') + } + if s.Flag('+') { + format = append(format, '+') + } + if s.Flag(' ') { + format = append(format, ' ') + } + if okW { + format = strconv.AppendInt(format, int64(width), 10) + } + if okP { + format = append(format, '.') + format = strconv.AppendInt(format, int64(prec), 10) + } + format = append(format, string(verb)...) + fmt.Fprintf(s, string(format), p.buf.String()) + } else { + io.Copy(s, &p.buf) + } +} + +var detailSep = []byte("\n ") + +// state tracks error printing state. It implements fmt.State. +type state struct { + fmt.State + buf bytes.Buffer + + printDetail bool + inDetail bool + needColon bool + needNewline bool +} + +func (s *state) Write(b []byte) (n int, err error) { + if s.printDetail { + if len(b) == 0 { + return 0, nil + } + if s.inDetail && s.needColon { + s.needNewline = true + if b[0] == '\n' { + b = b[1:] + } + } + k := 0 + for i, c := range b { + if s.needNewline { + if s.inDetail && s.needColon { + s.buf.WriteByte(':') + s.needColon = false + } + s.buf.Write(detailSep) + s.needNewline = false + } + if c == '\n' { + s.buf.Write(b[k:i]) + k = i + 1 + s.needNewline = true + } + } + s.buf.Write(b[k:]) + if !s.inDetail { + s.needColon = true + } + } else if !s.inDetail { + s.buf.Write(b) + } + return len(b), nil +} + +// printer wraps a state to implement an xerrors.Printer. +type printer state + +func (s *printer) Print(args ...interface{}) { + if !s.inDetail || s.printDetail { + fmt.Fprint((*state)(s), args...) + } +} + +func (s *printer) Printf(format string, args ...interface{}) { + if !s.inDetail || s.printDetail { + fmt.Fprintf((*state)(s), format, args...) + } +} + +func (s *printer) Detail() bool { + s.inDetail = true + return s.printDetail +} diff --git a/vendor/golang.org/x/xerrors/codereview.cfg b/vendor/golang.org/x/xerrors/codereview.cfg new file mode 100644 index 00000000..3f8b14b6 --- /dev/null +++ b/vendor/golang.org/x/xerrors/codereview.cfg @@ -0,0 +1 @@ +issuerepo: golang/go diff --git a/vendor/golang.org/x/xerrors/doc.go b/vendor/golang.org/x/xerrors/doc.go new file mode 100644 index 00000000..eef99d9d --- /dev/null +++ b/vendor/golang.org/x/xerrors/doc.go @@ -0,0 +1,22 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package xerrors implements functions to manipulate errors. +// +// This package is based on the Go 2 proposal for error values: +// https://golang.org/design/29934-error-values +// +// These functions were incorporated into the standard library's errors package +// in Go 1.13: +// - Is +// - As +// - Unwrap +// +// Also, Errorf's %w verb was incorporated into fmt.Errorf. +// +// Use this package to get equivalent behavior in all supported Go versions. +// +// No other features of this package were included in Go 1.13, and at present +// there are no plans to include any of them. +package xerrors // import "golang.org/x/xerrors" diff --git a/vendor/golang.org/x/xerrors/errors.go b/vendor/golang.org/x/xerrors/errors.go new file mode 100644 index 00000000..e88d3772 --- /dev/null +++ b/vendor/golang.org/x/xerrors/errors.go @@ -0,0 +1,33 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import "fmt" + +// errorString is a trivial implementation of error. +type errorString struct { + s string + frame Frame +} + +// New returns an error that formats as the given text. +// +// The returned error contains a Frame set to the caller's location and +// implements Formatter to show this information when printed with details. +func New(text string) error { + return &errorString{text, Caller(1)} +} + +func (e *errorString) Error() string { + return e.s +} + +func (e *errorString) Format(s fmt.State, v rune) { FormatError(e, s, v) } + +func (e *errorString) FormatError(p Printer) (next error) { + p.Print(e.s) + e.frame.Format(p) + return nil +} diff --git a/vendor/golang.org/x/xerrors/fmt.go b/vendor/golang.org/x/xerrors/fmt.go new file mode 100644 index 00000000..74c1c93e --- /dev/null +++ b/vendor/golang.org/x/xerrors/fmt.go @@ -0,0 +1,109 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "fmt" + "strings" + + "golang.org/x/xerrors/internal" +) + +// Errorf formats according to a format specifier and returns the string as a +// value that satisfies error. +// +// The returned error includes the file and line number of the caller when +// formatted with additional detail enabled. If the last argument is an error +// the returned error's Format method will return it if the format string ends +// with ": %s", ": %v", or ": %w". If the last argument is an error and the +// format string ends with ": %w", the returned error implements Wrapper +// with an Unwrap method returning it. +func Errorf(format string, a ...interface{}) error { + err, wrap := lastError(format, a) + format = formatPlusW(format) + if err == nil { + return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)} + } + + // TODO: this is not entirely correct. The error value could be + // printed elsewhere in format if it mixes numbered with unnumbered + // substitutions. With relatively small changes to doPrintf we can + // have it optionally ignore extra arguments and pass the argument + // list in its entirety. + msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...) + frame := Frame{} + if internal.EnableTrace { + frame = Caller(1) + } + if wrap { + return &wrapError{msg, err, frame} + } + return &noWrapError{msg, err, frame} +} + +// formatPlusW is used to avoid the vet check that will barf at %w. +func formatPlusW(s string) string { + return s +} + +func lastError(format string, a []interface{}) (err error, wrap bool) { + wrap = strings.HasSuffix(format, ": %w") + if !wrap && + !strings.HasSuffix(format, ": %s") && + !strings.HasSuffix(format, ": %v") { + return nil, false + } + + if len(a) == 0 { + return nil, false + } + + err, ok := a[len(a)-1].(error) + if !ok { + return nil, false + } + + return err, wrap +} + +type noWrapError struct { + msg string + err error + frame Frame +} + +func (e *noWrapError) Error() string { + return fmt.Sprint(e) +} + +func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } + +func (e *noWrapError) FormatError(p Printer) (next error) { + p.Print(e.msg) + e.frame.Format(p) + return e.err +} + +type wrapError struct { + msg string + err error + frame Frame +} + +func (e *wrapError) Error() string { + return fmt.Sprint(e) +} + +func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } + +func (e *wrapError) FormatError(p Printer) (next error) { + p.Print(e.msg) + e.frame.Format(p) + return e.err +} + +func (e *wrapError) Unwrap() error { + return e.err +} diff --git a/vendor/golang.org/x/xerrors/format.go b/vendor/golang.org/x/xerrors/format.go new file mode 100644 index 00000000..1bc9c26b --- /dev/null +++ b/vendor/golang.org/x/xerrors/format.go @@ -0,0 +1,34 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +// A Formatter formats error messages. +type Formatter interface { + error + + // FormatError prints the receiver's first error and returns the next error in + // the error chain, if any. + FormatError(p Printer) (next error) +} + +// A Printer formats error messages. +// +// The most common implementation of Printer is the one provided by package fmt +// during Printf (as of Go 1.13). Localization packages such as golang.org/x/text/message +// typically provide their own implementations. +type Printer interface { + // Print appends args to the message output. + Print(args ...interface{}) + + // Printf writes a formatted string. + Printf(format string, args ...interface{}) + + // Detail reports whether error detail is requested. + // After the first call to Detail, all text written to the Printer + // is formatted as additional detail, or ignored when + // detail has not been requested. + // If Detail returns false, the caller can avoid printing the detail at all. + Detail() bool +} diff --git a/vendor/golang.org/x/xerrors/frame.go b/vendor/golang.org/x/xerrors/frame.go new file mode 100644 index 00000000..0de628ec --- /dev/null +++ b/vendor/golang.org/x/xerrors/frame.go @@ -0,0 +1,56 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "runtime" +) + +// A Frame contains part of a call stack. +type Frame struct { + // Make room for three PCs: the one we were asked for, what it called, + // and possibly a PC for skipPleaseUseCallersFrames. See: + // https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169 + frames [3]uintptr +} + +// Caller returns a Frame that describes a frame on the caller's stack. +// The argument skip is the number of frames to skip over. +// Caller(0) returns the frame for the caller of Caller. +func Caller(skip int) Frame { + var s Frame + runtime.Callers(skip+1, s.frames[:]) + return s +} + +// location reports the file, line, and function of a frame. +// +// The returned function may be "" even if file and line are not. +func (f Frame) location() (function, file string, line int) { + frames := runtime.CallersFrames(f.frames[:]) + if _, ok := frames.Next(); !ok { + return "", "", 0 + } + fr, ok := frames.Next() + if !ok { + return "", "", 0 + } + return fr.Function, fr.File, fr.Line +} + +// Format prints the stack as error detail. +// It should be called from an error's Format implementation +// after printing any other error detail. +func (f Frame) Format(p Printer) { + if p.Detail() { + function, file, line := f.location() + if function != "" { + p.Printf("%s\n ", function) + } + if file != "" { + p.Printf("%s:%d\n", file, line) + } + } +} diff --git a/vendor/golang.org/x/xerrors/go.mod b/vendor/golang.org/x/xerrors/go.mod new file mode 100644 index 00000000..870d4f61 --- /dev/null +++ b/vendor/golang.org/x/xerrors/go.mod @@ -0,0 +1,3 @@ +module golang.org/x/xerrors + +go 1.11 diff --git a/vendor/golang.org/x/xerrors/internal/internal.go b/vendor/golang.org/x/xerrors/internal/internal.go new file mode 100644 index 00000000..89f4eca5 --- /dev/null +++ b/vendor/golang.org/x/xerrors/internal/internal.go @@ -0,0 +1,8 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package internal + +// EnableTrace indicates whether stack information should be recorded in errors. +var EnableTrace = true diff --git a/vendor/golang.org/x/xerrors/wrap.go b/vendor/golang.org/x/xerrors/wrap.go new file mode 100644 index 00000000..9a3b5103 --- /dev/null +++ b/vendor/golang.org/x/xerrors/wrap.go @@ -0,0 +1,106 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "reflect" +) + +// A Wrapper provides context around another error. +type Wrapper interface { + // Unwrap returns the next error in the error chain. + // If there is no next error, Unwrap returns nil. + Unwrap() error +} + +// Opaque returns an error with the same error formatting as err +// but that does not match err and cannot be unwrapped. +func Opaque(err error) error { + return noWrapper{err} +} + +type noWrapper struct { + error +} + +func (e noWrapper) FormatError(p Printer) (next error) { + if f, ok := e.error.(Formatter); ok { + return f.FormatError(p) + } + p.Print(e.error) + return nil +} + +// Unwrap returns the result of calling the Unwrap method on err, if err implements +// Unwrap. Otherwise, Unwrap returns nil. +func Unwrap(err error) error { + u, ok := err.(Wrapper) + if !ok { + return nil + } + return u.Unwrap() +} + +// Is reports whether any error in err's chain matches target. +// +// An error is considered to match a target if it is equal to that target or if +// it implements a method Is(error) bool such that Is(target) returns true. +func Is(err, target error) bool { + if target == nil { + return err == target + } + + isComparable := reflect.TypeOf(target).Comparable() + for { + if isComparable && err == target { + return true + } + if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { + return true + } + // TODO: consider supporing target.Is(err). This would allow + // user-definable predicates, but also may allow for coping with sloppy + // APIs, thereby making it easier to get away with them. + if err = Unwrap(err); err == nil { + return false + } + } +} + +// As finds the first error in err's chain that matches the type to which target +// points, and if so, sets the target to its value and returns true. An error +// matches a type if it is assignable to the target type, or if it has a method +// As(interface{}) bool such that As(target) returns true. As will panic if target +// is not a non-nil pointer to a type which implements error or is of interface type. +// +// The As method should set the target to its value and return true if err +// matches the type to which target points. +func As(err error, target interface{}) bool { + if target == nil { + panic("errors: target cannot be nil") + } + val := reflect.ValueOf(target) + typ := val.Type() + if typ.Kind() != reflect.Ptr || val.IsNil() { + panic("errors: target must be a non-nil pointer") + } + if e := typ.Elem(); e.Kind() != reflect.Interface && !e.Implements(errorType) { + panic("errors: *target must be interface or implement error") + } + targetType := typ.Elem() + for err != nil { + if reflect.TypeOf(err).AssignableTo(targetType) { + val.Elem().Set(reflect.ValueOf(err)) + return true + } + if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) { + return true + } + err = Unwrap(err) + } + return false +} + +var errorType = reflect.TypeOf((*error)(nil)).Elem() diff --git a/vendor/modules.txt b/vendor/modules.txt index fc7ed7b3..9e0ceb04 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -285,14 +285,18 @@ golang.org/x/text/runes golang.org/x/text/internal/language golang.org/x/text/internal/language/compact golang.org/x/text/internal/tag +# golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 +golang.org/x/xerrors +golang.org/x/xerrors/internal # gopkg.in/fsnotify.v1 v1.4.7 gopkg.in/fsnotify.v1 # gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 gopkg.in/tomb.v1 # gopkg.in/yaml.v2 v2.2.5 gopkg.in/yaml.v2 -# mvdan.cc/sh v2.6.4+incompatible -mvdan.cc/sh/expand -mvdan.cc/sh/shell -mvdan.cc/sh/syntax -mvdan.cc/sh/interp +# mvdan.cc/sh/v3 v3.0.0-beta1 +mvdan.cc/sh/v3/expand +mvdan.cc/sh/v3/shell +mvdan.cc/sh/v3/syntax +mvdan.cc/sh/v3/pattern +mvdan.cc/sh/v3/interp diff --git a/vendor/mvdan.cc/sh/expand/braces.go b/vendor/mvdan.cc/sh/expand/braces.go deleted file mode 100644 index d8010da5..00000000 --- a/vendor/mvdan.cc/sh/expand/braces.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2018, Daniel Martí -// 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 -} diff --git a/vendor/mvdan.cc/sh/interp/module.go b/vendor/mvdan.cc/sh/interp/module.go deleted file mode 100644 index 9e288bb5..00000000 --- a/vendor/mvdan.cc/sh/interp/module.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) 2017, Daniel Martí -// 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 } diff --git a/vendor/mvdan.cc/sh/syntax/expand.go b/vendor/mvdan.cc/sh/syntax/expand.go deleted file mode 100644 index 9316dce9..00000000 --- a/vendor/mvdan.cc/sh/syntax/expand.go +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright (c) 2018, Daniel Martí -// 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) -} diff --git a/vendor/mvdan.cc/sh/syntax/pattern.go b/vendor/mvdan.cc/sh/syntax/pattern.go deleted file mode 100644 index 4fb0844a..00000000 --- a/vendor/mvdan.cc/sh/syntax/pattern.go +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) 2017, Daniel Martí -// 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() -} diff --git a/vendor/mvdan.cc/sh/syntax/token_string.go b/vendor/mvdan.cc/sh/syntax/token_string.go deleted file mode 100644 index 0327dbae..00000000 --- a/vendor/mvdan.cc/sh/syntax/token_string.go +++ /dev/null @@ -1,16 +0,0 @@ -// 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]] -} diff --git a/vendor/mvdan.cc/sh/syntax/tokens.go b/vendor/mvdan.cc/sh/syntax/tokens.go deleted file mode 100644 index aff34c48..00000000 --- a/vendor/mvdan.cc/sh/syntax/tokens.go +++ /dev/null @@ -1,346 +0,0 @@ -// Copyright (c) 2016, Daniel Martí -// 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() } diff --git a/vendor/mvdan.cc/sh/LICENSE b/vendor/mvdan.cc/sh/v3/LICENSE similarity index 100% rename from vendor/mvdan.cc/sh/LICENSE rename to vendor/mvdan.cc/sh/v3/LICENSE diff --git a/vendor/mvdan.cc/sh/expand/arith.go b/vendor/mvdan.cc/sh/v3/expand/arith.go similarity index 90% rename from vendor/mvdan.cc/sh/expand/arith.go rename to vendor/mvdan.cc/sh/v3/expand/arith.go index 84ce5547..1e48a709 100644 --- a/vendor/mvdan.cc/sh/expand/arith.go +++ b/vendor/mvdan.cc/sh/v3/expand/arith.go @@ -7,7 +7,7 @@ import ( "fmt" "strconv" - "mvdan.cc/sh/syntax" + "mvdan.cc/sh/v3/syntax" ) func Arithm(cfg *Config, expr syntax.ArithmExpr) (int, error) { @@ -19,7 +19,7 @@ func Arithm(cfg *Config, expr syntax.ArithmExpr) (int, error) { } // recursively fetch vars i := 0 - for str != "" && syntax.ValidName(str) { + for syntax.ValidName(str) { val := cfg.envGet(str) if val == "" { break @@ -44,7 +44,9 @@ func Arithm(cfg *Config, expr syntax.ArithmExpr) (int, error) { } else { val-- } - cfg.envSet(name, strconv.Itoa(val)) + if err := cfg.envSet(name, strconv.Itoa(val)); err != nil { + return 0, err + } if x.Post { return old, nil } @@ -57,6 +59,8 @@ func Arithm(cfg *Config, expr syntax.ArithmExpr) (int, error) { switch x.Op { case syntax.Not: return oneIf(val == 0), nil + case syntax.BitNegation: + return ^val, nil case syntax.Plus: return val, nil default: // syntax.Minus @@ -69,12 +73,12 @@ func Arithm(cfg *Config, expr syntax.ArithmExpr) (int, error) { syntax.AndAssgn, syntax.OrAssgn, syntax.XorAssgn, syntax.ShlAssgn, syntax.ShrAssgn: return cfg.assgnArit(x) - case syntax.Quest: // Colon can't happen here + case syntax.TernQuest: // TernColon 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 + b2 := x.Y.(*syntax.BinaryArithm) // must have Op==TernColon if cond == 1 { return Arithm(cfg, b2.X) } @@ -139,7 +143,9 @@ func (cfg *Config) assgnArit(b *syntax.BinaryArithm) (int, error) { case syntax.ShrAssgn: val >>= uint(arg) } - cfg.envSet(name, strconv.Itoa(val)) + if err := cfg.envSet(name, strconv.Itoa(val)); err != nil { + return 0, err + } return val, nil } diff --git a/vendor/mvdan.cc/sh/v3/expand/braces.go b/vendor/mvdan.cc/sh/v3/expand/braces.go new file mode 100644 index 00000000..482f8116 --- /dev/null +++ b/vendor/mvdan.cc/sh/v3/expand/braces.go @@ -0,0 +1,85 @@ +// Copyright (c) 2018, Daniel Martí +// See LICENSE for licensing information + +package expand + +import ( + "strconv" + + "mvdan.cc/sh/v3/syntax" +) + +// Braces performs brace expansion on a word, given that it contains any +// syntax.BraceExp parts. For example, the word with a brace expansion +// "foo{bar,baz}" will return two literal words, "foobar" and "foobaz". +// +// Note that the resulting words may share word parts. +func Braces(word *syntax.Word) []*syntax.Word { + var all []*syntax.Word + var left []syntax.WordPart + for i, wp := range word.Parts { + br, ok := wp.(*syntax.BraceExp) + if !ok { + left = append(left, wp.(syntax.WordPart)) + continue + } + if br.Sequence { + var from, to int + if br.Chars { + from = int(br.Elems[0].Lit()[0]) + to = int(br.Elems[1].Lit()[0]) + } else { + from, _ = strconv.Atoi(br.Elems[0].Lit()) + to, _ = strconv.Atoi(br.Elems[1].Lit()) + } + upward := from <= to + incr := 1 + if !upward { + incr = -1 + } + if len(br.Elems) > 2 { + n, _ := strconv.Atoi(br.Elems[2].Lit()) + if n != 0 && n > 0 == upward { + incr = n + } + } + n := from + for { + if upward && n > to { + break + } + if !upward && n < to { + break + } + next := *word + next.Parts = next.Parts[i+1:] + lit := &syntax.Lit{} + if br.Chars { + lit.Value = string(n) + } else { + lit.Value = strconv.Itoa(n) + } + next.Parts = append([]syntax.WordPart{lit}, next.Parts...) + exp := Braces(&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 := *word + next.Parts = next.Parts[i+1:] + next.Parts = append(elem.Parts, next.Parts...) + exp := Braces(&next) + for _, w := range exp { + w.Parts = append(left, w.Parts...) + } + all = append(all, exp...) + } + return all + } + return []*syntax.Word{{Parts: left}} +} diff --git a/vendor/mvdan.cc/sh/expand/doc.go b/vendor/mvdan.cc/sh/v3/expand/doc.go similarity index 100% rename from vendor/mvdan.cc/sh/expand/doc.go rename to vendor/mvdan.cc/sh/v3/expand/doc.go diff --git a/vendor/mvdan.cc/sh/expand/environ.go b/vendor/mvdan.cc/sh/v3/expand/environ.go similarity index 79% rename from vendor/mvdan.cc/sh/expand/environ.go rename to vendor/mvdan.cc/sh/v3/expand/environ.go index ebd90b7c..7b89c3d2 100644 --- a/vendor/mvdan.cc/sh/expand/environ.go +++ b/vendor/mvdan.cc/sh/v3/expand/environ.go @@ -40,15 +40,27 @@ type WriteEnviron interface { // 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) + // + // An error may be returned if the operation is invalid, such as if the + // name is empty or if we're trying to overwrite a read-only variable. + Set(name string, vr Variable) error } +type ValueKind uint8 + +const ( + Unset ValueKind = iota + String + NameRef + Indexed + Associative +) + // 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. +// A Variable is unset if its Kind field is Unset, 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 @@ -57,27 +69,31 @@ 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 + + Kind ValueKind + + Str string // Used when Kind is String or NameRef. + List []string // Used when Kind is Indexed. + Map map[string]string // Used when Kind is Associative. } // 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 + return v.Kind != Unset } // 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] + switch v.Kind { + case String: + return v.Str + case Indexed: + if len(v.List) > 0 { + return v.List[0] } - case map[string]string: + case Associative: // nothing to do } return "" @@ -93,10 +109,10 @@ const maxNameRefDepth = 100 func (v Variable) Resolve(env Environ) (string, Variable) { name := "" for i := 0; i < maxNameRefDepth; i++ { - if !v.NameRef { + if v.Kind != NameRef { return name, v } - name = v.Value.(string) + name = v.Str // keep name for the next iteration v = env.Get(name) } return name, Variable{} @@ -118,7 +134,7 @@ func (f funcEnviron) Get(name string) Variable { if value == "" { return Variable{} } - return Variable{Exported: true, Value: value} + return Variable{Exported: true, Kind: String, Str: value} } func (f funcEnviron) Each(func(name string, vr Variable) bool) {} @@ -167,15 +183,14 @@ func listEnvironWithUpper(upper bool, pairs ...string) Environ { return listEnviron(list) } +// listEnviron is a sorted list of "name=value" strings. 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} - } + i := sort.SearchStrings(l, prefix) + if i < len(l) && strings.HasPrefix(l[i], prefix) { + return Variable{Exported: true, Kind: String, Str: strings.TrimPrefix(l[i], prefix)} } return Variable{} } @@ -184,11 +199,11 @@ 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 + // should never happen; see listEnvironWithUpper 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}) { + if !fn(name, Variable{Exported: true, Kind: String, Str: value}) { return } } diff --git a/vendor/mvdan.cc/sh/expand/expand.go b/vendor/mvdan.cc/sh/v3/expand/expand.go similarity index 77% rename from vendor/mvdan.cc/sh/expand/expand.go rename to vendor/mvdan.cc/sh/v3/expand/expand.go index 16f63d45..0186a8fc 100644 --- a/vendor/mvdan.cc/sh/expand/expand.go +++ b/vendor/mvdan.cc/sh/v3/expand/expand.go @@ -15,7 +15,8 @@ import ( "strconv" "strings" - "mvdan.cc/sh/syntax" + "mvdan.cc/sh/v3/pattern" + "mvdan.cc/sh/v3/syntax" ) // A Config specifies details about how shell expansion should be performed. The @@ -35,14 +36,6 @@ type Config struct { // 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. // @@ -54,6 +47,10 @@ type Config struct { // Use ioutil.ReadDir to use the filesystem directly. ReadDir func(string) ([]os.FileInfo, error) + // GlobStar corresponds to the shell option that allows globbing with + // "**". + GlobStar bool + bufferAlloc bytes.Buffer fieldAlloc [4]fieldPart fieldsAlloc [4][]fieldPart @@ -118,13 +115,12 @@ func (cfg *Config) envGet(name string) string { return cfg.Env.Get(name).String() } -func (cfg *Config) envSet(name, value string) { +func (cfg *Config) envSet(name, value string) error { wenv, ok := cfg.Env.(WriteEnviron) if !ok { - // TODO: we should probably error here - return + return fmt.Errorf("environment is read-only") } - wenv.Set(name, Variable{Value: value}) + return wenv.Set(name, Variable{Kind: String, Str: value}) } // Literal expands a single shell word. It is similar to Fields, but the result @@ -178,7 +174,7 @@ func Pattern(cfg *Config, word *syntax.Word) (string, error) { buf := cfg.strBuilder() for _, part := range field { if part.quote > quoteNone { - buf.WriteString(syntax.QuotePattern(part.val)) + buf.WriteString(pattern.QuoteMeta(part.val)) } else { buf.WriteString(part.val) } @@ -196,28 +192,81 @@ func Pattern(cfg *Config, word *syntax.Word) (string, error) { func Format(cfg *Config, format string, args []string) (string, int, error) { cfg = prepareConfig(cfg) buf := cfg.strBuilder() - esc := false - var fmts []rune + var fmts []byte 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) + for i := 0; i < len(format); i++ { + // readDigits reads from 0 to max digits, either octal or + // hexadecimal. + readDigits := func(max int, hex bool) string { + j := 0 + for ; j < max; j++ { + c := format[i+j] + if (c >= '0' && c <= '9') || + (hex && c >= 'a' && c <= 'f') || + (hex && c >= 'A' && c <= 'F') { + // valid octal or hex char + } else { + break + } + } + digits := format[i : i+j] + i += j - 1 // -1 since the outer loop does i++ + return digits + } + c := format[i] + switch { + case c == '\\': // escaped + i++ + switch c = format[i]; c { + case 'a': // bell + buf.WriteByte('\a') + case 'b': // backspace + buf.WriteByte('\b') + case 'e', 'E': // escape + buf.WriteByte('\x1b') + case 'f': // form feed + buf.WriteByte('\f') + case 'n': // new line + buf.WriteByte('\n') + case 'r': // carriage return + buf.WriteByte('\r') + case 't': // horizontal tab + buf.WriteByte('\t') + case 'v': // vertical tab + buf.WriteByte('\v') + case '\\', '\'', '"', '?': // just the character + buf.WriteByte(c) + case '0', '1', '2', '3', '4', '5', '6', '7': + digits := readDigits(3, false) + // if digits don't fit in 8 bits, 0xff via strconv + n, _ := strconv.ParseUint(digits, 8, 8) + buf.WriteByte(byte(n)) + case 'x', 'u', 'U': + i++ + max := 2 + if c == 'u' { + max = 4 + } else if c == 'U' { + max = 8 + } + digits := readDigits(max, true) + if len(digits) > 0 { + // can't error + n, _ := strconv.ParseUint(digits, 16, 32) + if c == 'x' { + // always as a single byte + buf.WriteByte(byte(n)) + } else { + buf.WriteRune(rune(n)) + } + break + } + fallthrough + default: // no escape sequence + buf.WriteByte('\\') + buf.WriteByte(c) } - case len(fmts) > 0: switch c { case '%': @@ -264,14 +313,12 @@ func Format(cfg *Config, format string, args []string) (string, int, error) { 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} + fmts = []byte{c} default: - buf.WriteRune(c) + buf.WriteByte(c) } } if len(fmts) > 0 { @@ -298,11 +345,11 @@ func (cfg *Config) escapedGlobField(parts []fieldPart) (escaped string, glob boo buf := cfg.strBuilder() for _, part := range parts { if part.quote > quoteNone { - buf.WriteString(syntax.QuotePattern(part.val)) + buf.WriteString(pattern.QuoteMeta(part.val)) continue } buf.WriteString(part.val) - if syntax.HasPattern(part.val) { + if pattern.HasMeta(part.val) { glob = true } } @@ -319,34 +366,30 @@ 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 _, word := range words { + afterBraces := []*syntax.Word{word} + if syntax.SplitBraces(word) { + afterBraces = Braces(word) } - 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 - } + for _, word2 := range afterBraces { + wfields, err := cfg.wordFields(word2.Parts) + if err != nil { + return nil, err } - if len(matches) == 0 { + for _, field := range wfields { + path, doGlob := cfg.escapedGlobField(field) + var matches []string + if doGlob && cfg.ReadDir != nil { + matches, err = cfg.glob(dir, path) + if err != nil { + return nil, err + } + if len(matches) > 0 { + fields = append(fields, matches...) + continue + } + } fields = append(fields, cfg.fieldJoin(field)) - continue - } - for _, match := range matches { - if !abs { - match = strings.TrimPrefix(match, dir) - } - fields = append(fields, match) } } } @@ -385,9 +428,6 @@ func (cfg *Config) wordField(wps []syntax.WordPart, ql quoteLevel) ([]fieldPart, b := s[i] if b == '\\' && i+1 < len(s) { switch s[i+1] { - case '\n': // remove \\\n - i++ - continue case '"', '\\', '$', '`': // special chars continue } @@ -559,14 +599,14 @@ func (cfg *Config) quotedElems(pe *syntax.ParamExp) []string { return nil } if pe.Param.Value == "@" { - return cfg.Env.Get("@").Value.([]string) + return cfg.Env.Get("@").List } if nodeLit(pe.Index) != "@" { return nil } - val := cfg.Env.Get(pe.Param.Value).Value - if x, ok := val.([]string); ok { - return x + vr := cfg.Env.Get(pe.Param.Value) + if vr.Kind == Indexed { + return vr.List } return nil } @@ -581,8 +621,26 @@ func (cfg *Config) expandUser(field string) (prefix, rest string) { name = name[:i] } if name == "" { - return cfg.Env.Get("HOME").String(), rest + // Current user; try via "HOME", otherwise fall back to the + // system's appropriate home dir env var. Don't use os/user, as + // that's overkill. We can't use os.UserHomeDir, because we want + // to use cfg.Env, and we always want to check "HOME" first. + + if vr := cfg.Env.Get("HOME"); vr.IsSet() { + return vr.String(), rest + } + + if runtime.GOOS == "windows" { + if vr := cfg.Env.Get("USERPROFILE"); vr.IsSet() { + return vr.String(), rest + } + } + return "", field } + + // Not the current user; try via "HOME ", otherwise fall back to + // os/user. There isn't a way to lookup user home dirs without cgo. + if vr := cfg.Env.Get("HOME " + name); vr.IsSet() { return vr.String(), rest } @@ -594,8 +652,8 @@ func (cfg *Config) expandUser(field string) (prefix, rest string) { return u.HomeDir, rest } -func findAllIndex(pattern, name string, n int) [][]int { - expr, err := syntax.TranslatePattern(pattern, true) +func findAllIndex(pat, name string, n int) [][]int { + expr, err := pattern.Regexp(pat, 0) if err != nil { return nil } @@ -603,16 +661,6 @@ func findAllIndex(pattern, name string, n int) [][]int { 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, @@ -635,10 +683,10 @@ func pathSplit(path string) []string { return strings.Split(path, string(filepath.Separator)) } -func (cfg *Config) glob(base, pattern string) ([]string, error) { - parts := pathSplit(pattern) +func (cfg *Config) glob(base, pat string) ([]string, error) { + parts := pathSplit(pat) matches := []string{""} - if filepath.IsAbs(pattern) { + if filepath.IsAbs(pat) { if parts[0] == "" { // unix-like matches[0] = string(filepath.Separator) @@ -649,20 +697,13 @@ func (cfg *Config) glob(base, pattern string) ([]string, error) { } parts = parts[1:] } - for _, part := range parts { + for i, part := range parts { + wantDir := i < len(parts)-1 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)) + for i, dir := range matches { + matches[i] = pathJoin2(dir, part) } - matches = newMatches continue case part == "**" && cfg.GlobStar: for i, match := range matches { @@ -677,7 +718,7 @@ func (cfg *Config) glob(base, pattern string) ([]string, error) { var newMatches []string for _, dir := range latest { var err error - newMatches, err = cfg.globDir(base, dir, rxGlobStar, newMatches) + newMatches, err = cfg.globDir(base, dir, rxGlobStar, wantDir, newMatches) if err != nil { return nil, err } @@ -692,7 +733,7 @@ func (cfg *Config) glob(base, pattern string) ([]string, error) { } continue } - expr, err := syntax.TranslatePattern(part, true) + expr, err := pattern.Regexp(part, pattern.Filenames) if err != nil { // If any glob part is not a valid pattern, don't glob. return nil, nil @@ -700,7 +741,7 @@ func (cfg *Config) glob(base, pattern string) ([]string, error) { rx := regexp.MustCompile("^" + expr + "$") var newMatches []string for _, dir := range matches { - newMatches, err = cfg.globDir(base, dir, rx, newMatches) + newMatches, err = cfg.globDir(base, dir, rx, wantDir, newMatches) if err != nil { return nil, err } @@ -710,24 +751,30 @@ func (cfg *Config) glob(base, pattern string) ([]string, error) { 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 +func (cfg *Config) globDir(base, dir string, rx *regexp.Regexp, wantDir bool, matches []string) ([]string, error) { + fullDir := dir + if !filepath.IsAbs(dir) { + fullDir = filepath.Join(base, dir) } - infos, err := cfg.ReadDir(filepath.Join(base, dir)) + infos, err := cfg.ReadDir(fullDir) 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 + return nil, err } for _, info := range infos { name := info.Name() + if !wantDir { + // no filtering + } else if mode := info.Mode(); mode&os.ModeSymlink != 0 { + // TODO: is there a way to do this without the + // extra syscall? + if _, err := cfg.ReadDir(filepath.Join(fullDir, name)); err != nil { + // symlink pointing to non-directory + continue + } + } else if !mode.IsDir() { + // definitely not a directory + continue + } if !strings.HasPrefix(rx.String(), `^\.`) && name[0] == '.' { continue } @@ -738,6 +785,7 @@ func (cfg *Config) globDir(base, dir string, rx *regexp.Regexp, matches []string return matches, nil } +// ReadFields TODO write doc. // // The config specifies shell expansion options; nil behaves the same as an // empty config. @@ -791,7 +839,7 @@ func ReadFields(cfg *Config, s string, n int, raw bool) []string { fpos = fpos[:n] } - var fields = make([]string, len(fpos)) + fields := make([]string, len(fpos)) for i, p := range fpos { fields[i] = string(runes[p.start:p.end]) } diff --git a/vendor/mvdan.cc/sh/expand/param.go b/vendor/mvdan.cc/sh/v3/expand/param.go similarity index 76% rename from vendor/mvdan.cc/sh/expand/param.go rename to vendor/mvdan.cc/sh/v3/expand/param.go index 69e33aea..e077c154 100644 --- a/vendor/mvdan.cc/sh/expand/param.go +++ b/vendor/mvdan.cc/sh/v3/expand/param.go @@ -12,7 +12,8 @@ import ( "unicode" "unicode/utf8" - "mvdan.cc/sh/syntax" + "mvdan.cc/sh/v3/pattern" + "mvdan.cc/sh/v3/syntax" ) func nodeLit(node syntax.Node) string { @@ -50,7 +51,7 @@ func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) { // This is the only parameter expansion that the environment // interface cannot satisfy. line := uint64(cfg.curParam.Pos().Line()) - vr.Value = strconv.FormatUint(line, 10) + vr = Variable{Kind: String, Str: strconv.FormatUint(line, 10)} default: vr = cfg.Env.Get(name) } @@ -74,11 +75,11 @@ func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) { elems := []string{str} switch nodeLit(index) { case "@", "*": - switch x := vr.Value.(type) { - case nil: + switch vr.Kind { + case Unset: elems = nil - case []string: - elems = x + case Indexed: + elems = vr.List } } switch { @@ -92,21 +93,24 @@ func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) { str = strconv.Itoa(n) case pe.Excl: var strs []string - if pe.Names != 0 { + switch { + case 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 { + case orig.Kind == NameRef: + strs = append(strs, orig.Str) + case vr.Kind == Indexed: + for i, e := range vr.List { if e != "" { strs = append(strs, strconv.Itoa(i)) } } - } else if x, ok := vr.Value.(map[string]string); ok { - for k := range x { + case vr.Kind == Associative: + for k := range vr.Map { strs = append(strs, k) } - } else if str != "" { + case !syntax.ValidName(str): + return "", fmt.Errorf("invalid indirect expansion") + default: vr = cfg.Env.Get(str) strs = append(strs, vr.String()) } @@ -156,54 +160,54 @@ func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) { return "", err } switch op := pe.Exp.Op; op { - case syntax.SubstColPlus: + case syntax.AlternateUnsetOrNull: if str == "" { break } fallthrough - case syntax.SubstPlus: + case syntax.AlternateUnset: if vr.IsSet() { str = arg } - case syntax.SubstMinus: + case syntax.DefaultUnset: if vr.IsSet() { break } fallthrough - case syntax.SubstColMinus: + case syntax.DefaultUnsetOrNull: if str == "" { str = arg } - case syntax.SubstQuest: + case syntax.ErrorUnset: if vr.IsSet() { break } fallthrough - case syntax.SubstColQuest: + case syntax.ErrorUnsetOrNull: if str == "" { return "", UnsetParameterError{ Node: pe, Message: arg, } } - case syntax.SubstAssgn: + case syntax.AssignUnset: if vr.IsSet() { break } fallthrough - case syntax.SubstColAssgn: + case syntax.AssignUnsetOrNull: if str == "" { - cfg.envSet(name, arg) + if err := cfg.envSet(name, arg); err != nil { + return "", err + } 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 + suffix := op == syntax.RemSmallSuffix || op == syntax.RemLargeSuffix + small := op == syntax.RemSmallPrefix || op == syntax.RemSmallSuffix for i, elem := range elems { - elems[i] = removePattern(elem, arg, suffix, large) + elems[i] = removePattern(elem, arg, suffix, small) } str = strings.Join(elems, " ") case syntax.UpperFirst, syntax.UpperAll, @@ -216,7 +220,7 @@ func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) { all := op == syntax.UpperAll || op == syntax.LowerAll // empty string means '?'; nothing to do there - expr, err := syntax.TranslatePattern(arg, false) + expr, err := pattern.Regexp(arg, 0) if err != nil { return str, nil } @@ -258,14 +262,18 @@ func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) { return str, nil } -func removePattern(str, pattern string, fromEnd, greedy bool) string { - expr, err := syntax.TranslatePattern(pattern, greedy) +func removePattern(str, pat string, fromEnd, shortest bool) string { + var mode pattern.Mode + if shortest { + mode |= pattern.Shortest + } + expr, err := pattern.Regexp(pat, mode) if err != nil { return str } switch { - case fromEnd && !greedy: - // use .* to get the right-most (shortest) match + case fromEnd && shortest: + // use .* to get the right-most shortest match expr = ".*(" + expr + ")$" case fromEnd: // simple suffix @@ -274,7 +282,7 @@ func removePattern(str, pattern string, fromEnd, greedy bool) string { // simple prefix expr = "^(" + expr + ")" } - // no need to check error as TranslatePattern returns one + // no need to check error as Translate returns one rx := regexp.MustCompile(expr) if loc := rx.FindStringSubmatchIndex(str); loc != nil { // remove the original pattern (the submatch) @@ -287,41 +295,37 @@ 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: + switch vr.Kind { + case String: n, err := Arithm(cfg, idx) if err != nil { return "", err } if n == 0 { - return x, nil + return vr.Str, nil } - case []string: + case Indexed: switch nodeLit(idx) { case "@": - return strings.Join(x, " "), nil + return strings.Join(vr.List, " "), nil case "*": - return cfg.ifsJoin(x), nil + return cfg.ifsJoin(vr.List), nil } i, err := Arithm(cfg, idx) if err != nil { return "", err } - if len(x) > 0 { - return x[i], nil + if len(vr.List) > 0 { + return vr.List[i], nil } - case map[string]string: + case Associative: 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]) + strs := make([]string, 0, len(vr.Map)) + for _, val := range vr.Map { + strs = append(strs, val) } + sort.Strings(strs) if lit == "*" { return cfg.ifsJoin(strs), nil } @@ -331,7 +335,7 @@ func (cfg *Config) varInd(vr Variable, idx syntax.ArithmExpr) (string, error) { if err != nil { return "", err } - return x[val], nil + return vr.Map[val], nil } return "", nil } diff --git a/vendor/mvdan.cc/sh/interp/builtin.go b/vendor/mvdan.cc/sh/v3/interp/builtin.go similarity index 94% rename from vendor/mvdan.cc/sh/interp/builtin.go rename to vendor/mvdan.cc/sh/v3/interp/builtin.go index f36bfd04..491f81fe 100644 --- a/vendor/mvdan.cc/sh/interp/builtin.go +++ b/vendor/mvdan.cc/sh/v3/interp/builtin.go @@ -13,8 +13,8 @@ import ( "strconv" "strings" - "mvdan.cc/sh/expand" - "mvdan.cc/sh/syntax" + "mvdan.cc/sh/v3/expand" + "mvdan.cc/sh/v3/syntax" ) func isBuiltin(name string) bool { @@ -50,21 +50,21 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a case "false": return 1 case "exit": + r.exitShell = true switch len(args) { case 0: + return r.exit case 1: - if n, err := strconv.Atoi(args[0]); err != nil { + n, err := strconv.Atoi(args[0]) + if err != nil { r.errf("invalid exit status code: %q\n", args[0]) - r.exit = 2 - } else { - r.exit = n + return 2 } + return n default: r.errf("exit cannot take multiple arguments\n") - r.exit = 1 + return 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) @@ -200,11 +200,8 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a if len(args) > 0 { panic("wait with args not handled yet") } - switch err := r.bgShells.Wait().(type) { - case nil: - case ExitStatus: - case ShellExitStatus: - default: + err := r.bgShells.Wait() + if _, ok := IsExitStatus(err); err != nil && !ok { r.setErr(err) } case "builtin": @@ -244,14 +241,14 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a r.errf("eval: %v\n", err) return 1 } - r.stmts(ctx, file.StmtList) + r.stmts(ctx, file.Stmts) 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) + f, err := r.open(ctx, args[0], os.O_RDONLY, 0, false) if err != nil { r.errf("source: %v\n", err) return 1 @@ -267,13 +264,13 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a r.Params = args[1:] oldInSource := r.inSource r.inSource = true - r.stmts(ctx, file.StmtList) + r.stmts(ctx, file.Stmts) r.Params = oldParams r.inSource = oldInSource if code, ok := r.err.(returnStatus); ok { r.err = nil - r.exit = int(code) + return int(code) } return r.exit case "[": @@ -308,8 +305,8 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a break } r.exec(ctx, args) - r.setErr(ShellExitStatus(r.exit)) - return 0 + r.exitShell = true + return r.exit case "command": show := false for len(args) > 0 && strings.HasPrefix(args[0], "-") { @@ -470,7 +467,7 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a if i < len(values) { val = values[i] } - r.setVar(name, nil, expand.Variable{Value: val}) + r.setVar(name, nil, expand.Variable{Kind: expand.String, Str: val}) } return 0 @@ -584,7 +581,7 @@ func (r *Runner) readLine(raw bool) ([]byte, error) { for { var buf [1]byte - n, err := r.Stdin.Read(buf[:]) + n, err := r.stdin.Read(buf[:]) if n > 0 { b := buf[0] switch { @@ -612,7 +609,7 @@ func (r *Runner) readLine(raw bool) ([]byte, error) { } func (r *Runner) changeDir(path string) int { - path = r.relPath(path) + path = r.absPath(path) info, err := r.stat(path) if err != nil || !info.IsDir() { return 1 @@ -622,11 +619,11 @@ func (r *Runner) changeDir(path string) int { } r.Dir = path r.Vars["OLDPWD"] = r.Vars["PWD"] - r.Vars["PWD"] = expand.Variable{Value: path} + r.Vars["PWD"] = expand.Variable{Kind: expand.String, Str: path} return 0 } -func (r *Runner) relPath(path string) string { +func (r *Runner) absPath(path string) string { if !filepath.IsAbs(path) { path = filepath.Join(r.Dir, path) } diff --git a/vendor/mvdan.cc/sh/interp/doc.go b/vendor/mvdan.cc/sh/v3/interp/doc.go similarity index 100% rename from vendor/mvdan.cc/sh/interp/doc.go rename to vendor/mvdan.cc/sh/v3/interp/doc.go diff --git a/vendor/mvdan.cc/sh/v3/interp/handler.go b/vendor/mvdan.cc/sh/v3/interp/handler.go new file mode 100644 index 00000000..733156ad --- /dev/null +++ b/vendor/mvdan.cc/sh/v3/interp/handler.go @@ -0,0 +1,288 @@ +// Copyright (c) 2017, Daniel Martí +// See LICENSE for licensing information + +package interp + +import ( + "context" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "syscall" + "time" + + "mvdan.cc/sh/v3/expand" +) + +// HandlerCtx returns HandlerContext value stored in ctx. +// It panics if ctx has no HandlerContext stored. +func HandlerCtx(ctx context.Context) HandlerContext { + hc, ok := ctx.Value(handlerCtxKey{}).(HandlerContext) + if !ok { + panic("interp.HandlerCtx: no HandlerContext in ctx") + } + return hc +} + +type handlerCtxKey struct{} + +// HandlerContext is the data passed to all the handler functions via a context value. +// It contains some of the current state of the Runner. +type HandlerContext struct { + // Env is a read-only version of the interpreter's environment, + // including environment variables, global variables, and local function + // variables. + Env expand.Environ + + // Dir is the interpreter's current directory. + Dir string + + // Stdin is the interpreter's current standard input reader. + Stdin io.Reader + // Stdout is the interpreter's current standard output writer. + Stdout io.Writer + // Stderr is the interpreter's current standard error writer. + Stderr io.Writer +} + +// ExecHandlerFunc is a handler which executes simple command. It is +// called for all CallExpr nodes where the first argument is neither a +// declared function nor a builtin. +// +// Returning nil error sets commands exit status to 0. Other exit statuses +// can be set with NewExitStatus. Any other error will halt an interpreter. +type ExecHandlerFunc func(ctx context.Context, args []string) error + +// DefaultExecHandler returns an ExecHandlerFunc used by default. +// It finds binaries in PATH and executes them. +// When context is cancelled, interrupt signal is sent to running processes. +// KillTimeout is a duration to wait before sending kill signal. +// A negative value means that a kill signal will be sent immediately. +// On Windows, the kill signal is always sent immediately, +// because Go doesn't currently support sending Interrupt on Windows. +// Runner.New sets killTimeout to 2 seconds by default. +func DefaultExecHandler(killTimeout time.Duration) ExecHandlerFunc { + return func(ctx context.Context, args []string) error { + hc := HandlerCtx(ctx) + path, err := LookPath(hc.Env, args[0]) + if err != nil { + fmt.Fprintln(hc.Stderr, err) + return NewExitStatus(127) + } + cmd := exec.Cmd{ + Path: path, + Args: args, + Env: execEnv(hc.Env), + Dir: hc.Dir, + Stdin: hc.Stdin, + Stdout: hc.Stdout, + Stderr: hc.Stderr, + } + + err = cmd.Start() + if err == nil { + if done := ctx.Done(); done != nil { + go func() { + <-done + + if 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(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 NewExitStatus(uint8(status.ExitStatus())) + } + return NewExitStatus(1) + case *exec.Error: + // did not start + fmt.Fprintf(hc.Stderr, "%v\n", err) + return NewExitStatus(127) + default: + return err + } + } +} + +func checkStat(dir, file string) (string, error) { + if !filepath.IsAbs(file) { + file = filepath.Join(dir, file) + } + info, err := os.Stat(file) + if err != nil { + return "", err + } + m := info.Mode() + if m.IsDir() { + return "", fmt.Errorf("is a directory") + } + if runtime.GOOS != "windows" && m&0111 == 0 { + return "", fmt.Errorf("permission denied") + } + return file, nil +} + +func winHasExt(file string) bool { + i := strings.LastIndex(file, ".") + if i < 0 { + return false + } + return strings.LastIndexAny(file, `:\/`) < i +} + +func findExecutable(dir, file string, exts []string) (string, error) { + if len(exts) == 0 { + // non-windows + return checkStat(dir, file) + } + if winHasExt(file) { + if file, err := checkStat(dir, file); err == nil { + return file, nil + } + } + for _, e := range exts { + f := file + e + if f, err := checkStat(dir, f); err == nil { + return f, nil + } + } + return "", fmt.Errorf("not found") +} + +func driveLetter(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') +} + +// splitList is like filepath.SplitList, but always using the unix path +// list separator ':'. On Windows, it also makes sure not to split +// [A-Z]:[/\]. +func splitList(path string) []string { + if path == "" { + return []string{""} + } + list := strings.Split(path, ":") + if runtime.GOOS != "windows" { + return list + } + // join "C", "/foo" into "C:/foo" + var fixed []string + for i := 0; i < len(list); i++ { + s := list[i] + switch { + case len(s) != 1, !driveLetter(s[0]): + case i+1 >= len(list): + // last element + case strings.IndexAny(list[i+1], `/\`) != 0: + // next element doesn't start with / or \ + default: + fixed = append(fixed, s+":"+list[i+1]) + i++ + continue + } + fixed = append(fixed, s) + } + return fixed +} + +// LookPath is similar to os/exec.LookPath, with the difference that it uses the +// provided environment. env is used to fetch relevant environment variables +// such as PWD and PATH. +// +// If no error is returned, the returned path must be valid. +func LookPath(env expand.Environ, file string) (string, error) { + pathList := splitList(env.Get("PATH").String()) + chars := `/` + if runtime.GOOS == "windows" { + chars = `:\/` + // so that "foo" always tries "./foo" + pathList = append([]string{"."}, pathList...) + } + exts := pathExts(env) + dir := env.Get("PWD").String() + if strings.ContainsAny(file, chars) { + return findExecutable(dir, file, exts) + } + for _, elem := range pathList { + var path string + switch elem { + case "", ".": + // otherwise "foo" won't be "./foo" + path = "." + string(filepath.Separator) + file + default: + path = filepath.Join(elem, file) + } + if f, err := findExecutable(dir, path, exts); err == nil { + return f, nil + } + } + return "", fmt.Errorf("%q: executable file not found in $PATH", file) +} + +func pathExts(env expand.Environ) []string { + if runtime.GOOS != "windows" { + return nil + } + pathext := env.Get("PATHEXT").String() + if pathext == "" { + return []string{".com", ".exe", ".bat", ".cmd"} + } + var exts []string + for _, e := range strings.Split(strings.ToLower(pathext), `;`) { + if e == "" { + continue + } + if e[0] != '.' { + e = "." + e + } + exts = append(exts, e) + } + return exts +} + +// OpenHandlerFunc is a handler which opens files. It is +// called 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 may be relative to the current directory, which can be +// fetched via HandlerCtx. +// +// 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. +type OpenHandlerFunc func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) + +// DefaultOpenHandler returns an OpenHandlerFunc used by default. It uses os.OpenFile to open files. +func DefaultOpenHandler() OpenHandlerFunc { + return func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) { + mc := HandlerCtx(ctx) + if !filepath.IsAbs(path) { + path = filepath.Join(mc.Dir, path) + } + return os.OpenFile(path, flag, perm) + } +} diff --git a/vendor/mvdan.cc/sh/interp/interp.go b/vendor/mvdan.cc/sh/v3/interp/interp.go similarity index 68% rename from vendor/mvdan.cc/sh/interp/interp.go rename to vendor/mvdan.cc/sh/v3/interp/interp.go index 5fd53e86..257ed04f 100644 --- a/vendor/mvdan.cc/sh/interp/interp.go +++ b/vendor/mvdan.cc/sh/v3/interp/interp.go @@ -11,28 +11,40 @@ import ( "io/ioutil" "math" "os" - "os/user" "path/filepath" "regexp" "runtime" + "strconv" "strings" "sync" "time" "golang.org/x/sync/errgroup" + "golang.org/x/xerrors" - "mvdan.cc/sh/expand" - "mvdan.cc/sh/syntax" + "mvdan.cc/sh/v3/expand" + "mvdan.cc/sh/v3/pattern" + "mvdan.cc/sh/v3/syntax" ) +// RunnerOption is a function which can be passed to New to alter Runner behaviour. +// To apply option to existing Runner call it directly, +// for example interp.Params("-e")(runner). +type RunnerOption func(*Runner) error + // New creates a new Runner, applying a number of options. If applying any of // the options results in an error, it is returned. // // Any unset options fall back to their defaults. For example, not supplying the // environment falls back to the process's environment, and not supplying the // standard output writer means that the output will be discarded. -func New(opts ...func(*Runner) error) (*Runner, error) { - r := &Runner{usedNew: true} +func New(opts ...RunnerOption) (*Runner, error) { + r := &Runner{ + usedNew: true, + execHandler: DefaultExecHandler(2 * time.Second), + openHandler: DefaultOpenHandler(), + } + r.dirStack = r.dirBootstrap[:0] for _, opt := range opts { if err := opt(r); err != nil { return nil, err @@ -47,14 +59,8 @@ func New(opts ...func(*Runner) error) (*Runner, error) { return nil, err } } - if r.Exec == nil { - Module(ModuleExec(nil))(r) - } - if r.Open == nil { - Module(ModuleOpen(nil))(r) - } - if r.Stdout == nil || r.Stderr == nil { - StdIO(r.Stdin, r.Stdout, r.Stderr)(r) + if r.stdout == nil || r.stderr == nil { + StdIO(r.stdin, r.stdout, r.stderr)(r) } return r, nil } @@ -73,7 +79,7 @@ func (r *Runner) fillExpandConfig(ctx context.Context) { break } path := r.literal(word) - f, err := r.open(ctx, r.relPath(path), os.O_RDONLY, 0, true) + f, err := r.open(ctx, path, os.O_RDONLY, 0, true) if err != nil { return err } @@ -81,11 +87,10 @@ func (r *Runner) fillExpandConfig(ctx context.Context) { return err } r2 := r.sub() - r2.Stdout = w - r2.stmts(ctx, cs.StmtList) + r2.stdout = w + r2.stmts(ctx, cs.Stmts) return r2.err }, - ReadDir: ioutil.ReadDir, } r.updateExpandOpts() } @@ -107,20 +112,19 @@ func catShortcutArg(stmt *syntax.Stmt) *syntax.Word { } func (r *Runner) updateExpandOpts() { - r.ecfg.NoGlob = r.opts[optNoGlob] + if r.opts[optNoGlob] { + r.ecfg.ReadDir = nil + } else { + r.ecfg.ReadDir = ioutil.ReadDir + } r.ecfg.GlobStar = r.opts[optGlobStar] } func (r *Runner) expandErr(err error) { - switch err := err.(type) { - case nil: - case expand.UnsetParameterError: - r.errf("%s\n", err.Message) - r.exit = 1 - r.setErr(ShellExitStatus(r.exit)) - default: - r.setErr(err) + if err != nil { + r.errf("%v\n", err) r.exit = 1 + r.exitShell = true } } @@ -159,12 +163,17 @@ type expandEnv struct { r *Runner } +var _ expand.WriteEnviron = expandEnv{} + func (e expandEnv) Get(name string) expand.Variable { return e.r.lookupVar(name) } -func (e expandEnv) Set(name string, vr expand.Variable) { + +func (e expandEnv) Set(name string, vr expand.Variable) error { e.r.setVarInternal(name, vr) + return nil // TODO: return any errors } + func (e expandEnv) Each(fn func(name string, vr expand.Variable) bool) { e.r.Env.Each(fn) for name, vr := range e.r.Vars { @@ -176,7 +185,7 @@ func (e expandEnv) Each(fn func(name string, vr expand.Variable) bool) { // Env sets the interpreter's environment. If nil, a copy of the current // process's environment is used. -func Env(env expand.Environ) func(*Runner) error { +func Env(env expand.Environ) RunnerOption { return func(r *Runner) error { if env == nil { env = expand.ListEnviron(os.Environ()...) @@ -188,7 +197,7 @@ func Env(env expand.Environ) func(*Runner) error { // Dir sets the interpreter's working directory. If empty, the process's current // directory is used. -func Dir(path string) func(*Runner) error { +func Dir(path string) RunnerOption { return func(r *Runner) error { if path == "" { path, err := os.Getwd() @@ -215,17 +224,21 @@ func Dir(path string) func(*Runner) error { } // Params populates the shell options and parameters. For example, Params("-e", -// "--", "foo") will set the "-e" option and the parameters ["foo"]. +// "--", "foo") will set the "-e" option and the parameters ["foo"], and +// Params("+e") will unset the "-e" option and leave the parameters untouched. // // This is similar to what the interpreter's "set" builtin does. -func Params(args ...string) func(*Runner) error { +func Params(args ...string) RunnerOption { return func(r *Runner) error { + onlyFlags := true for len(args) > 0 { arg := args[0] if arg == "" || (arg[0] != '-' && arg[0] != '+') { + onlyFlags = false break } if arg == "--" { + onlyFlags = false args = args[1:] break } @@ -259,33 +272,27 @@ func Params(args ...string) func(*Runner) error { *opt = enable args = args[1:] } - r.Params = args + if !onlyFlags { + // If "--" wasn't given and there were zero arguments, + // we don't want to override the current parameters. + r.Params = args + } return nil } } -type ModuleFunc interface { - isModule() +// ExecHandler sets command execution handler. See ExecHandlerFunc for more info. +func ExecHandler(f ExecHandlerFunc) RunnerOption { + return func(r *Runner) error { + r.execHandler = f + return nil + } } -// Module sets an interpreter module, which can be ModuleExec or ModuleOpen. If -// the value is nil, the default module implementation is used. -func Module(mod ModuleFunc) func(*Runner) error { +// OpenHandler sets file open handler. See OpenHandlerFunc for more info. +func OpenHandler(f OpenHandlerFunc) RunnerOption { return func(r *Runner) error { - switch mod := mod.(type) { - case ModuleExec: - if mod == nil { - mod = DefaultExec - } - r.Exec = mod - case ModuleOpen: - if mod == nil { - mod = DefaultOpen - } - r.Open = mod - default: - return fmt.Errorf("unknown module type: %T", mod) - } + r.openHandler = f return nil } } @@ -293,17 +300,17 @@ func Module(mod ModuleFunc) func(*Runner) error { // StdIO configures an interpreter's standard input, standard output, and // standard error. If out or err are nil, they default to a writer that discards // the output. -func StdIO(in io.Reader, out, err io.Writer) func(*Runner) error { +func StdIO(in io.Reader, out, err io.Writer) RunnerOption { return func(r *Runner) error { - r.Stdin = in + r.stdin = in if out == nil { out = ioutil.Discard } - r.Stdout = out + r.stdout = out if err == nil { err = ioutil.Discard } - r.Stderr = err + r.stderr = err return nil } } @@ -316,7 +323,9 @@ func StdIO(in io.Reader, out, err io.Writer) func(*Runner) error { // isn't safe for concurrent use, consider a workaround like hiding writes // behind a mutex. // -// To create a Runner, use New. +// To create a Runner, use New. Runner's exported fields are meant to be +// configured via runner options; once a Runner has been created, the fields +// should be treated as read-only. type Runner struct { // Env specifies the environment of the interpreter, which must be // non-nil. @@ -330,22 +339,22 @@ type Runner struct { // file or calling a function. Accessible via the $@/$* family of vars. Params []string - // Exec is the module responsible for executing programs. It must be - // non-nil. - Exec ModuleExec - // Open is the module responsible for opening files. It must be non-nil. - Open ModuleOpen - - Stdin io.Reader - Stdout io.Writer - Stderr io.Writer - // Separate maps - note that bash allows a name to be both a var and a // func simultaneously Vars map[string]expand.Variable Funcs map[string]*syntax.Stmt + // execHandler is a function responsible for executing programs. It must be non-nil. + execHandler ExecHandlerFunc + + // openHandler is a function responsible for opening files. It must be non-nil. + openHandler OpenHandlerFunc + + stdin io.Reader + stdout io.Writer + stderr io.Writer + ecfg *expand.Config ectx context.Context // just so that Runner.Sub can use it again @@ -367,18 +376,30 @@ type Runner struct { // >0 to break or continue out of N enclosing loops breakEnclosing, contnEnclosing int - inLoop bool - inFunc bool - inSource bool + inLoop bool + inFunc bool + inSource bool + noErrExit bool - err error // current shell exit code or fatal error - exit int // current (last) exit status code + err error // current shell exit code or fatal error + exit int // current (last) exit status code + exitShell bool // whether the shell needs to exit bgShells errgroup.Group - opts [len(shellOptsTable) + len(bashOptsTable)]bool + opts runnerOpts - dirStack []string + origDir string + origParams []string + origOpts runnerOpts + origStdin io.Reader + origStdout io.Writer + origStderr io.Writer + + // Most scripts don't use pushd/popd, so make space for the initial PWD + // without requiring an extra allocation. + dirStack []string + dirBootstrap [1]string optState getopts @@ -386,18 +407,22 @@ type Runner struct { // apply to the current shell, and not just the command. keepRedirs bool - // KillTimeout holds how much time the interpreter will wait for a - // program to stop after being sent an interrupt signal, after - // which a kill signal will be sent. This process will happen when the - // interpreter's context is cancelled. - // - // The zero value will default to 2 seconds. - // - // A negative value means that a kill signal will be sent immediately. - // - // On Windows, the kill signal is always sent immediately, - // because Go doesn't currently support sending Interrupt on Windows. - KillTimeout time.Duration + // So that we can get io.Copy to reuse the same buffer within a runner. + // For example, this saves an allocation for every shell pipe, since + // io.PipeReader does not implement io.WriterTo. + bufCopier bufCopier +} + +type bufCopier struct { + io.Reader + buf []byte +} + +func (r *bufCopier) WriteTo(w io.Writer) (n int64, err error) { + if r.buf == nil { + r.buf = make([]byte, 32*1024) + } + return io.CopyBuffer(w, r.Reader, r.buf) } func (r *Runner) optByFlag(flag string) *bool { @@ -425,6 +450,8 @@ func (r *Runner) optByName(name string, bash bool) *bool { return nil } +type runnerOpts [len(shellOptsTable) + len(bashOptsTable)]bool + var shellOptsTable = [...]struct { flag, name string }{ @@ -457,34 +484,54 @@ const ( optGlobStar ) -// Reset empties the runner state and sets any exported fields with zero values -// to their default values. +// Reset returns a runner to its initial state, right before the first call to +// Run or Reset. // // Typically, this function only needs to be called if a runner is reused to run // multiple programs non-incrementally. Not calling Reset between each run will -// mean that the shell state will be kept, including variables and options. +// mean that the shell state will be kept, including variables, options, and the +// current directory. func (r *Runner) Reset() { if !r.usedNew { panic("use interp.New to construct a Runner") } + if !r.didReset { + r.origDir = r.Dir + r.origParams = r.Params + r.origOpts = r.opts + r.origStdin = r.stdin + r.origStdout = r.stdout + r.origStderr = r.stderr + } // reset the internal state *r = Runner{ Env: r.Env, - Dir: r.Dir, - Params: r.Params, - Stdin: r.Stdin, - Stdout: r.Stdout, - Stderr: r.Stderr, - Exec: r.Exec, - Open: r.Open, - KillTimeout: r.KillTimeout, - opts: r.opts, + execHandler: r.execHandler, + openHandler: r.openHandler, + + // These can be set by functions like Dir or Params, but + // builtins can overwrite them; reset the fields to whatever the + // constructor set up. + Dir: r.origDir, + Params: r.origParams, + opts: r.origOpts, + stdin: r.origStdin, + stdout: r.origStdout, + stderr: r.origStderr, + + origDir: r.origDir, + origParams: r.origParams, + origOpts: r.origOpts, + origStdin: r.origStdin, + origStdout: r.origStdout, + origStderr: r.origStderr, // emptied below, to reuse the space - Vars: r.Vars, - cmdVars: r.cmdVars, - dirStack: r.dirStack[:0], - usedNew: r.usedNew, + Vars: r.Vars, + cmdVars: r.cmdVars, + dirStack: r.dirStack[:0], + usedNew: r.usedNew, + bufCopier: r.bufCopier, } if r.Vars == nil { r.Vars = make(map[string]expand.Variable) @@ -501,34 +548,36 @@ func (r *Runner) Reset() { } } if vr := r.Env.Get("HOME"); !vr.IsSet() { - u, _ := user.Current() - r.Vars["HOME"] = expand.Variable{Value: u.HomeDir} + home, _ := os.UserHomeDir() + r.Vars["HOME"] = expand.Variable{Kind: expand.String, Str: home} } - r.Vars["PWD"] = expand.Variable{Value: r.Dir} - r.Vars["IFS"] = expand.Variable{Value: " \t\n"} - r.Vars["OPTIND"] = expand.Variable{Value: "1"} + r.Vars["UID"] = expand.Variable{ + Kind: expand.String, + ReadOnly: true, + Str: strconv.Itoa(os.Getuid()), + } + r.Vars["PWD"] = expand.Variable{Kind: expand.String, Str: r.Dir} + r.Vars["IFS"] = expand.Variable{Kind: expand.String, Str: " \t\n"} + r.Vars["OPTIND"] = expand.Variable{Kind: expand.String, Str: "1"} if runtime.GOOS == "windows" { // convert $PATH to a unix path list path := r.Env.Get("PATH").String() path = strings.Join(filepath.SplitList(path), ":") - r.Vars["PATH"] = expand.Variable{Value: path} + r.Vars["PATH"] = expand.Variable{Kind: expand.String, Str: path} } r.dirStack = append(r.dirStack, r.Dir) - if r.KillTimeout == 0 { - r.KillTimeout = 2 * time.Second - } r.didReset = true + r.bufCopier.Reader = nil } -func (r *Runner) modCtx(ctx context.Context) context.Context { - mc := ModuleCtx{ - Dir: r.Dir, - Stdin: r.Stdin, - Stdout: r.Stdout, - Stderr: r.Stderr, - KillTimeout: r.KillTimeout, +func (r *Runner) handlerCtx(ctx context.Context) context.Context { + hc := HandlerContext{ + Dir: r.Dir, + Stdin: r.stdin, + Stdout: r.stdout, + Stderr: r.stderr, } oenv := overlayEnviron{ parent: r.Env, @@ -541,21 +590,30 @@ func (r *Runner) modCtx(ctx context.Context) context.Context { oenv.Set(name, vr) } for name, value := range r.cmdVars { - oenv.Set(name, expand.Variable{Exported: true, Value: value}) + oenv.Set(name, expand.Variable{Exported: true, Kind: expand.String, Str: value}) } - mc.Env = oenv - return context.WithValue(ctx, moduleCtxKey{}, mc) + hc.Env = oenv + return context.WithValue(ctx, handlerCtxKey{}, hc) } -// ShellExitStatus exits the shell with a status code. -type ShellExitStatus uint8 +// exitStatus is a non-zero status code resulting from running a shell node. +type exitStatus uint8 -func (s ShellExitStatus) Error() string { return fmt.Sprintf("exit status %d", s) } +func (s exitStatus) Error() string { return fmt.Sprintf("exit status %d", s) } -// ExitStatus is a non-zero status code resulting from running a shell node. -type ExitStatus uint8 +// NewExitStatus creates an error which contains the specified exit status code. +func NewExitStatus(status uint8) error { + return exitStatus(status) +} -func (s ExitStatus) Error() string { return fmt.Sprintf("exit status %d", s) } +// IsExitStatus checks whether error contains an exit status and returns it. +func IsExitStatus(err error) (status uint8, ok bool) { + var s exitStatus + if xerrors.As(err, &s) { + return uint8(s), true + } + return 0, false +} func (r *Runner) setErr(err error) { if r.err == nil { @@ -564,8 +622,8 @@ func (r *Runner) setErr(err error) { } // Run interprets a node, which can be a *File, *Stmt, or Command. If a non-nil -// error is returned, it will typically be of type ExitStatus or -// ShellExitStatus. +// error is returned, it will typically contain commands exit status, +// which can be retrieved with IsExitStatus. // // Run can be called multiple times synchronously to interpret programs // incrementally. To reuse a Runner without keeping the internal shell state, @@ -576,11 +634,12 @@ func (r *Runner) Run(ctx context.Context, node syntax.Node) error { } r.fillExpandConfig(ctx) r.err = nil + r.exitShell = false r.filename = "" switch x := node.(type) { case *syntax.File: r.filename = x.Name - r.stmts(ctx, x.StmtList) + r.stmts(ctx, x.Stmts) case *syntax.Stmt: r.stmt(ctx, x) case syntax.Command: @@ -588,26 +647,35 @@ func (r *Runner) Run(ctx context.Context, node syntax.Node) error { default: return fmt.Errorf("node can only be File, Stmt, or Command: %T", x) } - if r.exit > 0 { - r.setErr(ExitStatus(r.exit)) + if r.exit != 0 { + r.setErr(NewExitStatus(uint8(r.exit))) } return r.err } +// Exited reports whether the last Run call should exit an entire shell. This +// can be triggered by the "exit" built-in command, for example. +// +// Note that this state is overwritten at every Run call, so it should be +// checked immediately after each Run call. +func (r *Runner) Exited() bool { + return r.exitShell +} + func (r *Runner) out(s string) { - io.WriteString(r.Stdout, s) + io.WriteString(r.stdout, s) } func (r *Runner) outf(format string, a ...interface{}) { - fmt.Fprintf(r.Stdout, format, a...) + fmt.Fprintf(r.stdout, format, a...) } func (r *Runner) errf(format string, a ...interface{}) { - fmt.Fprintf(r.Stderr, format, a...) + fmt.Fprintf(r.stderr, format, a...) } func (r *Runner) stop(ctx context.Context) bool { - if r.err != nil { + if r.err != nil || r.exitShell { return true } if err := ctx.Err(); err != nil { @@ -637,7 +705,7 @@ func (r *Runner) stmt(ctx context.Context, st *syntax.Stmt) { } func (r *Runner) stmtSync(ctx context.Context, st *syntax.Stmt) { - oldIn, oldOut, oldErr := r.Stdin, r.Stdout, r.Stderr + oldIn, oldOut, oldErr := r.stdin, r.stdout, r.stderr for _, rd := range st.Redirs { cls, err := r.redir(ctx, rd) if err != nil { @@ -655,12 +723,18 @@ func (r *Runner) stmtSync(ctx context.Context, st *syntax.Stmt) { } if st.Negated { r.exit = oneIf(r.exit == 0) - } - if r.exit != 0 && r.opts[optErrExit] { - r.setErr(ShellExitStatus(r.exit)) + } else if _, ok := st.Cmd.(*syntax.CallExpr); !ok { + } else if r.exit != 0 && !r.noErrExit && r.opts[optErrExit] { + // If the "errexit" option is set and a simple command failed, + // exit the shell. Exceptions: + // + // conditions (if , while , etc) + // part of && or || lists + // preceded by ! + r.exitShell = true } if !r.keepRedirs { - r.Stdin, r.Stdout, r.Stderr = oldIn, oldOut, oldErr + r.stdin, r.stdout, r.stderr = oldIn, oldOut, oldErr } } @@ -671,13 +745,12 @@ func (r *Runner) sub() *Runner { Env: r.Env, Dir: r.Dir, Params: r.Params, - Exec: r.Exec, - Open: r.Open, - Stdin: r.Stdin, - Stdout: r.Stdout, - Stderr: r.Stderr, Funcs: r.Funcs, - KillTimeout: r.KillTimeout, + execHandler: r.execHandler, + openHandler: r.openHandler, + stdin: r.stdin, + stdout: r.stdout, + stderr: r.stderr, filename: r.filename, opts: r.opts, } @@ -693,7 +766,7 @@ func (r *Runner) sub() *Runner { for k, v := range r.cmdVars { r2.cmdVars[k] = v } - r2.dirStack = append([]string(nil), r.dirStack...) + r2.dirStack = append(r2.dirBootstrap[:0], r.dirStack...) r2.fillExpandConfig(r.ectx) r2.didReset = true return r2 @@ -705,26 +778,25 @@ func (r *Runner) cmd(ctx context.Context, cm syntax.Command) { } switch x := cm.(type) { case *syntax.Block: - r.stmts(ctx, x.StmtList) + r.stmts(ctx, x.Stmts) case *syntax.Subshell: r2 := r.sub() - r2.stmts(ctx, x.StmtList) + r2.stmts(ctx, x.Stmts) r.exit = r2.exit r.setErr(r2.err) case *syntax.CallExpr: fields := r.fields(x.Args...) if len(fields) == 0 { for _, as := range x.Assigns { - vr := r.lookupVar(as.Name.Value) - vr.Value = r.assignVal(as, "") + vr := r.assignVal(as, "") r.setVar(as.Name.Value, as.Index, vr) } break } for _, as := range x.Assigns { - val := r.assignVal(as, "") + vr := r.assignVal(as, "") // we know that inline vars must be strings - r.cmdVars[as.Name.Value] = val.(string) + r.cmdVars[as.Name.Value] = vr.Str } r.call(ctx, x.Args[0].Pos(), fields) // cmdVars can be nuked here, as they are never useful @@ -735,26 +807,25 @@ func (r *Runner) cmd(ctx context.Context, cm syntax.Command) { } case *syntax.BinaryCmd: switch x.Op { - case syntax.AndStmt: + case syntax.AndStmt, syntax.OrStmt: + oldNoErrExit := r.noErrExit + r.noErrExit = true r.stmt(ctx, x.X) - if r.exit == 0 { - r.stmt(ctx, x.Y) - } - case syntax.OrStmt: - r.stmt(ctx, x.X) - if r.exit != 0 { + r.noErrExit = oldNoErrExit + if (r.exit == 0) == (x.Op == syntax.AndStmt) { r.stmt(ctx, x.Y) } case syntax.Pipe, syntax.PipeAll: pr, pw := io.Pipe() r2 := r.sub() - r2.Stdout = pw + r2.stdout = pw if x.Op == syntax.PipeAll { - r2.Stderr = pw + r2.stderr = pw } else { - r2.Stderr = r.Stderr + r2.stderr = r.stderr } - r.Stdin = pr + r.bufCopier.Reader = pr + r.stdin = &r.bufCopier var wg sync.WaitGroup wg.Add(1) go func() { @@ -765,22 +836,32 @@ func (r *Runner) cmd(ctx context.Context, cm syntax.Command) { r.stmt(ctx, x.Y) pr.Close() wg.Wait() - if r.opts[optPipeFail] && r2.exit > 0 && r.exit == 0 { + if r.opts[optPipeFail] && r2.exit != 0 && r.exit == 0 { r.exit = r2.exit } r.setErr(r2.err) } case *syntax.IfClause: + oldNoErrExit := r.noErrExit + r.noErrExit = true r.stmts(ctx, x.Cond) + r.noErrExit = oldNoErrExit + if r.exit == 0 { r.stmts(ctx, x.Then) break } r.exit = 0 - r.stmts(ctx, x.Else) + if x.Else != nil { + r.cmd(ctx, x.Else) + } case *syntax.WhileClause: for !r.stop(ctx) { + oldNoErrExit := r.noErrExit + r.noErrExit = true r.stmts(ctx, x.Cond) + r.noErrExit = oldNoErrExit + stop := (r.exit == 0) == x.Until r.exit = 0 if stop || r.loopStmtsBroken(ctx, x.Do) { @@ -804,7 +885,7 @@ func (r *Runner) cmd(ctx context.Context, cm syntax.Command) { case *syntax.CStyleLoop: r.arithm(y.Init) for r.arithm(y.Cond) != 0 { - if r.loopStmtsBroken(ctx, x.Do) { + if r.exit != 0 || r.loopStmtsBroken(ctx, x.Do) { break } r.arithm(y.Post) @@ -826,7 +907,7 @@ func (r *Runner) cmd(ctx context.Context, cm syntax.Command) { for _, word := range ci.Patterns { pattern := r.pattern(word) if match(pattern, str) { - r.stmts(ctx, ci.StmtList) + r.stmts(ctx, ci.Stmts) return } } @@ -858,32 +939,32 @@ func (r *Runner) cmd(ctx context.Context, cm syntax.Command) { case "readonly": modes = append(modes, "-r") case "nameref": - modes = append(modes, "-n") + valType = "-n" } - for _, opt := range x.Opts { - switch s := r.literal(opt); s { - case "-x", "-r", "-n": - modes = append(modes, s) - case "-a", "-A": - valType = s - case "-g": - global = true - default: - r.errf("declare: invalid option %q\n", s) - r.exit = 2 - return - } - } - for _, as := range x.Assigns { + for _, as := range x.Args { for _, as := range r.flattenAssign(as) { name := as.Name.Value + if strings.HasPrefix(name, "-") { + switch name { + case "-x", "-r": + modes = append(modes, name) + case "-a", "-A", "-n": + valType = name + case "-g": + global = true + default: + r.errf("declare: invalid option %q\n", name) + r.exit = 2 + return + } + continue + } if !syntax.ValidName(name) { r.errf("declare: invalid name %q\n", name) r.exit = 1 return } - vr := r.lookupVar(as.Name.Value) - vr.Value = r.assignVal(as, valType) + vr := r.assignVal(as, valType) if global { vr.Local = false } else if local { @@ -895,8 +976,6 @@ func (r *Runner) cmd(ctx context.Context, cm syntax.Command) { vr.Exported = true case "-r": vr.ReadOnly = true - case "-n": - vr.NameRef = true } } r.setVar(name, as.Index, vr) @@ -947,8 +1026,8 @@ func (r *Runner) flattenAssign(as *syntax.Assign) []*syntax.Assign { return asgns } -func match(pattern, name string) bool { - expr, err := syntax.TranslatePattern(pattern, true) +func match(pat, name string) bool { + expr, err := pattern.Regexp(pat, 0) if err != nil { return false } @@ -965,8 +1044,8 @@ func elapsedString(d time.Duration, posix bool) string { return fmt.Sprintf("%dm%.3fs", min, sec) } -func (r *Runner) stmts(ctx context.Context, sl syntax.StmtList) { - for _, stmt := range sl.Stmts { +func (r *Runner) stmts(ctx context.Context, stmts []*syntax.Stmt) { + for _, stmt := range stmts { r.stmt(ctx, stmt) } } @@ -1006,28 +1085,28 @@ func (r *Runner) hdocReader(rd *syntax.Redirect) io.Reader { func (r *Runner) redir(ctx context.Context, rd *syntax.Redirect) (io.Closer, error) { if rd.Hdoc != nil { - r.Stdin = r.hdocReader(rd) + r.stdin = r.hdocReader(rd) return nil, nil } - orig := &r.Stdout + orig := &r.stdout if rd.N != nil { switch rd.N.Value { case "1": case "2": - orig = &r.Stderr + orig = &r.stderr } } arg := r.literal(rd.Word) switch rd.Op { case syntax.WordHdoc: - r.Stdin = strings.NewReader(arg + "\n") + r.stdin = strings.NewReader(arg + "\n") return nil, nil case syntax.DplOut: switch arg { case "1": - *orig = r.Stdout + *orig = r.stdout case "2": - *orig = r.Stderr + *orig = r.stderr } return nil, nil case syntax.RdrIn, syntax.RdrOut, syntax.AppOut, @@ -1044,29 +1123,29 @@ func (r *Runner) redir(ctx context.Context, rd *syntax.Redirect) (io.Closer, err case syntax.RdrOut, syntax.RdrAll: mode = os.O_WRONLY | os.O_CREATE | os.O_TRUNC } - f, err := r.open(ctx, r.relPath(arg), mode, 0644, true) + f, err := r.open(ctx, arg, mode, 0644, true) if err != nil { return nil, err } switch rd.Op { case syntax.RdrIn: - r.Stdin = f + r.stdin = f case syntax.RdrOut, syntax.AppOut: *orig = f case syntax.RdrAll, syntax.AppAll: - r.Stdout = f - r.Stderr = f + r.stdout = f + r.stderr = f default: panic(fmt.Sprintf("unhandled redirect op: %v", rd.Op)) } return f, nil } -func (r *Runner) loopStmtsBroken(ctx context.Context, sl syntax.StmtList) bool { +func (r *Runner) loopStmtsBroken(ctx context.Context, stmts []*syntax.Stmt) bool { oldInLoop := r.inLoop r.inLoop = true defer func() { r.inLoop = oldInLoop }() - for _, stmt := range sl.Stmts { + for _, stmt := range stmts { r.stmt(ctx, stmt) if r.contnEnclosing > 0 { r.contnEnclosing-- @@ -1117,155 +1196,34 @@ func (r *Runner) call(ctx context.Context, pos syntax.Pos, args []string) { } func (r *Runner) exec(ctx context.Context, args []string) { - path := r.lookPath(args[0]) - err := r.Exec(r.modCtx(ctx), path, args) - switch x := err.(type) { - case nil: - r.exit = 0 - case ExitStatus: - r.exit = int(x) - default: // module's custom fatal error - r.setErr(err) + err := r.execHandler(r.handlerCtx(ctx), args) + if status, ok := IsExitStatus(err); ok { + r.exit = int(status) + return } + if err != nil { + // handler's custom fatal error + r.setErr(err) + return + } + r.exit = 0 } func (r *Runner) open(ctx context.Context, path string, flags int, mode os.FileMode, print bool) (io.ReadWriteCloser, error) { - f, err := r.Open(r.modCtx(ctx), path, flags, mode) + f, err := r.openHandler(r.handlerCtx(ctx), path, flags, mode) + // TODO: support wrapped PathError returned from openHandler. switch err.(type) { case nil: case *os.PathError: if print { r.errf("%v\n", err) } - default: // module's custom fatal error + default: // handler's custom fatal error r.setErr(err) } return f, err } func (r *Runner) stat(name string) (os.FileInfo, error) { - return os.Stat(r.relPath(name)) -} - -func (r *Runner) checkStat(file string) string { - d, err := r.stat(file) - if err != nil { - return "" - } - m := d.Mode() - if m.IsDir() { - return "" - } - if runtime.GOOS != "windows" && m&0111 == 0 { - return "" - } - return file -} - -func winHasExt(file string) bool { - i := strings.LastIndex(file, ".") - if i < 0 { - return false - } - return strings.LastIndexAny(file, `:\/`) < i -} - -func (r *Runner) findExecutable(file string, exts []string) string { - if len(exts) == 0 { - // non-windows - return r.checkStat(file) - } - if winHasExt(file) && r.checkStat(file) != "" { - return file - } - for _, e := range exts { - if f := file + e; r.checkStat(f) != "" { - return f - } - } - return "" -} - -func driveLetter(c byte) bool { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') -} - -// splitList is like filepath.SplitList, but always using the unix path -// list separator ':'. On Windows, it also makes sure not to split -// [A-Z]:[/\]. -func splitList(path string) []string { - if path == "" { - return []string{""} - } - list := strings.Split(path, ":") - if runtime.GOOS != "windows" { - return list - } - // join "C", "/foo" into "C:/foo" - var fixed []string - for i := 0; i < len(list); i++ { - s := list[i] - switch { - case len(s) != 1, !driveLetter(s[0]): - case i+1 >= len(list): - // last element - case strings.IndexAny(list[i+1], `/\`) != 0: - // next element doesn't start with / or \ - default: - fixed = append(fixed, s+":"+list[i+1]) - i++ - continue - } - fixed = append(fixed, s) - } - return fixed -} - -func (r *Runner) lookPath(file string) string { - pathList := splitList(r.envGet("PATH")) - chars := `/` - if runtime.GOOS == "windows" { - chars = `:\/` - // so that "foo" always tries "./foo" - pathList = append([]string{"."}, pathList...) - } - exts := r.pathExts() - if strings.ContainsAny(file, chars) { - return r.findExecutable(file, exts) - } - for _, dir := range pathList { - var path string - switch dir { - case "", ".": - // otherwise "foo" won't be "./foo" - path = "." + string(filepath.Separator) + file - default: - path = filepath.Join(dir, file) - } - if f := r.findExecutable(path, exts); f != "" { - return f - } - } - return "" -} - -func (r *Runner) pathExts() []string { - if runtime.GOOS != "windows" { - return nil - } - pathext := r.envGet("PATHEXT") - if pathext == "" { - return []string{".com", ".exe", ".bat", ".cmd"} - } - var exts []string - for _, e := range strings.Split(strings.ToLower(pathext), `;`) { - if e == "" { - continue - } - if e[0] != '.' { - e = "." + e - } - exts = append(exts, e) - } - return exts + return os.Stat(r.absPath(name)) } diff --git a/vendor/mvdan.cc/sh/interp/perm_unix.go b/vendor/mvdan.cc/sh/v3/interp/perm_unix.go similarity index 100% rename from vendor/mvdan.cc/sh/interp/perm_unix.go rename to vendor/mvdan.cc/sh/v3/interp/perm_unix.go diff --git a/vendor/mvdan.cc/sh/interp/perm_windows.go b/vendor/mvdan.cc/sh/v3/interp/perm_windows.go similarity index 100% rename from vendor/mvdan.cc/sh/interp/perm_windows.go rename to vendor/mvdan.cc/sh/v3/interp/perm_windows.go diff --git a/vendor/mvdan.cc/sh/interp/test.go b/vendor/mvdan.cc/sh/v3/interp/test.go similarity index 88% rename from vendor/mvdan.cc/sh/interp/test.go rename to vendor/mvdan.cc/sh/v3/interp/test.go index 96cc311b..9b0c3d5e 100644 --- a/vendor/mvdan.cc/sh/interp/test.go +++ b/vendor/mvdan.cc/sh/v3/interp/test.go @@ -12,7 +12,8 @@ import ( "golang.org/x/crypto/ssh/terminal" - "mvdan.cc/sh/syntax" + "mvdan.cc/sh/v3/expand" + "mvdan.cc/sh/v3/syntax" ) // non-empty string is true, empty string is false @@ -24,17 +25,17 @@ func (r *Runner) bashTest(ctx context.Context, expr syntax.TestExpr, classic boo return r.bashTest(ctx, x.X, classic) case *syntax.BinaryTest: switch x.Op { - case syntax.TsMatch, syntax.TsNoMatch: + case syntax.TsMatchShort, 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) { + if (str == lit) == (x.Op != syntax.TsNoMatch) { return "1" } } else { // [[ pattern := r.pattern(yw) - if match(pattern, str) == (x.Op == syntax.TsMatch) { + if match(pattern, str) == (x.Op != syntax.TsNoMatch) { return "1" } } @@ -132,7 +133,7 @@ func (r *Runner) unTest(ctx context.Context, op syntax.UnTestOperator, x string) case syntax.TsSocket: return r.statMode(x, os.ModeSocket) case syntax.TsSmbLink: - info, err := os.Lstat(r.relPath(x)) + info, err := os.Lstat(r.absPath(x)) return err == nil && info.Mode()&os.ModeSymlink != 0 case syntax.TsSticky: return r.statMode(x, os.ModeSticky) @@ -140,23 +141,23 @@ func (r *Runner) unTest(ctx context.Context, op syntax.UnTestOperator, x string) 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.TsGrpOwn: + // case syntax.TsUsrOwn: + // case syntax.TsModif: case syntax.TsRead: - f, err := r.open(ctx, r.relPath(x), os.O_RDONLY, 0, false) + f, err := r.open(ctx, 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) + f, err := r.open(ctx, x, os.O_WRONLY, 0, false) if err == nil { f.Close() } return err == nil case syntax.TsExec: - _, err := exec.LookPath(r.relPath(x)) + _, err := exec.LookPath(r.absPath(x)) return err == nil case syntax.TsNoEmpty: info, err := r.stat(x) @@ -175,7 +176,7 @@ func (r *Runner) unTest(ctx context.Context, op syntax.UnTestOperator, x string) case syntax.TsVarSet: return r.lookupVar(x).IsSet() case syntax.TsRefVar: - return r.lookupVar(x).NameRef + return r.lookupVar(x).Kind == expand.NameRef case syntax.TsNot: return x == "" default: diff --git a/vendor/mvdan.cc/sh/interp/test_classic.go b/vendor/mvdan.cc/sh/v3/interp/test_classic.go similarity index 99% rename from vendor/mvdan.cc/sh/interp/test_classic.go rename to vendor/mvdan.cc/sh/v3/interp/test_classic.go index 7fba5e02..f0b1b20b 100644 --- a/vendor/mvdan.cc/sh/interp/test_classic.go +++ b/vendor/mvdan.cc/sh/v3/interp/test_classic.go @@ -6,7 +6,7 @@ package interp import ( "fmt" - "mvdan.cc/sh/syntax" + "mvdan.cc/sh/v3/syntax" ) const illegalTok = 0 diff --git a/vendor/mvdan.cc/sh/interp/vars.go b/vendor/mvdan.cc/sh/v3/interp/vars.go similarity index 66% rename from vendor/mvdan.cc/sh/interp/vars.go rename to vendor/mvdan.cc/sh/v3/interp/vars.go index f0b2640e..8fa9b4aa 100644 --- a/vendor/mvdan.cc/sh/interp/vars.go +++ b/vendor/mvdan.cc/sh/v3/interp/vars.go @@ -9,8 +9,8 @@ import ( "strconv" "strings" - "mvdan.cc/sh/expand" - "mvdan.cc/sh/syntax" + "mvdan.cc/sh/v3/expand" + "mvdan.cc/sh/v3/syntax" ) type overlayEnviron struct { @@ -39,7 +39,7 @@ func (o overlayEnviron) Each(f func(name string, vr expand.Variable) bool) { } func execEnv(env expand.Environ) []string { - list := make([]string, 0, 32) + list := make([]string, 0, 64) env.Each(func(name string, vr expand.Variable) bool { if vr.Exported { list = append(list, name+"="+vr.String()) @@ -53,39 +53,41 @@ func (r *Runner) lookupVar(name string) expand.Variable { if name == "" { panic("variable name must not be empty") } - var value interface{} + var vr expand.Variable switch name { case "#": - value = strconv.Itoa(len(r.Params)) + vr.Kind, vr.Str = expand.String, strconv.Itoa(len(r.Params)) case "@", "*": - value = r.Params + vr.Kind, vr.List = expand.Indexed, r.Params case "?": - value = strconv.Itoa(r.exit) + vr.Kind, vr.Str = expand.String, strconv.Itoa(r.exit) case "$": - value = strconv.Itoa(os.Getpid()) + vr.Kind, vr.Str = expand.String, strconv.Itoa(os.Getpid()) case "PPID": - value = strconv.Itoa(os.Getppid()) + vr.Kind, vr.Str = expand.String, strconv.Itoa(os.Getppid()) case "DIRSTACK": - value = r.dirStack + vr.Kind, vr.List = expand.Indexed, r.dirStack case "0": + vr.Kind = expand.String if r.filename != "" { - value = r.filename + vr.Str = r.filename } else { - value = "gosh" + vr.Str = "gosh" } case "1", "2", "3", "4", "5", "6", "7", "8", "9": + vr.Kind = expand.String i := int(name[0] - '1') if i < len(r.Params) { - value = r.Params[i] + vr.Str = r.Params[i] } else { - value = "" + vr.Str = "" } } - if value != nil { - return expand.Variable{Value: value} + if vr.IsSet() { + return vr } if value, e := r.cmdVars[name]; e { - return expand.Variable{Value: value} + return expand.Variable{Kind: expand.String, Str: value} } if vr, e := r.funcVars[name]; e { vr.Local = true @@ -105,7 +107,8 @@ func (r *Runner) lookupVar(name string) expand.Variable { } if r.opts[optNoUnset] { r.errf("%s: unbound variable\n", name) - r.setErr(ShellExitStatus(1)) + r.exit = 1 + r.exitShell = true } return expand.Variable{} } @@ -130,11 +133,11 @@ func (r *Runner) delVar(name string) { } func (r *Runner) setVarString(name, value string) { - r.setVar(name, nil, expand.Variable{Value: value}) + r.setVar(name, nil, expand.Variable{Kind: expand.String, Str: value}) } func (r *Runner) setVarInternal(name string, vr expand.Variable) { - if _, ok := vr.Value.(string); ok { + if vr.Kind == expand.String { if r.opts[optAllExport] { vr.Exported = true } @@ -161,20 +164,17 @@ func (r *Runner) setVar(name string, index syntax.ArithmExpr, vr expand.Variable 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 { + if vr.Kind == expand.String && index == nil { // When assigning a string to an array, fall back to the // zero value for the index. - if isIndexArray { + switch cur.Kind { + case expand.Indexed: index = &syntax.Word{Parts: []syntax.WordPart{ &syntax.Lit{Value: "0"}, }} - } else if isAssocArray { + case expand.Associative: index = &syntax.Word{Parts: []syntax.WordPart{ &syntax.DblQuoted{}, }} @@ -187,36 +187,33 @@ func (r *Runner) setVar(name string, index syntax.ArithmExpr, vr expand.Variable // 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) + valStr := vr.Str - // 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) + var list []string + switch cur.Kind { + case expand.String: + list = append(list, cur.Str) + case expand.Indexed: + list = cur.List + case expand.Associative: + // if the existing variable is already an AssocArray, try our + // best to convert the key to a string w, ok := index.(*syntax.Word) if !ok { return } k := r.literal(w) - amap[k] = valStr - cur.Value = amap + cur.Map[k] = valStr 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 + cur.Kind = expand.Indexed + cur.List = list r.setVarInternal(name, cur) } @@ -239,56 +236,64 @@ func stringIndex(index syntax.ArithmExpr) bool { return false } -func (r *Runner) assignVal(as *syntax.Assign, valType string) interface{} { +func (r *Runner) assignVal(as *syntax.Assign, valType string) expand.Variable { prev := r.lookupVar(as.Name.Value) if as.Naked { - return prev.Value + return prev } 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, "") + prev.Kind = expand.String + if valType == "-n" { + prev.Kind = expand.NameRef } - x[0] += s - return x - case map[string]string: + prev.Str = s + return prev + } + switch prev.Kind { + case expand.String: + prev.Str += s + case expand.Indexed: + if len(prev.List) == 0 { + prev.List = append(prev.List, "") + } + prev.List[0] += s + case expand.Associative: // TODO } - return s + return prev } if as.Array == nil { - // don't return nil, as that's an unset variable - return "" + // don't return the zero value, as that's an unset variable + prev.Kind = expand.String + if valType == "-n" { + prev.Kind = expand.NameRef + } + prev.Str = "" + return prev } elems := as.Array.Elems if valType == "" { - if len(elems) == 0 || !stringIndex(elems[0].Index) { - valType = "-a" // indexed - } else { + valType = "-a" // indexed + if len(elems) > 0 && stringIndex(elems[0].Index) { 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 + if !as.Append { + prev.Kind = expand.Associative + prev.Map = amap + return prev } // TODO - return amap + return prev } - // indexed array maxIndex := len(elems) - 1 indexes := make([]int, len(elems)) for i, elem := range elems { @@ -306,16 +311,19 @@ func (r *Runner) assignVal(as *syntax.Assign, valType string) interface{} { for i, elem := range elems { strs[indexes[i]] = r.literal(elem.Value) } - if !as.Append || !prev.IsSet() { - return strs + if !as.Append { + prev.Kind = expand.Indexed + prev.List = strs + return prev } - switch x := prev.Value.(type) { - case string: - return append([]string{x}, strs...) - case []string: - return append(x, strs...) - case map[string]string: + switch prev.Kind { + case expand.String: + prev.Kind = expand.Indexed + prev.List = append([]string{prev.Str}, strs...) + case expand.Indexed: + prev.List = append(prev.List, strs...) + case expand.Associative: // TODO } - return strs + return prev } diff --git a/vendor/mvdan.cc/sh/v3/pattern/pattern.go b/vendor/mvdan.cc/sh/v3/pattern/pattern.go new file mode 100644 index 00000000..185f2d80 --- /dev/null +++ b/vendor/mvdan.cc/sh/v3/pattern/pattern.go @@ -0,0 +1,295 @@ +// Copyright (c) 2017, Daniel Martí +// See LICENSE for licensing information + +// Package pattern allows working with shell pattern matching notation, also +// known as wildcards or globbing. +// +// For reference, see +// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13. +package pattern + +import ( + "bytes" + "fmt" + "regexp" + "strconv" + "strings" +) + +// TODO: support Mode in the other APIs too + +type Mode uint + +const ( + Shortest Mode = 1 << iota // prefer the shortest match. + Filenames // "*" and "?" don't match slashes; only "**" does + Braces // support "{a,b}" and "{1..4}" +) + +var numRange = regexp.MustCompile(`^([+-]?\d+)\.\.([+-]?\d+)}`) + +// Regexp turns a shell 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, Regexp(`foo*bar?`, true) returns `foo.*bar.`. +// +// Note that this function (and QuoteMeta) 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 Regexp(pat string, mode Mode) (string, error) { + any := false +noopLoop: + for _, r := range pat { + switch r { + // including those that need escaping since they are + // regular expression metacharacters + case '*', '?', '[', '\\', '.', '+', '(', ')', '|', + ']', '{', '}', '^', '$': + any = true + break noopLoop + } + } + if !any { // short-cut without a string copy + return pat, nil + } + closingBraces := []int{} + var buf bytes.Buffer +writeLoop: + for i := 0; i < len(pat); i++ { + switch c := pat[i]; c { + case '*': + if mode&Filenames != 0 { + if i++; i < len(pat) && pat[i] == '*' { + if i++; i < len(pat) && pat[i] == '/' { + buf.WriteString("(.*/|)") + } else { + buf.WriteString(".*") + i-- + } + } else { + buf.WriteString("[^/]*") + i-- + } + } else { + buf.WriteString(".*") + } + if mode&Shortest != 0 { + buf.WriteByte('?') + } + case '?': + if mode&Filenames != 0 { + buf.WriteString("[^/]") + } else { + buf.WriteByte('.') + } + case '\\': + if i++; i >= len(pat) { + return "", fmt.Errorf(`\ at end of pattern`) + } + buf.WriteString(regexp.QuoteMeta(string(pat[i]))) + case '[': + name, err := charClass(pat[i:]) + if err != nil { + return "", err + } + if name != "" { + buf.WriteString(name) + i += len(name) - 1 + break + } + if mode&Filenames != 0 { + for _, c := range pat[i:] { + if c == ']' { + break + } else if c == '/' { + buf.WriteString("\\[") + continue writeLoop + } + } + } + buf.WriteByte(c) + if i++; i >= len(pat) { + return "", fmt.Errorf("[ was not matched with a closing ]") + } + switch c = pat[i]; c { + case '!', '^': + buf.WriteByte('^') + if i++; i >= len(pat) { + return "", fmt.Errorf("[ was not matched with a closing ]") + } + } + if c = pat[i]; c == ']' { + buf.WriteByte(']') + if i++; i >= len(pat) { + return "", fmt.Errorf("[ was not matched with a closing ]") + } + } + rangeStart := byte(0) + loopBracket: + for ; i < len(pat); i++ { + c = pat[i] + buf.WriteByte(c) + switch c { + case '\\': + if i++; i < len(pat) { + buf.WriteByte(pat[i]) + } + continue + case ']': + break loopBracket + } + if rangeStart != 0 && rangeStart > c { + return "", fmt.Errorf("invalid range: %c-%c", rangeStart, c) + } + if c == '-' { + rangeStart = pat[i-1] + } else { + rangeStart = 0 + } + } + if i >= len(pat) { + return "", fmt.Errorf("[ was not matched with a closing ]") + } + case '{': + if mode&Braces == 0 { + buf.WriteString(regexp.QuoteMeta(string(c))) + break + } + innerLevel := 1 + commas := false + peekBrace: + for j := i + 1; j < len(pat); j++ { + switch c := pat[j]; c { + case '{': + innerLevel++ + case ',': + commas = true + case '\\': + j++ + case '}': + if innerLevel--; innerLevel > 0 { + continue + } + if !commas { + break peekBrace + } + closingBraces = append(closingBraces, j) + buf.WriteString("(?:") + continue writeLoop + } + } + if match := numRange.FindStringSubmatch(pat[i+1:]); len(match) == 3 { + start, err1 := strconv.Atoi(match[1]) + end, err2 := strconv.Atoi(match[2]) + if err1 != nil || err2 != nil || start > end { + return "", fmt.Errorf("invalid range: %q", match[0]) + } + // TODO: can we do better here? + buf.WriteString("(?:") + for n := start; n <= end; n++ { + if n > start { + buf.WriteByte('|') + } + fmt.Fprintf(&buf, "%d", n) + } + buf.WriteByte(')') + i += len(match[0]) + break + } + buf.WriteString(regexp.QuoteMeta(string(c))) + case ',': + if len(closingBraces) == 0 { + buf.WriteString(regexp.QuoteMeta(string(c))) + } else { + buf.WriteByte('|') + } + case '}': + if len(closingBraces) > 0 && closingBraces[len(closingBraces)-1] == i { + buf.WriteByte(')') + closingBraces = closingBraces[:len(closingBraces)-1] + } else { + buf.WriteString(regexp.QuoteMeta(string(c))) + } + default: + if c > 128 { + buf.WriteByte(c) + } else { + buf.WriteString(regexp.QuoteMeta(string(c))) + } + } + } + return buf.String(), nil +} + +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 +} + +// HasMeta returns whether a string contains any unescaped pattern +// metacharacters: '*', '?', or '['. When the function returns false, the given +// pattern can only match at most one string. +// +// For example, HasMeta(`foo\*bar`) returns false, but HasMeta(`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 HasMeta(pat string) bool { + for i := 0; i < len(pat); i++ { + switch pat[i] { + case '\\': + i++ + case '*', '?', '[': + return true + } + } + return false +} + +// QuoteMeta returns a string that quotes all pattern metacharacters in the +// given text. The returned string is a pattern that matches the literal text. +// +// For example, QuoteMeta(`foo*bar?`) returns `foo\*bar\?`. +func QuoteMeta(pat string) string { + any := false +loop: + for _, r := range pat { + switch r { + case '*', '?', '[', '\\': + any = true + break loop + } + } + if !any { // short-cut without a string copy + return pat + } + var buf bytes.Buffer + for _, r := range pat { + switch r { + case '*', '?', '[', '\\': + buf.WriteByte('\\') + } + buf.WriteRune(r) + } + return buf.String() +} diff --git a/vendor/mvdan.cc/sh/shell/doc.go b/vendor/mvdan.cc/sh/v3/shell/doc.go similarity index 100% rename from vendor/mvdan.cc/sh/shell/doc.go rename to vendor/mvdan.cc/sh/v3/shell/doc.go diff --git a/vendor/mvdan.cc/sh/shell/expand.go b/vendor/mvdan.cc/sh/v3/shell/expand.go similarity index 97% rename from vendor/mvdan.cc/sh/shell/expand.go rename to vendor/mvdan.cc/sh/v3/shell/expand.go index c0df1e32..1d45dd44 100644 --- a/vendor/mvdan.cc/sh/shell/expand.go +++ b/vendor/mvdan.cc/sh/v3/shell/expand.go @@ -7,8 +7,8 @@ import ( "os" "strings" - "mvdan.cc/sh/expand" - "mvdan.cc/sh/syntax" + "mvdan.cc/sh/v3/expand" + "mvdan.cc/sh/v3/syntax" ) // Expand performs shell expansion on s as if it were within double quotes, diff --git a/vendor/mvdan.cc/sh/shell/source.go b/vendor/mvdan.cc/sh/v3/shell/source.go similarity index 94% rename from vendor/mvdan.cc/sh/shell/source.go rename to vendor/mvdan.cc/sh/v3/shell/source.go index 7447b0bb..13261ffd 100644 --- a/vendor/mvdan.cc/sh/shell/source.go +++ b/vendor/mvdan.cc/sh/v3/shell/source.go @@ -8,9 +8,9 @@ import ( "fmt" "os" - "mvdan.cc/sh/expand" - "mvdan.cc/sh/interp" - "mvdan.cc/sh/syntax" + "mvdan.cc/sh/v3/expand" + "mvdan.cc/sh/v3/interp" + "mvdan.cc/sh/v3/syntax" ) // SourceFile sources a shell file from disk and returns the variables @@ -48,6 +48,7 @@ func SourceNode(ctx context.Context, node syntax.Node) (map[string]expand.Variab // delete the internal shell vars that the user is not // interested in delete(r.Vars, "PWD") + delete(r.Vars, "UID") delete(r.Vars, "HOME") delete(r.Vars, "PATH") delete(r.Vars, "IFS") diff --git a/vendor/mvdan.cc/sh/v3/syntax/braces.go b/vendor/mvdan.cc/sh/v3/syntax/braces.go new file mode 100644 index 00000000..0baa43a0 --- /dev/null +++ b/vendor/mvdan.cc/sh/v3/syntax/braces.go @@ -0,0 +1,178 @@ +// Copyright (c) 2018, Daniel Martí +// See LICENSE for licensing information + +package syntax + +import "strconv" + +var ( + litLeftBrace = &Lit{Value: "{"} + litComma = &Lit{Value: ","} + litDots = &Lit{Value: ".."} + litRightBrace = &Lit{Value: "}"} +) + +// SplitBraces parses brace expansions within a word's literal parts. If any +// valid brace expansions are found, they are replaced with BraceExp nodes, and +// the function returns true. Otherwise, the word is left untouched and the +// function returns false. +// +// For example, a literal word "foo{bar,baz}" will result in a word containing +// the literal "foo", and a brace expansion with the elements "bar" and "baz". +// +// It does not return an error; malformed brace expansions are simply skipped. +// For example, the literal word "a{b" is left unchanged. +func SplitBraces(word *Word) bool { + any := false + top := &Word{} + acc := top + var cur *BraceExp + open := []*BraceExp{} + + pop := func() *BraceExp { + 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) + } + + for _, wp := range word.Parts { + lit, ok := wp.(*Lit) + if !ok { + acc.Parts = append(acc.Parts, 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 = &Word{} + cur = &BraceExp{Elems: []*Word{acc}} + open = append(open, cur) + case ',': + if cur == nil { + continue + } + addlitidx() + acc = &Word{} + cur.Elems = append(cur.Elems, acc) + case '.': + if cur == nil { + continue + } + if j+1 >= len(lit.Value) || lit.Value[j+1] != '.' { + continue + } + addlitidx() + cur.Sequence = true + acc = &Word{} + 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) + acc.Parts = append(acc.Parts, br.Elems[0].Parts...) + addLit(litRightBrace) + break + } + if !br.Sequence { + acc.Parts = append(acc.Parts, br) + break + } + var chars [2]bool + broken := false + for i, elem := range br.Elems[:2] { + val := elem.Lit() + 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 := br.Elems[2].Lit() + 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] + acc.Parts = append(acc.Parts, br) + break + } + // return broken {x..y[..incr]} to a non-brace + addLit(litLeftBrace) + for i, elem := range br.Elems { + if i > 0 { + addLit(litDots) + } + acc.Parts = append(acc.Parts, elem.Parts...) + } + addLit(litRightBrace) + default: + continue + } + last = j + 1 + } + if last == 0 { + addLit(lit) + } else { + left := *lit + left.Value = left.Value[last:] + addLit(&left) + } + } + if !any { + return false + } + // 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.Sequence { + addLit(litDots) + } else { + addLit(litComma) + } + } + acc.Parts = append(acc.Parts, elem.Parts...) + } + } + *word = *top + return true +} diff --git a/vendor/mvdan.cc/sh/syntax/canonical.sh b/vendor/mvdan.cc/sh/v3/syntax/canonical.sh similarity index 100% rename from vendor/mvdan.cc/sh/syntax/canonical.sh rename to vendor/mvdan.cc/sh/v3/syntax/canonical.sh diff --git a/vendor/mvdan.cc/sh/syntax/doc.go b/vendor/mvdan.cc/sh/v3/syntax/doc.go similarity index 80% rename from vendor/mvdan.cc/sh/syntax/doc.go rename to vendor/mvdan.cc/sh/v3/syntax/doc.go index eff8c2fe..5c6275e4 100644 --- a/vendor/mvdan.cc/sh/syntax/doc.go +++ b/vendor/mvdan.cc/sh/v3/syntax/doc.go @@ -2,5 +2,5 @@ // See LICENSE for licensing information // Package syntax implements parsing and formatting of shell programs. -// It supports both POSIX Shell and Bash. +// It supports POSIX Shell, Bash, and mksh. package syntax diff --git a/vendor/mvdan.cc/sh/syntax/lexer.go b/vendor/mvdan.cc/sh/v3/syntax/lexer.go similarity index 93% rename from vendor/mvdan.cc/sh/syntax/lexer.go rename to vendor/mvdan.cc/sh/v3/syntax/lexer.go index d2c1990c..ac144cc2 100644 --- a/vendor/mvdan.cc/sh/syntax/lexer.go +++ b/vendor/mvdan.cc/sh/v3/syntax/lexer.go @@ -40,7 +40,7 @@ func paramNameOp(r rune) bool { // tokenize these inside arithmetic expansions func arithmOps(r rune) bool { switch r { - case '+', '-', '!', '*', '/', '%', '(', ')', '^', '<', '>', ':', '=', + case '+', '-', '!', '~', '*', '/', '%', '(', ')', '^', '<', '>', ':', '=', ',', '?', '|', '&', '[', ']', '#': return true } @@ -55,8 +55,10 @@ func bquoteEscaped(b byte) bool { return false } +const escNewl rune = utf8.RuneSelf + 1 + func (p *Parser) rune() rune { - if p.r == '\n' { + if p.r == '\n' || p.r == escNewl { // p.r instead of b so that newline // character positions don't have col 0. p.npos.line++ @@ -68,11 +70,14 @@ retry: if p.bsp < len(p.bs) { if b := p.bs[p.bsp]; b < utf8.RuneSelf { p.bsp++ - if b == '\\' && p.openBquotes > 0 { - // don't do it for newlines, as we want - // the newlines to be eaten in p.next - if bquotes < p.openBquotes && p.bsp < len(p.bs) && - bquoteEscaped(p.bs[p.bsp]) { + if b == '\\' { + if p.r != '\\' && p.peekByte('\n') { + p.bsp++ + p.w, p.r = 1, escNewl + return escNewl + } + if p.openBquotes > 0 && bquotes < p.openBquotes && + p.bsp < len(p.bs) && bquoteEscaped(p.bs[p.bsp]) { bquotes++ goto retry } @@ -194,6 +199,9 @@ func (p *Parser) next() { p.tok = _EOF return } + for p.r == escNewl { + p.rune() + } p.spaced = false if p.quote&allKeepSpaces != 0 { p.nextKeepSpaces() @@ -206,6 +214,8 @@ skipSpace: case utf8.RuneSelf: p.tok = _EOF return + case escNewl: + r = p.rune() case ' ', '\t', '\r': p.spaced = true r = p.rune() @@ -221,12 +231,6 @@ skipSpace: p.doHeredocs() } return - case '\\': - if !p.peekByte('\n') { - break skipSpace - } - p.rune() - r = p.rune() default: break skipSpace } @@ -240,7 +244,6 @@ skipSpace: return } } -changedState: p.pos = p.getPos() switch { case p.quote&allRegTokens != 0: @@ -250,7 +253,10 @@ changedState: case '#': r = p.rune() p.newLit(r) - for r != utf8.RuneSelf && r != '\n' { + for r != '\n' && r != utf8.RuneSelf { + if r == escNewl { + p.litBs = append(p.litBs, '\\', '\n') + } r = p.rune() } if p.keepComments { @@ -297,7 +303,7 @@ changedState: case p.quote == testRegexp: if !p.rxFirstPart && p.spaced { p.quote = noState - goto changedState + goto skipSpace } p.rxFirstPart = false switch r { @@ -613,6 +619,9 @@ func (p *Parser) arithmToken(r rune) token { return equal } return assgn + case '~': + p.rune() + return tilde case '(': p.rune() return leftParen @@ -739,7 +748,7 @@ func (p *Parser) newLit(r rune) { case r < utf8.RuneSelf: p.litBs = p.litBuf[:1] p.litBs[0] = byte(r) - case r > utf8.RuneSelf: + case r > escNewl: w := utf8.RuneLen(r) p.litBs = append(p.litBuf[:0], p.bs[p.bsp-w:p.bsp]...) default: @@ -749,10 +758,8 @@ func (p *Parser) newLit(r rune) { } } -func (p *Parser) discardLit(n int) { p.litBs = p.litBs[:len(p.litBs)-n] } - func (p *Parser) endLit() (s string) { - if p.r == utf8.RuneSelf { + if p.r == utf8.RuneSelf || p.r == escNewl { s = string(p.litBs) } else { s = string(p.litBs[:len(p.litBs)-int(p.w)]) @@ -781,17 +788,11 @@ func (p *Parser) advanceNameCont(r rune) { loop: for p.newLit(r); r != utf8.RuneSelf; r = p.rune() { switch { - case r == '\\': - if p.peekByte('\n') { - p.rune() - p.discardLit(2) - } else { - break loop - } case 'a' <= r && r <= 'z': case 'A' <= r && r <= 'Z': case r == '_': case '0' <= r && r <= '9': + case r == escNewl: default: break loop } @@ -805,9 +806,7 @@ loop: for p.newLit(r); r != utf8.RuneSelf; r = p.rune() { switch r { case '\\': // escaped byte follows - if r = p.rune(); r == '\n' { - p.discardLit(2) - } + p.rune() case '"', '`', '$': tok = _Lit break loop @@ -819,7 +818,7 @@ loop: if p.quote != paramExpExp { break loop } - case ':', '=', '%', '^', ',', '?', '!', '*': + case ':', '=', '%', '^', ',', '?', '!', '~', '*': if p.quote&allArithmExpr != 0 || p.quote == paramExpName { break loop } @@ -842,7 +841,7 @@ loop: } func (p *Parser) advanceLitNone(r rune) { - p.eqlOffs = 0 + p.eqlOffs = -1 tok := _LitWord loop: for p.newLit(r); r != utf8.RuneSelf; r = p.rune() { @@ -850,13 +849,11 @@ loop: case ' ', '\t', '\n', '\r', '&', '|', ';', '(', ')': break loop case '\\': // escaped byte follows - if r = p.rune(); r == '\n' { - p.discardLit(2) - } + p.rune() case '>', '<': - if p.peekByte('(') || !p.isLitRedir() { + if p.peekByte('(') { tok = _Lit - } else { + } else if p.isLitRedir() { tok = _LitRedir } break loop @@ -874,7 +871,7 @@ loop: break loop } case '=': - if p.eqlOffs == 0 { + if p.eqlOffs < 0 { p.eqlOffs = len(p.litBs) - 1 } case '[': @@ -896,7 +893,7 @@ loop: break loop case '\\': // escaped byte follows p.rune() - case '`', '$': + case escNewl, '`', '$': tok = _Lit break loop } @@ -913,12 +910,9 @@ func (p *Parser) advanceLitHdoc(r rune) { } } lStart := len(p.litBs) - 1 - if lStart < 0 { - return - } for ; ; r = p.rune() { switch r { - case '`', '$': + case escNewl, '`', '$': p.val = p.endLit() return case '\\': // escaped byte follows @@ -929,7 +923,7 @@ func (p *Parser) advanceLitHdoc(r rune) { p.val = p.endLit() return } - } else if bytes.HasPrefix(p.litBs[lStart:], p.hdocStop) { + } else if lStart >= 0 && bytes.HasPrefix(p.litBs[lStart:], p.hdocStop) { p.val = p.endLit()[:lStart] if p.val == "" { p.tok = _Newl @@ -964,13 +958,13 @@ func (p *Parser) quotedHdocWord() *Word { } } lStart := len(p.litBs) - 1 - if lStart < 0 { - return nil - } for r != utf8.RuneSelf && r != '\n' { + if r == escNewl { + p.litBs = append(p.litBs, '\\', '\n') + } r = p.rune() } - if bytes.HasPrefix(p.litBs[lStart:], p.hdocStop) { + if lStart >= 0 && bytes.HasPrefix(p.litBs[lStart:], p.hdocStop) { p.hdocStop = nil val := p.endLit()[:lStart] if val == "" { @@ -1070,7 +1064,9 @@ func testUnaryOp(val string) UnTestOperator { func testBinaryOp(val string) BinTestOperator { switch val { - case "==", "=": + case "=": + return TsMatchShort + case "==": return TsMatch case "!=": return TsNoMatch diff --git a/vendor/mvdan.cc/sh/syntax/nodes.go b/vendor/mvdan.cc/sh/v3/syntax/nodes.go similarity index 84% rename from vendor/mvdan.cc/sh/syntax/nodes.go rename to vendor/mvdan.cc/sh/v3/syntax/nodes.go index a4f473c5..e3d08b24 100644 --- a/vendor/mvdan.cc/sh/syntax/nodes.go +++ b/vendor/mvdan.cc/sh/v3/syntax/nodes.go @@ -23,19 +23,16 @@ type Node interface { 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] +func (f *File) Pos() Pos { return stmtsPos(f.Stmts, f.Last) } +func (f *File) End() Pos { return stmtsEnd(f.Stmts, f.Last) } + +func stmtsPos(stmts []*Stmt, last []Comment) Pos { + if len(stmts) > 0 { + s := stmts[0] sPos := s.Pos() if len(s.Comments) > 0 { if cPos := s.Comments[0].Pos(); sPos.After(cPos) { @@ -44,18 +41,18 @@ func (s StmtList) pos() Pos { } return sPos } - if len(s.Last) > 0 { - return s.Last[0].Pos() + if len(last) > 0 { + return last[0].Pos() } return Pos{} } -func (s StmtList) end() Pos { - if len(s.Last) > 0 { - return s.Last[len(s.Last)-1].End() +func stmtsEnd(stmts []*Stmt, last []Comment) Pos { + if len(last) > 0 { + return last[len(last)-1].End() } - if len(s.Stmts) > 0 { - s := s.Stmts[len(s.Stmts)-1] + if len(stmts) > 0 { + s := stmts[len(stmts)-1] sEnd := s.End() if len(s.Comments) > 0 { if cEnd := s.Comments[0].End(); cEnd.After(sEnd) { @@ -67,10 +64,6 @@ func (s StmtList) end() Pos { 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 @@ -100,9 +93,6 @@ func (p Pos) IsValid() bool { return p.line > 0 } // 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) @@ -198,11 +188,12 @@ func (*CoprocClause) commandNode() {} // 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. +// the argument (in the Value field) will be evaluated at run-time. This +// includes parameter expansions, which may expand to assignments or options. type Assign struct { - Append bool // += - Naked bool // without '=' - Name *Lit + Append bool // += + Naked bool // without '=' + Name *Lit // must be a valid name Index ArithmExpr // [i], ["k"] Value *Word // =val Array *ArrayExpr // =(arr) @@ -246,6 +237,7 @@ func (r *Redirect) Pos() Pos { } return r.OpPos } + func (r *Redirect) End() Pos { if r.Hdoc != nil { return r.Hdoc.End() @@ -281,74 +273,54 @@ func (c *CallExpr) End() Pos { // shell environment. type Subshell struct { Lparen, Rparen Pos - StmtList + + Stmts []*Stmt + Last []Comment } 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. +// scope. It is essentially a list of statements within curly braces. type Block struct { Lbrace, Rbrace Pos - StmtList + + Stmts []*Stmt + Last []Comment } 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 + Position Pos // position of the starting "if", "elif", or "else" token + ThenPos Pos // position of "then", empty if this is an "else" + FiPos Pos // position of "fi", shared with .Else if non-nil - Cond StmtList - Then StmtList - Else StmtList + Cond []*Stmt + CondLast []Comment + Then []*Stmt + ThenLast []Comment - ElseComments []Comment // comments on the "else" - FiComments []Comment // comments on the "fi" + Else *IfClause // if non-nil, an "elif" or an "else" + + Last []Comment // comments on the first "elif", "else", or "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 -} +func (c *IfClause) Pos() Pos { return c.Position } +func (c *IfClause) End() Pos { return posAddCol(c.FiPos, 2) } // WhileClause represents a while or an until clause. type WhileClause struct { WhilePos, DoPos, DonePos Pos Until bool - Cond StmtList - Do StmtList + + Cond []*Stmt + CondLast []Comment + Do []*Stmt + DoLast []Comment } func (w *WhileClause) Pos() Pos { return w.WhilePos } @@ -360,7 +332,9 @@ type ForClause struct { ForPos, DoPos, DonePos Pos Select bool Loop Loop - Do StmtList + + Do []*Stmt + DoLast []Comment } func (f *ForClause) Pos() Pos { return f.ForPos } @@ -474,6 +448,7 @@ func (*CmdSubst) wordPartNode() {} func (*ArithmExp) wordPartNode() {} func (*ProcSubst) wordPartNode() {} func (*ExtGlob) wordPartNode() {} +func (*BraceExp) wordPartNode() {} // Lit represents a string literal. // @@ -500,29 +475,24 @@ 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 + Left, Right 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) -} +func (q *DblQuoted) Pos() Pos { return q.Left } +func (q *DblQuoted) End() Pos { return posAddCol(q.Right, 1) } // CmdSubst represents a command substitution. type CmdSubst struct { Left, Right Pos - StmtList - TempFile bool // mksh's ${ foo;} - ReplyVar bool // mksh's ${|foo;} + Stmts []*Stmt + Last []Comment + + Backquotes bool // deprecated `foo` + TempFile bool // mksh's ${ foo;} + ReplyVar bool // mksh's ${|foo;} } func (c *CmdSubst) Pos() Pos { return c.Left } @@ -531,16 +501,17 @@ 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 + + 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 } @@ -583,7 +554,8 @@ type ArithmExp struct { Left, Right Pos Bracket bool // deprecated $[expr] form Unsigned bool // mksh's $((# expr)) - X ArithmExpr + + X ArithmExpr } func (a *ArithmExp) Pos() Pos { return a.Left } @@ -600,7 +572,8 @@ func (a *ArithmExp) End() Pos { type ArithmCmd struct { Left, Right Pos Unsigned bool // mksh's ((# expr)) - X ArithmExpr + + X ArithmExpr } func (a *ArithmCmd) Pos() Pos { return a.Left } @@ -625,8 +598,8 @@ func (*Word) arithmExprNode() {} // 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. +// Op==TernQuest, Y will be a *BinaryArithm with Op==TernColon. Op can only be +// TernColon in that scenario. type BinaryArithm struct { OpPos Pos Op BinAritOperator @@ -665,7 +638,8 @@ func (u *UnaryArithm) End() Pos { // ParenArithm represents an arithmetic expression within parentheses. type ParenArithm struct { Lparen, Rparen Pos - X ArithmExpr + + X ArithmExpr } func (p *ParenArithm) Pos() Pos { return p.Lparen } @@ -674,9 +648,10 @@ 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 + + Word *Word + Items []*CaseItem + Last []Comment } func (c *CaseClause) Pos() Pos { return c.Case } @@ -688,7 +663,9 @@ type CaseItem struct { OpPos Pos // unset if it was finished by "esac" Comments []Comment Patterns []*Word - StmtList + + Stmts []*Stmt + Last []Comment } func (c *CaseItem) Pos() Pos { return c.Patterns[0].Pos() } @@ -696,7 +673,7 @@ func (c *CaseItem) End() Pos { if c.OpPos.IsValid() { return posAddCol(c.OpPos, len(c.Op.String())) } - return c.StmtList.end() + return stmtsEnd(c.Stmts, c.Last) } // TestClause represents a Bash extended test clause. @@ -704,7 +681,8 @@ func (c *CaseItem) End() Pos { // This node will only appear in LangBash and LangMirBSDKorn. type TestClause struct { Left, Right Pos - X TestExpr + + X TestExpr } func (t *TestClause) Pos() Pos { return t.Left } @@ -747,7 +725,8 @@ func (u *UnaryTest) End() Pos { return u.X.End() } // ParenTest represents a test expression within parentheses. type ParenTest struct { Lparen, Rparen Pos - X TestExpr + + X TestExpr } func (p *ParenTest) Pos() Pos { return p.Lparen } @@ -755,22 +734,21 @@ func (p *ParenTest) End() Pos { return posAddCol(p.Rparen, 1) } // DeclClause represents a Bash declare clause. // +// Args can contain a mix of regular and naked assignments. The naked +// assignments can represent either options or variable names. +// // 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 + Args []*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) + if len(d.Args) > 0 { + return d.Args[len(d.Args)-1].End() } return d.Variant.End() } @@ -780,8 +758,9 @@ func (d *DeclClause) End() Pos { // This node will only appear with LangBash. type ArrayExpr struct { Lparen, Rparen Pos - Elems []*ArrayElem - Last []Comment + + Elems []*ArrayElem + Last []Comment } func (a *ArrayExpr) Pos() Pos { return a.Lparen } @@ -804,6 +783,7 @@ func (a *ArrayElem) Pos() Pos { } return a.Value.Pos() } + func (a *ArrayElem) End() Pos { if a.Value != nil { return a.Value.End() @@ -830,7 +810,9 @@ func (e *ExtGlob) End() Pos { return posAddCol(e.Pattern.End(), 1) } type ProcSubst struct { OpPos, Rparen Pos Op ProcOperator - StmtList + + Stmts []*Stmt + Last []Comment } func (s *ProcSubst) Pos() Pos { return s.OpPos } @@ -859,7 +841,7 @@ func (c *TimeClause) End() Pos { // This node will only appear with LangBash. type CoprocClause struct { Coproc Pos - Name *Lit + Name *Word Stmt *Stmt } @@ -877,6 +859,23 @@ type LetClause struct { func (l *LetClause) Pos() Pos { return l.Let } func (l *LetClause) End() Pos { return l.Exprs[len(l.Exprs)-1].End() } +// BraceExp represents a Bash brace expression, such as "{x,y}" or "{1..10}". +// +// This node will only appear as a result of SplitBraces. +type BraceExp struct { + Sequence bool // {x..y[..incr]} instead of {x,y[,...]} + Chars bool // sequence is of chars, not numbers (TODO: remove) + Elems []*Word +} + +func (b *BraceExp) Pos() Pos { + return posAddCol(b.Elems[0].Pos(), -1) +} + +func (b *BraceExp) End() Pos { + return posAddCol(wordLastEnd(b.Elems), 1) +} + func wordLastEnd(ws []*Word) Pos { if len(ws) == 0 { return Pos{} diff --git a/vendor/mvdan.cc/sh/syntax/parser.go b/vendor/mvdan.cc/sh/v3/syntax/parser.go similarity index 90% rename from vendor/mvdan.cc/sh/syntax/parser.go rename to vendor/mvdan.cc/sh/v3/syntax/parser.go index 1fca3c19..bce05999 100644 --- a/vendor/mvdan.cc/sh/syntax/parser.go +++ b/vendor/mvdan.cc/sh/v3/syntax/parser.go @@ -12,9 +12,16 @@ import ( "unicode/utf8" ) +// ParserOption is a function which can be passed to NewParser +// to alter its behaviour. To apply option to existing Parser +// call it directly, for example syntax.KeepComments(true)(parser). +type ParserOption func(*Parser) + // KeepComments makes the parser parse comments and attach them to // nodes, as opposed to discarding them. -func KeepComments(p *Parser) { p.keepComments = true } +func KeepComments(enabled bool) ParserOption { + return func(p *Parser) { p.keepComments = enabled } +} type LangVariant int @@ -26,7 +33,7 @@ const ( // Variant changes the shell language variant that the parser will // accept. -func Variant(l LangVariant) func(*Parser) { +func Variant(l LangVariant) ParserOption { return func(p *Parser) { p.lang = l } } @@ -55,7 +62,7 @@ func (l LangVariant) String() string { // // The match is done by prefix, so the example above will also act on // "foo $$bar". -func StopAt(word string) func(*Parser) { +func StopAt(word string) ParserOption { if len(word) > 4 { panic("stop word can't be over four bytes in size") } @@ -66,7 +73,7 @@ func StopAt(word string) func(*Parser) { } // NewParser allocates a new Parser and applies any number of options. -func NewParser(options ...func(*Parser)) *Parser { +func NewParser(options ...ParserOption) *Parser { p := &Parser{helperBuf: new(bytes.Buffer)} for _, opt := range options { opt(p) @@ -86,7 +93,7 @@ func (p *Parser) Parse(r io.Reader, name string) (*File, error) { p.src = r p.rune() p.next() - p.f.StmtList = p.stmtList() + p.f.Stmts, p.f.Last = p.stmtList() if p.err == nil { // EOF immediately after heredoc word so no newline to // trigger it @@ -126,7 +133,7 @@ func (w *wrappedReader) Read(p []byte) (n int, err error) { // If we lexed a newline for the first time, we just finished a line, so // we may need to give a callback for the edge cases below not covered // by Parser.Stmts. - if w.r == '\n' && w.npos.line > w.lastLine { + if (w.r == '\n' || w.r == escNewl) && w.npos.line > w.lastLine { if w.Incomplete() { // Incomplete statement; call back to print "> ". if !w.fn(w.accumulated) { @@ -242,6 +249,19 @@ func (p *Parser) Document(r io.Reader) (*Word, error) { return w, p.err } +// Arithmetic parses a single arithmetic expression. That is, as if the input +// were within the $(( and )) tokens. +func (p *Parser) Arithmetic(r io.Reader) (ArithmExpr, error) { + p.reset() + p.f = &File{} + p.src = r + p.rune() + p.quote = arithmExpr + p.next() + expr := p.arithmExpr(0, false, false) + return expr, p.err +} + // Parser holds the internal state of the parsing mechanism of a // program. type Parser struct { @@ -314,6 +334,11 @@ type Parser struct { litBs []byte } +// Incomplete reports whether the parser is waiting to read more bytes because +// it needs to finish properly parsing a statement. +// +// It is only safe to call while the parser is blocked on a read. For an example +// use case, see the documentation for Parser.Interactive. func (p *Parser) Incomplete() bool { // If we're in a quote state other than noState, we're parsing a node // such as a double-quoted string. @@ -578,16 +603,16 @@ func (p *Parser) followRsrv(lpos Pos, left, val string) Pos { return pos } -func (p *Parser) followStmts(left string, lpos Pos, stops ...string) StmtList { +func (p *Parser) followStmts(left string, lpos Pos, stops ...string) ([]*Stmt, []Comment) { if p.got(semicolon) { - return StmtList{} + return nil, nil } newLine := p.got(_Newl) - sl := p.stmtList(stops...) - if len(sl.Stmts) < 1 && !newLine { + stmts, last := p.stmtList(stops...) + if len(stmts) < 1 && !newLine { p.followErr(lpos, left, "a statement list") } - return sl + return stmts, last } func (p *Parser) followWordTok(tok token, pos Pos) *Word { @@ -642,12 +667,22 @@ func (p *Parser) errPass(err error) { } } +// IsIncomplete reports whether a Parser error could have been avoided with +// extra input bytes. For example, if an io.EOF was encountered while there was +// an unclosed quote or parenthesis. +func IsIncomplete(err error) bool { + perr, ok := err.(ParseError) + return ok && perr.Incomplete +} + // ParseError represents an error found when parsing a source file, from which // the parser cannot recover. type ParseError struct { Filename string Pos Text string + + Incomplete bool } func (e ParseError) Error() string { @@ -691,9 +726,10 @@ func (e LangError) Error() string { func (p *Parser) posErr(pos Pos, format string, a ...interface{}) { p.errPass(ParseError{ - Filename: p.f.Name, - Pos: pos, - Text: fmt.Sprintf(format, a...), + Filename: p.f.Name, + Pos: pos, + Text: fmt.Sprintf(format, a...), + Incomplete: p.tok == _EOF && p.Incomplete(), }) } @@ -756,12 +792,14 @@ loop: } } -func (p *Parser) stmtList(stops ...string) (sl StmtList) { +func (p *Parser) stmtList(stops ...string) ([]*Stmt, []Comment) { + var stmts []*Stmt + var last []Comment fn := func(s *Stmt) bool { - if sl.Stmts == nil { - sl.Stmts = p.stList() + if stmts == nil { + stmts = p.stList() } - sl.Stmts = append(sl.Stmts, s) + stmts = append(stmts, s) return true } p.stmts(fn, stops...) @@ -785,9 +823,9 @@ func (p *Parser) stmtList(stops ...string) (sl StmtList) { split = i } } - sl.Last = p.accComs[:split] + last = p.accComs[:split] p.accComs = p.accComs[split:] - return + return stmts, last } func (p *Parser) invalidStmtStart() { @@ -867,7 +905,7 @@ func (p *Parser) wordPart() WordPart { old := p.preNested(subCmd) p.rune() // don't tokenize '|' p.next() - cs.StmtList = p.stmtList("}") + cs.Stmts, cs.Last = p.stmtList("}") p.postNested(old) pos, ok := p.gotRsrv("}") if !ok { @@ -912,7 +950,7 @@ func (p *Parser) wordPart() WordPart { cs := &CmdSubst{Left: p.pos} old := p.preNested(subCmd) p.next() - cs.StmtList = p.stmtList() + cs.Stmts, cs.Last = p.stmtList() p.postNested(old) cs.Right = p.matched(cs.Left, leftParen, rightParen) return cs @@ -936,15 +974,9 @@ func (p *Parser) wordPart() WordPart { pe.Param = p.getLit() if pe.Param != nil && pe.Param.Value == "" { l := p.lit(pe.Dollar, "$") - if p.val == "" { - // e.g. "$\\\n" followed by a closing double - // quote, so we need the next token. - p.next() - } else { - // e.g. "$\\\"" within double quotes, so we must - // keep the rest of the literal characters. - l.ValueEnd = posAddCol(l.ValuePos, 1) - } + // e.g. "$\\\"" within double quotes, so we must + // keep the rest of the literal characters. + l.ValueEnd = posAddCol(l.ValuePos, 1) return l } return pe @@ -953,7 +985,7 @@ func (p *Parser) wordPart() WordPart { ps := &ProcSubst{Op: ProcOperator(p.tok), OpPos: p.pos} old := p.preNested(subCmd) p.next() - ps.StmtList = p.stmtList() + ps.Stmts, ps.Last = p.stmtList() p.postNested(old) ps.Rparen = p.matched(ps.OpPos, token(ps.Op), rightParen) return ps @@ -977,8 +1009,11 @@ func (p *Parser) wordPart() WordPart { p.rune() p.next() return sq + case escNewl: + p.litBs = append(p.litBs, '\\', '\n') case utf8.RuneSelf: - p.posErr(sq.Pos(), "reached EOF without closing quote %s", sglQuote) + p.tok = _EOF + p.quoteErr(sq.Pos(), sglQuote) return nil } } @@ -993,7 +1028,7 @@ func (p *Parser) wordPart() WordPart { return nil } p.ensureNoNested() - cs := &CmdSubst{Left: p.pos} + cs := &CmdSubst{Left: p.pos, Backquotes: true} old := p.preNested(subCmdBckquo) p.openBquotes++ @@ -1002,7 +1037,7 @@ func (p *Parser) wordPart() WordPart { p.rune() p.next() - cs.StmtList = p.stmtList() + cs.Stmts, cs.Last = p.stmtList() if p.tok == bckQuote && p.lastBquoteEsc < p.openBquotes-1 { // e.g. found ` before the nested backquote \` was closed. p.tok = _EOF @@ -1051,12 +1086,13 @@ func (p *Parser) wordPart() WordPart { } func (p *Parser) dblQuoted() *DblQuoted { - q := &DblQuoted{Position: p.pos, Dollar: p.tok == dollDblQuote} + q := &DblQuoted{Left: p.pos, Dollar: p.tok == dollDblQuote} old := p.quote p.quote = dblQuotes p.next() q.Parts = p.wordParts() p.quote = old + q.Right = p.pos if !p.got(dblQuote) { p.quoteErr(q.Pos(), dblQuote) } @@ -1072,7 +1108,7 @@ func arithmOpLevel(op BinAritOperator) int { return 1 case Assgn: return 2 - case Quest, Colon: + case TernQuest, TernColon: return 3 case AndArit, OrArit: return 4 @@ -1149,7 +1185,7 @@ func (p *Parser) arithmExpr(level int, compact, tern bool) ArithmExpr { X: left, } switch b.Op { - case Colon: + case TernColon: if !tern { p.posErr(b.Pos(), "ternary operator missing ? before :") } @@ -1162,12 +1198,12 @@ func (p *Parser) arithmExpr(level int, compact, tern bool) ArithmExpr { if p.next(); compact && p.spaced { p.followErrExp(b.OpPos, b.Op.String()) } - b.Y = p.arithmExpr(newLevel, compact, b.Op == Quest) + b.Y = p.arithmExpr(newLevel, compact, b.Op == TernQuest) if b.Y == nil { p.followErrExp(b.OpPos, b.Op.String()) } - if b.Op == Quest { - if b2, ok := b.Y.(*BinaryArithm); !ok || b2.Op != Colon { + if b.Op == TernQuest { + if b2, ok := b.Y.(*BinaryArithm); !ok || b2.Op != TernColon { p.posErr(b.Pos(), "ternary operator missing : after ?") } } @@ -1193,7 +1229,7 @@ func (p *Parser) arithmExprBase(compact bool) ArithmExpr { p.got(_Newl) var x ArithmExpr switch p.tok { - case exclMark: + case exclMark, tilde: ue := &UnaryArithm{OpPos: p.pos, Op: UnAritOperator(p.tok)} p.next() if ue.X = p.arithmExprBase(compact); ue.X == nil { @@ -1317,9 +1353,10 @@ func (p *Parser) paramExp() *ParamExp { p.next() case quest, minus: if pe.Length && p.r != '}' { - // actually ${#-default}, not ${#-}; error below + // actually ${#-default}, not ${#-}; fix the ambiguity pe.Length = false - pe.Param = p.lit(p.pos, "#") + pe.Param = p.lit(posAddCol(p.pos, -1), "#") + pe.Param.ValueEnd = p.pos break } fallthrough @@ -1542,9 +1579,13 @@ func (p *Parser) getAssign(needEqual bool) *Assign { p.rune() p.pos = posAddCol(p.pos, 1) as.Index = p.eitherIndex() - if !needEqual && (p.spaced || stopToken(p.tok)) { - as.Naked = true - return as + if p.spaced || stopToken(p.tok) { + if needEqual { + p.followErr(as.Pos(), "a[b]", "=") + } else { + as.Naked = true + return as + } } if len(p.val) > 0 && p.val[0] == '+' { as.Append = true @@ -1600,7 +1641,7 @@ func (p *Parser) getAssign(needEqual bool) *Assign { // TODO: support [index]=[ default: p.curErr("array element values must be words") - break + return nil } } if len(p.accComs) > 0 { @@ -1689,14 +1730,14 @@ func (p *Parser) getStmt(readEnd, binCmd, fnBody bool) *Stmt { p.posErr(s.Pos(), `cannot negate a command multiple times`) } } - if s = p.gotStmtPipe(s); s == nil || p.err != nil { + if s = p.gotStmtPipe(s, false); s == nil || p.err != nil { return nil } // instead of using recursion, iterate manually for p.tok == andAnd || p.tok == orOr { - // left associativity: in a list of BinaryCmds, the - // right recursion should only read a single element if binCmd { + // left associativity: in a list of BinaryCmds, the + // right recursion should only read a single element return s } b := &BinaryCmd{ @@ -1740,7 +1781,7 @@ func (p *Parser) getStmt(readEnd, binCmd, fnBody bool) *Stmt { return s } -func (p *Parser) gotStmtPipe(s *Stmt) *Stmt { +func (p *Parser) gotStmtPipe(s *Stmt, binCmd bool) *Stmt { s.Comments, p.accComs = p.accComs, nil switch p.tok { case _LitWord: @@ -1862,17 +1903,22 @@ func (p *Parser) gotStmtPipe(s *Stmt) *Stmt { for p.peekRedir() { p.doRedirect(s) } - switch p.tok { - case orAnd: - if p.lang == LangMirBSDKorn { + // instead of using recursion, iterate manually + for p.tok == or || p.tok == orAnd { + if binCmd { + // left associativity: in a list of BinaryCmds, the + // right recursion should only read a single element + return s + } + if p.tok == orAnd && p.lang == LangMirBSDKorn { + // No need to check for LangPOSIX, as on that language + // we parse |& as two tokens. break } - fallthrough - case or: b := &BinaryCmd{OpPos: p.pos, Op: BinCmdOperator(p.tok), X: s} p.next() p.got(_Newl) - if b.Y = p.gotStmtPipe(p.stmt(p.pos)); b.Y == nil || p.err != nil { + if b.Y = p.gotStmtPipe(p.stmt(p.pos), true); b.Y == nil || p.err != nil { p.followErr(b.OpPos, b.Op.String(), "a statement") break } @@ -1890,7 +1936,7 @@ func (p *Parser) subshell(s *Stmt) { sub := &Subshell{Lparen: p.pos} old := p.preNested(subCmd) p.next() - sub.StmtList = p.stmtList() + sub.Stmts, sub.Last = p.stmtList() p.postNested(old) sub.Rparen = p.matched(sub.Lparen, leftParen, rightParen) s.Cmd = sub @@ -1914,7 +1960,7 @@ func (p *Parser) arithmExpCmd(s *Stmt) { func (p *Parser) block(s *Stmt) { b := &Block{Lbrace: p.pos} p.next() - b.StmtList = p.stmtList("}") + b.Stmts, b.Last = p.stmtList("}") pos, ok := p.gotRsrv("}") b.Rbrace = pos if !ok { @@ -1924,37 +1970,39 @@ func (p *Parser) block(s *Stmt) { } func (p *Parser) ifClause(s *Stmt) { - rif := &IfClause{IfPos: p.pos} + rootIf := &IfClause{Position: p.pos} p.next() - rif.Cond = p.followStmts("if", rif.IfPos, "then") - rif.ThenPos = p.followRsrv(rif.IfPos, "if ", "then") - rif.Then = p.followStmts("then", rif.ThenPos, "fi", "elif", "else") - curIf := rif + rootIf.Cond, rootIf.CondLast = p.followStmts("if", rootIf.Position, "then") + rootIf.ThenPos = p.followRsrv(rootIf.Position, "if ", "then") + rootIf.Then, rootIf.ThenLast = p.followStmts("then", rootIf.ThenPos, "fi", "elif", "else") + curIf := rootIf for p.tok == _LitWord && p.val == "elif" { - elf := &IfClause{IfPos: p.pos, Elif: true} - s := p.stmt(elf.IfPos) - s.Cmd = elf - s.Comments = p.accComs + elf := &IfClause{Position: p.pos} + curIf.Last = p.accComs p.accComs = nil p.next() - elf.Cond = p.followStmts("elif", elf.IfPos, "then") - elf.ThenPos = p.followRsrv(elf.IfPos, "elif ", "then") - elf.Then = p.followStmts("then", elf.ThenPos, "fi", "elif", "else") - curIf.ElsePos = elf.IfPos - curIf.Else.Stmts = []*Stmt{s} + elf.Cond, elf.CondLast = p.followStmts("elif", elf.Position, "then") + elf.ThenPos = p.followRsrv(elf.Position, "elif ", "then") + elf.Then, elf.ThenLast = p.followStmts("then", elf.ThenPos, "fi", "elif", "else") + curIf.Else = elf curIf = elf } if elsePos, ok := p.gotRsrv("else"); ok { - curIf.ElseComments = p.accComs + curIf.Last = p.accComs p.accComs = nil - curIf.ElsePos = elsePos - curIf.Else = p.followStmts("else", curIf.ElsePos, "fi") + els := &IfClause{Position: elsePos} + els.Then, els.ThenLast = p.followStmts("else", els.Position, "fi") + curIf.Else = els + curIf = els } - curIf.FiComments = p.accComs + curIf.Last = p.accComs p.accComs = nil - rif.FiPos = p.stmtEnd(rif, "if", "fi") - curIf.FiPos = rif.FiPos - s.Cmd = rif + rootIf.FiPos = p.stmtEnd(rootIf, "if", "fi") + for els := rootIf.Else; els != nil; els = els.Else { + // All the nested IfClauses share the same FiPos. + els.FiPos = rootIf.FiPos + } + s.Cmd = rootIf } func (p *Parser) whileClause(s *Stmt, until bool) { @@ -1966,9 +2014,9 @@ func (p *Parser) whileClause(s *Stmt, until bool) { rsrvCond = "until " } p.next() - wc.Cond = p.followStmts(rsrv, wc.WhilePos, "do") + wc.Cond, wc.CondLast = p.followStmts(rsrv, wc.WhilePos, "do") wc.DoPos = p.followRsrv(wc.WhilePos, rsrvCond, "do") - wc.Do = p.followStmts("do", wc.DoPos, "done") + wc.Do, wc.DoLast = p.followStmts("do", wc.DoPos, "done") wc.DonePos = p.stmtEnd(wc, rsrv, "done") s.Cmd = wc } @@ -1981,7 +2029,7 @@ func (p *Parser) forClause(s *Stmt) { s.Comments = append(s.Comments, p.accComs...) p.accComs = nil - fc.Do = p.followStmts("do", fc.DoPos, "done") + fc.Do, fc.DoLast = p.followStmts("do", fc.DoPos, "done") fc.DonePos = p.stmtEnd(fc, "for", "done") s.Cmd = fc } @@ -2045,7 +2093,7 @@ func (p *Parser) selectClause(s *Stmt) { p.next() fc.Loop = p.wordIter("select", fc.ForPos) fc.DoPos = p.followRsrv(fc.ForPos, "select foo [in words]", "do") - fc.Do = p.followStmts("do", fc.DoPos, "done") + fc.Do, fc.DoLast = p.followStmts("do", fc.DoPos, "done") fc.DonePos = p.stmtEnd(fc, "select", "done") s.Cmd = fc } @@ -2091,7 +2139,7 @@ func (p *Parser) caseItems(stop string) (items []*CaseItem) { } old := p.preNested(switchCase) p.next() - ci.StmtList = p.stmtList(stop) + ci.Stmts, ci.Last = p.stmtList(stop) p.postNested(old) switch p.tok { case dblSemicolon, semiAnd, dblSemiAnd, semiOr: @@ -2253,22 +2301,18 @@ func (p *Parser) testExprBase(ftok token, fpos Pos) TestExpr { func (p *Parser) declClause(s *Stmt) { ds := &DeclClause{Variant: p.lit(p.pos, p.val)} p.next() - for (p.tok == _LitWord || p.tok == _Lit) && - (p.val[0] == '-' || p.val[0] == '+') { - ds.Opts = append(ds.Opts, p.getWord()) - } for !stopToken(p.tok) && !p.peekRedir() { if p.hasValidIdent() { - ds.Assigns = append(ds.Assigns, p.getAssign(false)) + ds.Args = append(ds.Args, p.getAssign(false)) } else if p.eqlOffs > 0 { p.curErr("invalid var name") - } else if p.tok == _LitWord { - ds.Assigns = append(ds.Assigns, &Assign{ + } else if p.tok == _LitWord && ValidName(p.val) { + ds.Args = append(ds.Args, &Assign{ Naked: true, Name: p.getLit(), }) } else if w := p.getWord(); w != nil { - ds.Assigns = append(ds.Assigns, &Assign{ + ds.Args = append(ds.Args, &Assign{ Naked: true, Value: w, }) @@ -2300,7 +2344,7 @@ func (p *Parser) timeClause(s *Stmt) { if _, ok := p.gotRsrv("-p"); ok { tc.PosixFormat = true } - tc.Stmt = p.gotStmtPipe(p.stmt(p.pos)) + tc.Stmt = p.gotStmtPipe(p.stmt(p.pos), false) s.Cmd = tc } @@ -2308,26 +2352,25 @@ func (p *Parser) coprocClause(s *Stmt) { cc := &CoprocClause{Coproc: p.pos} if p.next(); isBashCompoundCommand(p.tok, p.val) { // has no name - cc.Stmt = p.gotStmtPipe(p.stmt(p.pos)) + cc.Stmt = p.gotStmtPipe(p.stmt(p.pos), false) s.Cmd = cc return } - cc.Name = p.getLit() - cc.Stmt = p.gotStmtPipe(p.stmt(p.pos)) + cc.Name = p.getWord() + cc.Stmt = p.gotStmtPipe(p.stmt(p.pos), false) if cc.Stmt == nil { if cc.Name == nil { p.posErr(cc.Coproc, "coproc clause requires a command") return } // name was in fact the stmt - cc.Stmt = p.stmt(cc.Name.ValuePos) - cc.Stmt.Cmd = p.call(p.word(p.wps(cc.Name))) + cc.Stmt = p.stmt(cc.Name.Pos()) + cc.Stmt.Cmd = p.call(cc.Name) cc.Name = nil } else if cc.Name != nil { if call, ok := cc.Stmt.Cmd.(*CallExpr); ok { // name was in fact the start of a call - call.Args = append([]*Word{p.word(p.wps(cc.Name))}, - call.Args...) + call.Args = append([]*Word{cc.Name}, call.Args...) cc.Name = nil } } diff --git a/vendor/mvdan.cc/sh/syntax/printer.go b/vendor/mvdan.cc/sh/v3/syntax/printer.go similarity index 80% rename from vendor/mvdan.cc/sh/syntax/printer.go rename to vendor/mvdan.cc/sh/v3/syntax/printer.go index 87e31c3f..2bb80e47 100644 --- a/vendor/mvdan.cc/sh/syntax/printer.go +++ b/vendor/mvdan.cc/sh/v3/syntax/printer.go @@ -9,27 +9,39 @@ import ( "fmt" "io" "strings" + "text/tabwriter" "unicode" ) +// PrinterOption is a function which can be passed to NewPrinter +// to alter its behaviour. To apply option to existing Printer +// call it directly, for example syntax.KeepPadding(true)(printer). +type PrinterOption func(*Printer) + // Indent sets the number of spaces used for indentation. If set to 0, // tabs will be used instead. -func Indent(spaces uint) func(*Printer) { +func Indent(spaces uint) PrinterOption { return func(p *Printer) { p.indentSpaces = spaces } } // BinaryNextLine will make binary operators appear on the next line // when a binary command, such as a pipe, spans multiple lines. A // backslash will be used. -func BinaryNextLine(p *Printer) { p.binNextLine = true } +func BinaryNextLine(enabled bool) PrinterOption { + return func(p *Printer) { p.binNextLine = enabled } +} // SwitchCaseIndent will make switch cases be indented. As such, switch // case bodies will be two levels deeper than the switch itself. -func SwitchCaseIndent(p *Printer) { p.swtCaseIndent = true } +func SwitchCaseIndent(enabled bool) PrinterOption { + return func(p *Printer) { p.swtCaseIndent = enabled } +} // SpaceRedirects will put a space after most redirection operators. The // exceptions are '>&', '<&', '>(', and '<('. -func SpaceRedirects(p *Printer) { p.spaceRedirects = true } +func SpaceRedirects(enabled bool) PrinterOption { + return func(p *Printer) { p.spaceRedirects = enabled } +} // KeepPadding will keep most nodes and tokens in the same column that // they were in the original source. This allows the user to decide how @@ -38,25 +50,31 @@ func SpaceRedirects(p *Printer) { p.spaceRedirects = true } // Note that this feature is best-effort and will only keep the // alignment stable, so it may need some human help the first time it is // run. -func KeepPadding(p *Printer) { - p.keepPadding = true - p.cols.Writer = p.bufWriter.(*bufio.Writer) - p.bufWriter = &p.cols +func KeepPadding(enabled bool) PrinterOption { + return func(p *Printer) { + // TODO: support setting this option to false. + if enabled { + p.keepPadding = true + p.cols.Writer = p.bufWriter.(*bufio.Writer) + p.bufWriter = &p.cols + } + } } // Minify will print programs in a way to save the most bytes possible. // For example, indentation and comments are skipped, and extra // whitespace is avoided when possible. -func Minify(p *Printer) { p.minify = true } +func Minify(enabled bool) PrinterOption { + return func(p *Printer) { p.minify = enabled } +} // NewPrinter allocates a new Printer and applies any number of options. -func NewPrinter(options ...func(*Printer)) *Printer { +func NewPrinter(opts ...PrinterOption) *Printer { p := &Printer{ - bufWriter: bufio.NewWriter(nil), - lenPrinter: new(Printer), - tabsPrinter: new(Printer), + bufWriter: bufio.NewWriter(nil), + tabWriter: new(tabwriter.Writer), } - for _, opt := range options { + for _, opt := range opts { opt(p) } return p @@ -70,26 +88,53 @@ func NewPrinter(options ...func(*Printer)) *Printer { // *File is used. func (p *Printer) Print(w io.Writer, node Node) error { p.reset() + + // TODO: consider adding a raw mode to skip the tab writer, much like in + // go/printer. + twmode := tabwriter.DiscardEmptyColumns | tabwriter.StripEscape + tabwidth := 8 + if p.indentSpaces == 0 { + // indenting with tabs + twmode |= tabwriter.TabIndent + } else { + // indenting with spaces + tabwidth = int(p.indentSpaces) + } + p.tabWriter.Init(w, 0, tabwidth, 1, ' ', twmode) + w = p.tabWriter + p.bufWriter.Reset(w) switch x := node.(type) { case *File: - p.stmtList(x.StmtList) + p.stmtList(x.Stmts, x.Last) p.newline(x.End()) case *Stmt: - p.stmtList(StmtList{Stmts: []*Stmt{x}}) + p.stmtList([]*Stmt{x}, nil) case Command: p.line = x.Pos().Line() p.command(x, nil) case *Word: + p.line = x.Pos().Line() p.word(x) case WordPart: + p.line = x.Pos().Line() p.wordPart(x, nil) default: return fmt.Errorf("unsupported node type: %T", x) } p.flushHeredocs() p.flushComments() - return p.bufWriter.Flush() + + // flush the writers + if err := p.bufWriter.Flush(); err != nil { + return err + } + if tw, _ := w.(*tabwriter.Writer); tw != nil { + if err := tw.Flush(); err != nil { + return err + } + } + return nil } type bufWriter interface { @@ -140,7 +185,8 @@ func (c *colCounter) Reset(w io.Writer) { // program. type Printer struct { bufWriter - cols colCounter + tabWriter *tabwriter.Writer + cols colCounter indentSpaces uint binNextLine bool @@ -153,8 +199,6 @@ type Printer struct { wantNewline bool wroteSemi bool - commentPadding uint - // pendingComments are any comments in the current line or statement // that we have yet to print. This is useful because that way, we can // ensure that all comments are written immediately before a newline. @@ -180,17 +224,12 @@ type Printer struct { // pendingHdocs is the list of pending heredocs to write. pendingHdocs []*Redirect - // used in stmtCols to align comments - lenPrinter *Printer - lenCounter byteCounter - // used when printing <<- heredocs with tab indentation tabsPrinter *Printer } func (p *Printer) reset() { p.wantSpace, p.wantNewline = false, false - p.commentPadding = 0 p.pendingComments = p.pendingComments[:0] // minification uses its own newline logic @@ -272,6 +311,23 @@ func (p *Printer) semiOrNewl(s string, pos Pos) { p.wantSpace = true } +func (p *Printer) writeLit(s string) { + if !strings.Contains(s, "\t") { + p.WriteString(s) + return + } + p.WriteByte('\xff') + for i := 0; i < len(s); i++ { + b := s[i] + if b != '\t' { + p.WriteByte(b) + continue + } + p.WriteByte(b) + } + p.WriteByte('\xff') +} + func (p *Printer) incLevel() { inc := false if p.level <= p.lastLevel || len(p.levelIncs) == 0 { @@ -299,9 +355,11 @@ func (p *Printer) indent() { switch { case p.level == 0: case p.indentSpaces == 0: + p.WriteByte('\xff') for i := uint(0); i < p.level; i++ { p.WriteByte('\t') } + p.WriteByte('\xff') default: p.spaces(p.indentSpaces * p.level) } @@ -345,29 +403,31 @@ func (p *Printer) flushHeredocs() { p.line++ p.WriteByte('\n') p.wantNewline, p.wantSpace = false, false - if r.Op == DashHdoc && p.indentSpaces == 0 && - !p.minify && p.tabsPrinter != nil { + if r.Op == DashHdoc && p.indentSpaces == 0 && !p.minify { if r.Hdoc != nil { extra := extraIndenter{ bufWriter: p.bufWriter, baseIndent: int(p.level + 1), firstIndent: -1, } - *p.tabsPrinter = Printer{ + p.tabsPrinter = &Printer{ bufWriter: &extra, + line: r.Hdoc.Pos().Line(), } - p.tabsPrinter.line = r.Hdoc.Pos().Line() p.tabsPrinter.word(r.Hdoc) p.indent() - p.line = r.Hdoc.End().Line() } else { p.indent() } } else if r.Hdoc != nil { p.word(r.Hdoc) - p.line = r.Hdoc.End().Line() } p.unquotedWord(r.Word) + if r.Hdoc != nil { + // Overwrite p.line, since printing r.Word again can set + // p.line to the beginning of the heredoc again. + p.line = r.Hdoc.End().Line() + } p.wantSpace = false } p.level = newLevel @@ -433,7 +493,7 @@ func (p *Printer) flushComments() { if p.keepPadding { p.spacePad(c.Pos()) } else { - p.spaces(p.commentPadding + 1) + p.WriteByte('\t') } } // don't go back one line, which may happen in some edge cases @@ -441,7 +501,7 @@ func (p *Printer) flushComments() { p.line = cline } p.WriteByte('#') - p.WriteString(strings.TrimRightFunc(c.Text, unicode.IsSpace)) + p.writeLit(strings.TrimRightFunc(c.Text, unicode.IsSpace)) p.wantNewline = true } p.pendingComments = nil @@ -454,26 +514,36 @@ func (p *Printer) comments(comments ...Comment) { p.pendingComments = append(p.pendingComments, comments...) } -func (p *Printer) wordParts(wps []WordPart) { - for i, n := range wps { +func (p *Printer) wordParts(wps []WordPart, quoted bool) { + for i, wp := range wps { var next WordPart if i+1 < len(wps) { next = wps[i+1] } - p.wordPart(n, next) + for wp.Pos().Line() > p.line { + if quoted { + // No extra spacing or indentation if quoted. + p.WriteString("\\\n") + p.line++ + } else { + p.bslashNewl() + } + } + p.wordPart(wp, next) + p.line = wp.End().Line() } } func (p *Printer) wordPart(wp, next WordPart) { switch x := wp.(type) { case *Lit: - p.WriteString(x.Value) + p.writeLit(x.Value) case *SglQuoted: if x.Dollar { p.WriteByte('$') } p.WriteByte('\'') - p.WriteString(x.Value) + p.writeLit(x.Value) p.WriteByte('\'') p.line = x.End().Line() case *DblQuoted: @@ -484,18 +554,18 @@ func (p *Printer) wordPart(wp, next WordPart) { case x.TempFile: p.WriteString("${") p.wantSpace = true - p.nestedStmts(x.StmtList, x.Right) + p.nestedStmts(x.Stmts, x.Last, x.Right) p.wantSpace = false p.semiRsrv("}", x.Right) case x.ReplyVar: p.WriteString("${|") - p.nestedStmts(x.StmtList, x.Right) + p.nestedStmts(x.Stmts, x.Last, x.Right) p.wantSpace = false p.semiRsrv("}", x.Right) default: p.WriteString("$(") p.wantSpace = len(x.Stmts) > 0 && startsWithLparen(x.Stmts[0]) - p.nestedStmts(x.StmtList, x.Right) + p.nestedStmts(x.Stmts, x.Last, x.Right) p.rightParen(x.Right) } case *ParamExp: @@ -527,7 +597,7 @@ func (p *Printer) wordPart(wp, next WordPart) { p.WriteString("))") case *ExtGlob: p.WriteString(x.Op.String()) - p.WriteString(x.Pattern.Value) + p.writeLit(x.Pattern.Value) p.WriteByte(')') case *ProcSubst: // avoid conflict with << and others @@ -535,7 +605,7 @@ func (p *Printer) wordPart(wp, next WordPart) { p.space() } p.WriteString(x.Op.String()) - p.nestedStmts(x.StmtList, x.Rparen) + p.nestedStmts(x.Stmts, x.Last, x.Rparen) p.rightParen(x.Rparen) } } @@ -546,8 +616,12 @@ func (p *Printer) dblQuoted(dq *DblQuoted) { } p.WriteByte('"') if len(dq.Parts) > 0 { - p.wordParts(dq.Parts) - p.line = dq.Parts[len(dq.Parts)-1].End().Line() + p.wordParts(dq.Parts, true) + } + // Add any trailing escaped newlines. + for p.line < dq.Right.Line() { + p.WriteString("\\\n") + p.line++ } p.WriteByte('"') } @@ -564,13 +638,13 @@ func (p *Printer) wroteIndex(index ArithmExpr) bool { func (p *Printer) paramExp(pe *ParamExp) { if pe.nakedIndex() { // arr[x] - p.WriteString(pe.Param.Value) + p.writeLit(pe.Param.Value) p.wroteIndex(pe.Index) return } if pe.Short { // $var p.WriteByte('$') - p.WriteString(pe.Param.Value) + p.writeLit(pe.Param.Value) return } // ${var...} @@ -583,7 +657,7 @@ func (p *Printer) paramExp(pe *ParamExp) { case pe.Excl: p.WriteByte('!') } - p.WriteString(pe.Param.Value) + p.writeLit(pe.Param.Value) p.wroteIndex(pe.Index) switch { case pe.Slice != nil: @@ -606,7 +680,7 @@ func (p *Printer) paramExp(pe *ParamExp) { p.word(pe.Repl.With) } case pe.Names != 0: - p.WriteString(pe.Names.String()) + p.writeLit(pe.Names.String()) case pe.Exp != nil: p.WriteString(pe.Exp.Op.String()) if pe.Exp.Word != nil { @@ -619,7 +693,7 @@ func (p *Printer) paramExp(pe *ParamExp) { func (p *Printer) loop(loop Loop) { switch x := loop.(type) { case *WordIter: - p.WriteString(x.Name.Value) + p.writeLit(x.Name.Value) if x.InPos.IsValid() { p.spacedString(" in", Pos{}) p.wordJoin(x.Items) @@ -702,7 +776,7 @@ func (p *Printer) testExpr(expr TestExpr) { } func (p *Printer) word(w *Word) { - p.wordParts(w.Parts) + p.wordParts(w.Parts, false) p.wantSpace = true } @@ -710,9 +784,9 @@ func (p *Printer) unquotedWord(w *Word) { for _, wp := range w.Parts { switch x := wp.(type) { case *SglQuoted: - p.WriteString(x.Value) + p.writeLit(x.Value) case *DblQuoted: - p.wordParts(x.Parts) + p.wordParts(x.Parts, true) case *Lit: for i := 0; i < len(x.Value); i++ { if b := x.Value[i]; b == '\\' { @@ -818,7 +892,7 @@ func (p *Printer) stmt(s *Stmt) { p.spacePad(r.Pos()) } if r.N != nil { - p.WriteString(r.N.Value) + p.writeLit(r.N.Value) } p.WriteString(r.Op.String()) if p.spaceRedirects && (r.Op != DplIn && r.Op != DplOut) { @@ -868,7 +942,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { p.spacePad(r.Pos()) } if r.N != nil { - p.WriteString(r.N.Value) + p.writeLit(r.N.Value) } p.WriteString(r.Op.String()) if p.spaceRedirects && (r.Op != DplIn && r.Op != DplOut) { @@ -883,15 +957,15 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { case *Block: p.WriteByte('{') p.wantSpace = true - p.nestedStmts(x.StmtList, x.Rbrace) + p.nestedStmts(x.Stmts, x.Last, x.Rbrace) p.semiRsrv("}", x.Rbrace) case *IfClause: p.ifClause(x, false) case *Subshell: p.WriteByte('(') p.wantSpace = len(x.Stmts) > 0 && startsWithLparen(x.Stmts[0]) - p.spacePad(x.StmtList.pos()) - p.nestedStmts(x.StmtList, x.Rparen) + p.spacePad(stmtsPos(x.Stmts, x.Last)) + p.nestedStmts(x.Stmts, x.Last, x.Rparen) p.wantSpace = false p.spacePad(x.Rparen) p.rightParen(x.Rparen) @@ -901,9 +975,9 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { } else { p.spacedString("while", x.Pos()) } - p.nestedStmts(x.Cond, Pos{}) + p.nestedStmts(x.Cond, x.CondLast, Pos{}) p.semiOrNewl("do", x.DoPos) - p.nestedStmts(x.Do, x.DonePos) + p.nestedStmts(x.Do, x.DoLast, x.DonePos) p.semiRsrv("done", x.DonePos) case *ForClause: if x.Select { @@ -913,7 +987,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { } p.loop(x.Loop) p.semiOrNewl("do", x.DoPos) - p.nestedStmts(x.Do, x.DonePos) + p.nestedStmts(x.Do, x.DoLast, x.DonePos) p.semiRsrv("done", x.DonePos) case *BinaryCmd: p.stmt(x.X) @@ -959,7 +1033,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { if x.RsrvWord { p.WriteString("function ") } - p.WriteString(x.Name.Value) + p.writeLit(x.Name.Value) p.WriteString("()") if !p.minify { p.space() @@ -987,9 +1061,12 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { p.casePatternJoin(ci.Patterns) p.WriteByte(')') p.wantSpace = !p.minify - sep := len(ci.Stmts) > 1 || ci.StmtList.pos().Line() > p.line || - (!ci.StmtList.empty() && ci.OpPos.Line() > ci.StmtList.end().Line()) - p.nestedStmts(ci.StmtList, ci.OpPos) + + bodyPos := stmtsPos(ci.Stmts, ci.Last) + bodyEnd := stmtsEnd(ci.Stmts, ci.Last) + sep := len(ci.Stmts) > 1 || bodyPos.Line() > p.line || + (bodyEnd.IsValid() && ci.OpPos.Line() > bodyEnd.Line()) + p.nestedStmts(ci.Stmts, ci.Last, ci.OpPos) p.level++ if !p.minify || i != len(x.Items)-1 { if sep { @@ -1023,11 +1100,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { p.spacedString("]]", x.Right) case *DeclClause: p.spacedString(x.Variant.Value, x.Pos()) - for _, w := range x.Opts { - p.space() - p.word(w) - } - p.assigns(x.Assigns) + p.assigns(x.Args) case *TimeClause: p.spacedString("time", x.Pos()) if x.PosixFormat { @@ -1040,7 +1113,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) { p.spacedString("coproc", x.Pos()) if x.Name != nil { p.space() - p.WriteString(x.Name.Value) + p.word(x.Name) } p.space() p.stmt(x.Stmt) @@ -1058,33 +1131,37 @@ func (p *Printer) ifClause(ic *IfClause, elif bool) { if !elif { p.spacedString("if", ic.Pos()) } - p.nestedStmts(ic.Cond, Pos{}) + p.nestedStmts(ic.Cond, ic.CondLast, Pos{}) p.semiOrNewl("then", ic.ThenPos) - p.nestedStmts(ic.Then, ic.bodyEndPos()) - - var left []Comment - for _, c := range ic.ElseComments { - if c.Pos().After(ic.ElsePos) { - left = append(left, c) - break - } - p.comments(c) + thenEnd := ic.FiPos + el := ic.Else + if el != nil { + thenEnd = el.Position } - if ic.FollowedByElif() { - s := ic.Else.Stmts[0] - p.comments(s.Comments...) - p.semiRsrv("elif", ic.ElsePos) - p.ifClause(s.Cmd.(*IfClause), true) + p.nestedStmts(ic.Then, ic.ThenLast, thenEnd) + + if el != nil && el.ThenPos.IsValid() { + p.comments(ic.Last...) + p.semiRsrv("elif", el.Position) + p.ifClause(el, true) return } - if !ic.Else.empty() { - p.semiRsrv("else", ic.ElsePos) + if el == nil { + p.comments(ic.Last...) + } else { + var left []Comment + for _, c := range ic.Last { + if c.Pos().After(el.Position) { + left = append(left, c) + break + } + p.comments(c) + } + p.semiRsrv("else", el.Position) p.comments(left...) - p.nestedStmts(ic.Else, ic.FiPos) - } else if ic.ElsePos.IsValid() { - p.line = ic.ElsePos.Line() + p.nestedStmts(el.Then, el.ThenLast, ic.FiPos) + p.comments(el.Last...) } - p.comments(ic.FiComments...) p.semiRsrv("fi", ic.FiPos) } @@ -1098,21 +1175,10 @@ func startsWithLparen(s *Stmt) bool { return false } -func (p *Printer) hasInline(s *Stmt) bool { - for _, c := range s.Comments { - if c.Pos().Line() == s.End().Line() { - return true - } - } - return false -} - -func (p *Printer) stmtList(sl StmtList) { +func (p *Printer) stmtList(stmts []*Stmt, last []Comment) { sep := p.wantNewline || - (len(sl.Stmts) > 0 && sl.Stmts[0].Pos().Line() > p.line) - inlineIndent := 0 - lastIndentedLine := uint(0) - for i, s := range sl.Stmts { + (len(stmts) > 0 && stmts[0].Pos().Line() > p.line) + for _, s := range stmts { pos := s.Pos() var midComs, endComs []Comment for _, c := range s.Comments { @@ -1120,7 +1186,7 @@ func (p *Printer) stmtList(sl StmtList) { endComs = append(endComs, c) break } - if c.Pos().After(s.Pos()) { + if c.Pos().After(pos) { midComs = append(midComs, c) continue } @@ -1130,72 +1196,17 @@ func (p *Printer) stmtList(sl StmtList) { p.newlines(pos) } p.line = pos.Line() - if !p.hasInline(s) { - inlineIndent = 0 - p.commentPadding = 0 - p.comments(midComs...) - p.stmt(s) - p.wantNewline = true - continue - } p.comments(midComs...) p.stmt(s) - if s.Pos().Line() > lastIndentedLine+1 { - inlineIndent = 0 - } - if inlineIndent == 0 { - for _, s2 := range sl.Stmts[i:] { - if !p.hasInline(s2) { - break - } - if l := p.stmtCols(s2); l > inlineIndent { - inlineIndent = l - } - } - } - if inlineIndent > 0 { - if l := p.stmtCols(s); l > 0 { - p.commentPadding = uint(inlineIndent - l) - } - lastIndentedLine = p.line - } p.comments(endComs...) p.wantNewline = true } - if len(sl.Stmts) == 1 && !sep { + if len(stmts) == 1 && !sep { p.wantNewline = false } - p.comments(sl.Last...) + p.comments(last...) } -type byteCounter int - -func (c *byteCounter) WriteByte(b byte) error { - switch { - case *c < 0: - case b == '\n': - *c = -1 - default: - *c++ - } - return nil -} -func (c *byteCounter) Write(p []byte) (int, error) { - return c.WriteString(string(p)) -} -func (c *byteCounter) WriteString(s string) (int, error) { - switch { - case *c < 0: - case strings.Contains(s, "\n"): - *c = -1 - default: - *c += byteCounter(len(s)) - } - return 0, nil -} -func (c *byteCounter) Reset(io.Writer) { *c = 0 } -func (c *byteCounter) Flush() error { return nil } - // extraIndenter ensures that all lines in a '<<-' heredoc body have at least // baseIndent leading tabs. Those that had more tab indentation than the first // heredoc line will keep that relative indentation. @@ -1213,8 +1224,20 @@ func (e *extraIndenter) WriteByte(b byte) error { if b != '\n' { return nil } - trimmed := bytes.TrimLeft(e.curLine, "\t") - lineIndent := len(e.curLine) - len(trimmed) + line := e.curLine + if bytes.HasPrefix(e.curLine, []byte("\xff")) { + // beginning a multiline sequence, with the leading escape + line = line[1:] + } + trimmed := bytes.TrimLeft(line, "\t") + if len(trimmed) == 1 { + // no tabs if this is an empty line, i.e. "\n" + e.bufWriter.Write(trimmed) + e.curLine = e.curLine[:0] + return nil + } + + lineIndent := len(line) - len(trimmed) if e.firstIndent < 0 { e.firstIndent = lineIndent e.firstChange = e.baseIndent - lineIndent @@ -1241,41 +1264,26 @@ func (e *extraIndenter) WriteString(s string) (int, error) { return len(s), nil } -// stmtCols reports the length that s will take when formatted in a -// single line. If it will span multiple lines, stmtCols will return -1. -func (p *Printer) stmtCols(s *Stmt) int { - if p.lenPrinter == nil { - return -1 // stmtCols call within stmtCols, bail - } - *p.lenPrinter = Printer{ - bufWriter: &p.lenCounter, - line: s.Pos().Line(), - } - p.lenPrinter.bufWriter.Reset(nil) - p.lenPrinter.stmt(s) - return int(p.lenCounter) -} - -func (p *Printer) nestedStmts(sl StmtList, closing Pos) { +func (p *Printer) nestedStmts(stmts []*Stmt, last []Comment, closing Pos) { p.incLevel() switch { - case len(sl.Stmts) > 1: + case len(stmts) > 1: // Force a newline if we find: // { stmt; stmt; } p.wantNewline = true - case closing.Line() > p.line && len(sl.Stmts) > 0 && - sl.end().Line() < closing.Line(): + case closing.Line() > p.line && len(stmts) > 0 && + stmtsEnd(stmts, last).Line() < closing.Line(): // Force a newline if we find: // { stmt // } p.wantNewline = true - case len(p.pendingComments) > 0 && len(sl.Stmts) > 0: + case len(p.pendingComments) > 0 && len(stmts) > 0: // Force a newline if we find: // for i in a b # stmt // do foo; done p.wantNewline = true } - p.stmtList(sl) + p.stmtList(stmts, last) if closing.IsValid() { p.flushComments() } @@ -1291,7 +1299,7 @@ func (p *Printer) assigns(assigns []*Assign) { p.spacePad(a.Pos()) } if a.Name != nil { - p.WriteString(a.Name.Value) + p.writeLit(a.Name.Value) p.wroteIndex(a.Index) if a.Append { p.WriteByte('+') diff --git a/vendor/mvdan.cc/sh/syntax/quotestate_string.go b/vendor/mvdan.cc/sh/v3/syntax/quotestate_string.go similarity index 65% rename from vendor/mvdan.cc/sh/syntax/quotestate_string.go rename to vendor/mvdan.cc/sh/v3/syntax/quotestate_string.go index bf17419e..a57d7c37 100644 --- a/vendor/mvdan.cc/sh/syntax/quotestate_string.go +++ b/vendor/mvdan.cc/sh/v3/syntax/quotestate_string.go @@ -4,6 +4,30 @@ package syntax import "strconv" +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[noState-1] + _ = x[subCmd-2] + _ = x[subCmdBckquo-4] + _ = x[dblQuotes-8] + _ = x[hdocWord-16] + _ = x[hdocBody-32] + _ = x[hdocBodyTabs-64] + _ = x[arithmExpr-128] + _ = x[arithmExprLet-256] + _ = x[arithmExprCmd-512] + _ = x[arithmExprBrack-1024] + _ = x[testRegexp-2048] + _ = x[switchCase-4096] + _ = x[paramExpName-8192] + _ = x[paramExpSlice-16384] + _ = x[paramExpRepl-32768] + _ = x[paramExpExp-65536] + _ = x[arrayElems-131072] +} + const _quoteState_name = "noStatesubCmdsubCmdBckquodblQuoteshdocWordhdocBodyhdocBodyTabsarithmExprarithmExprLetarithmExprCmdarithmExprBracktestRegexpswitchCaseparamExpNameparamExpSliceparamExpReplparamExpExparrayElems" var _quoteState_map = map[quoteState]string{ diff --git a/vendor/mvdan.cc/sh/syntax/simplify.go b/vendor/mvdan.cc/sh/v3/syntax/simplify.go similarity index 96% rename from vendor/mvdan.cc/sh/syntax/simplify.go rename to vendor/mvdan.cc/sh/v3/syntax/simplify.go index a7644716..1259fffe 100644 --- a/vendor/mvdan.cc/sh/syntax/simplify.go +++ b/vendor/mvdan.cc/sh/v3/syntax/simplify.go @@ -5,8 +5,8 @@ package syntax import "bytes" -// Simplify simplifies a given program and returns whether any changes -// were made. +// Simplify modifies a node to remove redundant pieces of syntax, and returns +// whether any changes were made. // // The changes currently applied are: // @@ -71,6 +71,10 @@ func (s *simplifier) visit(node Node) bool { case *BinaryTest: x.X = s.unquoteParams(x.X) x.X = s.removeNegateTest(x.X) + if x.Op == TsMatchShort { + s.modified = true + x.Op = TsMatch + } switch x.Op { case TsMatch, TsNoMatch: // unquoting enables globbing diff --git a/vendor/mvdan.cc/sh/v3/syntax/token_string.go b/vendor/mvdan.cc/sh/v3/syntax/token_string.go new file mode 100644 index 00000000..ab5c83ac --- /dev/null +++ b/vendor/mvdan.cc/sh/v3/syntax/token_string.go @@ -0,0 +1,149 @@ +// Code generated by "stringer -type token -linecomment -trimprefix _"; DO NOT EDIT. + +package syntax + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[illegalTok-0] + _ = x[_EOF-1] + _ = x[_Newl-2] + _ = x[_Lit-3] + _ = x[_LitWord-4] + _ = x[_LitRedir-5] + _ = x[sglQuote-6] + _ = x[dblQuote-7] + _ = x[bckQuote-8] + _ = x[and-9] + _ = x[andAnd-10] + _ = x[orOr-11] + _ = x[or-12] + _ = x[orAnd-13] + _ = x[dollar-14] + _ = x[dollSglQuote-15] + _ = x[dollDblQuote-16] + _ = x[dollBrace-17] + _ = x[dollBrack-18] + _ = x[dollParen-19] + _ = x[dollDblParen-20] + _ = x[leftBrack-21] + _ = x[dblLeftBrack-22] + _ = x[leftParen-23] + _ = x[dblLeftParen-24] + _ = x[rightBrace-25] + _ = x[rightBrack-26] + _ = x[rightParen-27] + _ = x[dblRightParen-28] + _ = x[semicolon-29] + _ = x[dblSemicolon-30] + _ = x[semiAnd-31] + _ = x[dblSemiAnd-32] + _ = x[semiOr-33] + _ = x[exclMark-34] + _ = x[tilde-35] + _ = x[addAdd-36] + _ = x[subSub-37] + _ = x[star-38] + _ = x[power-39] + _ = x[equal-40] + _ = x[nequal-41] + _ = x[lequal-42] + _ = x[gequal-43] + _ = x[addAssgn-44] + _ = x[subAssgn-45] + _ = x[mulAssgn-46] + _ = x[quoAssgn-47] + _ = x[remAssgn-48] + _ = x[andAssgn-49] + _ = x[orAssgn-50] + _ = x[xorAssgn-51] + _ = x[shlAssgn-52] + _ = x[shrAssgn-53] + _ = x[rdrOut-54] + _ = x[appOut-55] + _ = x[rdrIn-56] + _ = x[rdrInOut-57] + _ = x[dplIn-58] + _ = x[dplOut-59] + _ = x[clbOut-60] + _ = x[hdoc-61] + _ = x[dashHdoc-62] + _ = x[wordHdoc-63] + _ = x[rdrAll-64] + _ = x[appAll-65] + _ = x[cmdIn-66] + _ = x[cmdOut-67] + _ = x[plus-68] + _ = x[colPlus-69] + _ = x[minus-70] + _ = x[colMinus-71] + _ = x[quest-72] + _ = x[colQuest-73] + _ = x[assgn-74] + _ = x[colAssgn-75] + _ = x[perc-76] + _ = x[dblPerc-77] + _ = x[hash-78] + _ = x[dblHash-79] + _ = x[caret-80] + _ = x[dblCaret-81] + _ = x[comma-82] + _ = x[dblComma-83] + _ = x[at-84] + _ = x[slash-85] + _ = x[dblSlash-86] + _ = x[colon-87] + _ = x[tsExists-88] + _ = x[tsRegFile-89] + _ = x[tsDirect-90] + _ = x[tsCharSp-91] + _ = x[tsBlckSp-92] + _ = x[tsNmPipe-93] + _ = x[tsSocket-94] + _ = x[tsSmbLink-95] + _ = x[tsSticky-96] + _ = x[tsGIDSet-97] + _ = x[tsUIDSet-98] + _ = x[tsGrpOwn-99] + _ = x[tsUsrOwn-100] + _ = x[tsModif-101] + _ = x[tsRead-102] + _ = x[tsWrite-103] + _ = x[tsExec-104] + _ = x[tsNoEmpty-105] + _ = x[tsFdTerm-106] + _ = x[tsEmpStr-107] + _ = x[tsNempStr-108] + _ = x[tsOptSet-109] + _ = x[tsVarSet-110] + _ = x[tsRefVar-111] + _ = x[tsReMatch-112] + _ = x[tsNewer-113] + _ = x[tsOlder-114] + _ = x[tsDevIno-115] + _ = x[tsEql-116] + _ = x[tsNeq-117] + _ = x[tsLeq-118] + _ = x[tsGeq-119] + _ = x[tsLss-120] + _ = x[tsGtr-121] + _ = x[globQuest-122] + _ = x[globStar-123] + _ = x[globPlus-124] + _ = x[globAt-125] + _ = x[globExcl-126] +} + +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, 83, 85, 87, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 117, 120, 121, 123, 124, 126, 128, 130, 132, 134, 137, 140, 142, 145, 147, 149, 150, 152, 153, 155, 156, 158, 159, 161, 162, 164, 165, 167, 168, 170, 171, 173, 174, 175, 177, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 231, 234, 237, 240, 243, 246, 249, 252, 255, 257, 259, 261, 263, 265} + +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]] +} diff --git a/vendor/mvdan.cc/sh/v3/syntax/tokens.go b/vendor/mvdan.cc/sh/v3/syntax/tokens.go new file mode 100644 index 00000000..6a64b213 --- /dev/null +++ b/vendor/mvdan.cc/sh/v3/syntax/tokens.go @@ -0,0 +1,349 @@ +// Copyright (c) 2016, Daniel Martí +// 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 // ! + tilde // ~ + 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 ( + GlobZeroOrOne = GlobOperator(globQuest) + iota // ?( + GlobZeroOrMore // *( + GlobOneOrMore // +( + GlobOne // @( + GlobExcept // !( +) + +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 ( + AlternateUnset = ParExpOperator(plus) + iota // + + AlternateUnsetOrNull // :+ + DefaultUnset // - + DefaultUnsetOrNull // :- + ErrorUnset // ? + ErrorUnsetOrNull // :? + AssignUnset // = + AssignUnsetOrNull // := + RemSmallSuffix // % + RemLargeSuffix // %% + RemSmallPrefix // # + RemLargePrefix // ## + UpperFirst // ^ + UpperAll // ^^ + LowerFirst // , + LowerAll // ,, + OtherParamOps // @ +) + +type UnAritOperator token + +const ( + Not = UnAritOperator(exclMark) + iota // ! + BitNegation // ~ + 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) // , + TernQuest = BinAritOperator(quest) // ? + TernColon = 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 // -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 + TsNot = UnTestOperator(exclMark) // ! +) + +type BinTestOperator token + +const ( + TsReMatch = BinTestOperator(tsReMatch) + iota // =~ + TsNewer // -nt + TsOlder // -ot + TsDevIno // -ef + TsEql // -eq + TsNeq // -ne + TsLeq // -le + TsGeq // -ge + TsLss // -lt + TsGtr // -gt + AndTest = BinTestOperator(andAnd) // && + OrTest = BinTestOperator(orOr) // || + TsMatchShort = BinTestOperator(assgn) // = + 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() } diff --git a/vendor/mvdan.cc/sh/syntax/walk.go b/vendor/mvdan.cc/sh/v3/syntax/walk.go similarity index 90% rename from vendor/mvdan.cc/sh/syntax/walk.go rename to vendor/mvdan.cc/sh/v3/syntax/walk.go index 9ff07da8..19238836 100644 --- a/vendor/mvdan.cc/sh/syntax/walk.go +++ b/vendor/mvdan.cc/sh/v3/syntax/walk.go @@ -9,11 +9,11 @@ import ( "reflect" ) -func walkStmts(sl StmtList, f func(Node) bool) { - for _, s := range sl.Stmts { +func walkStmts(stmts []*Stmt, last []Comment, f func(Node) bool) { + for _, s := range stmts { Walk(s, f) } - for _, c := range sl.Last { + for _, c := range last { Walk(&c, f) } } @@ -35,7 +35,7 @@ func Walk(node Node, f func(Node) bool) { switch x := node.(type) { case *File: - walkStmts(x.StmtList, f) + walkStmts(x.Stmts, x.Last, f) case *Comment: case *Stmt: for _, c := range x.Comments { @@ -78,19 +78,21 @@ func Walk(node Node, f func(Node) bool) { } walkWords(x.Args, f) case *Subshell: - walkStmts(x.StmtList, f) + walkStmts(x.Stmts, x.Last, f) case *Block: - walkStmts(x.StmtList, f) + walkStmts(x.Stmts, x.Last, f) case *IfClause: - walkStmts(x.Cond, f) - walkStmts(x.Then, f) - walkStmts(x.Else, f) + walkStmts(x.Cond, x.CondLast, f) + walkStmts(x.Then, x.ThenLast, f) + if x.Else != nil { + Walk(x.Else, f) + } case *WhileClause: - walkStmts(x.Cond, f) - walkStmts(x.Do, f) + walkStmts(x.Cond, x.CondLast, f) + walkStmts(x.Do, x.DoLast, f) case *ForClause: Walk(x.Loop, f) - walkStmts(x.Do, f) + walkStmts(x.Do, x.DoLast, f) case *WordIter: Walk(x.Name, f) walkWords(x.Items, f) @@ -121,7 +123,7 @@ func Walk(node Node, f func(Node) bool) { Walk(wp, f) } case *CmdSubst: - walkStmts(x.StmtList, f) + walkStmts(x.Stmts, x.Last, f) case *ParamExp: Walk(x.Param, f) if x.Index != nil { @@ -173,12 +175,11 @@ func Walk(node Node, f func(Node) bool) { Walk(&c, f) } walkWords(x.Patterns, f) - walkStmts(x.StmtList, f) + walkStmts(x.Stmts, x.Last, f) case *TestClause: Walk(x.X, f) case *DeclClause: - walkWords(x.Opts, f) - for _, a := range x.Assigns { + for _, a := range x.Args { Walk(a, f) } case *ArrayExpr: @@ -205,7 +206,7 @@ func Walk(node Node, f func(Node) bool) { case *ExtGlob: Walk(x.Pattern, f) case *ProcSubst: - walkStmts(x.StmtList, f) + walkStmts(x.Stmts, x.Last, f) case *TimeClause: if x.Stmt != nil { Walk(x.Stmt, f)