mirror of
https://github.com/mudler/luet.git
synced 2025-09-04 00:34:41 +00:00
Move mvdan.cc/sh to v3
This commit is contained in:
3
go.mod
3
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
|
||||
)
|
||||
|
9
go.sum
9
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=
|
||||
|
27
vendor/golang.org/x/xerrors/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/xerrors/LICENSE
generated
vendored
Normal file
@@ -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.
|
22
vendor/golang.org/x/xerrors/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/xerrors/PATENTS
generated
vendored
Normal file
@@ -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.
|
2
vendor/golang.org/x/xerrors/README
generated
vendored
Normal file
2
vendor/golang.org/x/xerrors/README
generated
vendored
Normal file
@@ -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.
|
193
vendor/golang.org/x/xerrors/adaptor.go
generated
vendored
Normal file
193
vendor/golang.org/x/xerrors/adaptor.go
generated
vendored
Normal file
@@ -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("<nil>")
|
||||
}
|
||||
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
|
||||
}
|
1
vendor/golang.org/x/xerrors/codereview.cfg
generated
vendored
Normal file
1
vendor/golang.org/x/xerrors/codereview.cfg
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
issuerepo: golang/go
|
22
vendor/golang.org/x/xerrors/doc.go
generated
vendored
Normal file
22
vendor/golang.org/x/xerrors/doc.go
generated
vendored
Normal file
@@ -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"
|
33
vendor/golang.org/x/xerrors/errors.go
generated
vendored
Normal file
33
vendor/golang.org/x/xerrors/errors.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
109
vendor/golang.org/x/xerrors/fmt.go
generated
vendored
Normal file
109
vendor/golang.org/x/xerrors/fmt.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
34
vendor/golang.org/x/xerrors/format.go
generated
vendored
Normal file
34
vendor/golang.org/x/xerrors/format.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
56
vendor/golang.org/x/xerrors/frame.go
generated
vendored
Normal file
56
vendor/golang.org/x/xerrors/frame.go
generated
vendored
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
3
vendor/golang.org/x/xerrors/go.mod
generated
vendored
Normal file
3
vendor/golang.org/x/xerrors/go.mod
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
module golang.org/x/xerrors
|
||||
|
||||
go 1.11
|
8
vendor/golang.org/x/xerrors/internal/internal.go
generated
vendored
Normal file
8
vendor/golang.org/x/xerrors/internal/internal.go
generated
vendored
Normal file
@@ -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
|
106
vendor/golang.org/x/xerrors/wrap.go
generated
vendored
Normal file
106
vendor/golang.org/x/xerrors/wrap.go
generated
vendored
Normal file
@@ -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()
|
14
vendor/modules.txt
vendored
14
vendor/modules.txt
vendored
@@ -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
|
||||
|
24
vendor/mvdan.cc/sh/expand/braces.go
vendored
24
vendor/mvdan.cc/sh/expand/braces.go
vendored
@@ -1,24 +0,0 @@
|
||||
// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
package expand
|
||||
|
||||
import "mvdan.cc/sh/syntax"
|
||||
|
||||
// Braces performs Bash brace expansion on words. For example, passing it a
|
||||
// literal word "foo{bar,baz}" will return two literal words, "foobar" and
|
||||
// "foobaz".
|
||||
//
|
||||
// It does not return an error; malformed brace expansions are simply skipped.
|
||||
// For example, "a{b{c,d}" results in the words "a{bc" and "a{bd".
|
||||
//
|
||||
// Note that the resulting words may have more word parts than necessary, such
|
||||
// as contiguous *syntax.Lit nodes, and that these parts may be shared between
|
||||
// words.
|
||||
func Braces(words ...*syntax.Word) []*syntax.Word {
|
||||
var res []*syntax.Word
|
||||
for _, word := range words {
|
||||
res = append(res, syntax.ExpandBraces(word)...)
|
||||
}
|
||||
return res
|
||||
}
|
164
vendor/mvdan.cc/sh/interp/module.go
vendored
164
vendor/mvdan.cc/sh/interp/module.go
vendored
@@ -1,164 +0,0 @@
|
||||
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
package interp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"mvdan.cc/sh/expand"
|
||||
)
|
||||
|
||||
// FromModuleContext returns the ModuleCtx value stored in ctx, if any.
|
||||
func FromModuleContext(ctx context.Context) (ModuleCtx, bool) {
|
||||
mc, ok := ctx.Value(moduleCtxKey{}).(ModuleCtx)
|
||||
return mc, ok
|
||||
}
|
||||
|
||||
type moduleCtxKey struct{}
|
||||
|
||||
// ModuleCtx is the data passed to all the module functions via a context value.
|
||||
// It contains some of the current state of the Runner, as well as some fields
|
||||
// necessary to implement some of the modules.
|
||||
type ModuleCtx struct {
|
||||
Env expand.Environ
|
||||
Dir string
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
KillTimeout time.Duration
|
||||
}
|
||||
|
||||
// UnixPath fixes absolute unix paths on Windows, for example converting
|
||||
// "C:\\CurDir\\dev\\null" to "/dev/null".
|
||||
func (mc ModuleCtx) UnixPath(path string) string {
|
||||
if runtime.GOOS != "windows" {
|
||||
return path
|
||||
}
|
||||
path = strings.TrimPrefix(path, mc.Dir)
|
||||
return strings.Replace(path, `\`, `/`, -1)
|
||||
}
|
||||
|
||||
// ModuleExec is the module responsible for executing a program. It is
|
||||
// executed for all CallExpr nodes where the first argument is neither a
|
||||
// declared function nor a builtin.
|
||||
//
|
||||
// Note that the name is included as the first argument. If path is an
|
||||
// empty string, it means that the executable did not exist or was not
|
||||
// found in $PATH.
|
||||
//
|
||||
// Use a return error of type ExitStatus to set the exit status. A nil error has
|
||||
// the same effect as ExitStatus(0). If the error is of any other type, the
|
||||
// interpreter will come to a stop.
|
||||
type ModuleExec func(ctx context.Context, path string, args []string) error
|
||||
|
||||
func (ModuleExec) isModule() {}
|
||||
|
||||
var DefaultExec = ModuleExec(func(ctx context.Context, path string, args []string) error {
|
||||
mc, _ := FromModuleContext(ctx)
|
||||
if path == "" {
|
||||
fmt.Fprintf(mc.Stderr, "%q: executable file not found in $PATH\n", args[0])
|
||||
return ExitStatus(127)
|
||||
}
|
||||
cmd := exec.Cmd{
|
||||
Path: path,
|
||||
Args: args,
|
||||
Env: execEnv(mc.Env),
|
||||
Dir: mc.Dir,
|
||||
Stdin: mc.Stdin,
|
||||
Stdout: mc.Stdout,
|
||||
Stderr: mc.Stderr,
|
||||
}
|
||||
|
||||
err := cmd.Start()
|
||||
if err == nil {
|
||||
if done := ctx.Done(); done != nil {
|
||||
go func() {
|
||||
<-done
|
||||
|
||||
if mc.KillTimeout <= 0 || runtime.GOOS == "windows" {
|
||||
_ = cmd.Process.Signal(os.Kill)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: don't temporarily leak this goroutine
|
||||
// if the program stops itself with the
|
||||
// interrupt.
|
||||
go func() {
|
||||
time.Sleep(mc.KillTimeout)
|
||||
_ = cmd.Process.Signal(os.Kill)
|
||||
}()
|
||||
_ = cmd.Process.Signal(os.Interrupt)
|
||||
}()
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
}
|
||||
|
||||
switch x := err.(type) {
|
||||
case *exec.ExitError:
|
||||
// started, but errored - default to 1 if OS
|
||||
// doesn't have exit statuses
|
||||
if status, ok := x.Sys().(syscall.WaitStatus); ok {
|
||||
if status.Signaled() && ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
return ExitStatus(status.ExitStatus())
|
||||
}
|
||||
return ExitStatus(1)
|
||||
case *exec.Error:
|
||||
// did not start
|
||||
fmt.Fprintf(mc.Stderr, "%v\n", err)
|
||||
return ExitStatus(127)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
})
|
||||
|
||||
// ModuleOpen is the module responsible for opening a file. It is
|
||||
// executed for all files that are opened directly by the shell, such as
|
||||
// in redirects. Files opened by executed programs are not included.
|
||||
//
|
||||
// The path parameter is absolute and has been cleaned.
|
||||
//
|
||||
// Use a return error of type *os.PathError to have the error printed to
|
||||
// stderr and the exit status set to 1. If the error is of any other type, the
|
||||
// interpreter will come to a stop.
|
||||
//
|
||||
// TODO: What about stat calls? They are used heavily in the builtin
|
||||
// test expressions, and also when doing a cd. Should they have a
|
||||
// separate module?
|
||||
type ModuleOpen func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error)
|
||||
|
||||
func (ModuleOpen) isModule() {}
|
||||
|
||||
var DefaultOpen = ModuleOpen(func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
||||
return os.OpenFile(path, flag, perm)
|
||||
})
|
||||
|
||||
func OpenDevImpls(next ModuleOpen) ModuleOpen {
|
||||
return func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
||||
mc, _ := FromModuleContext(ctx)
|
||||
switch mc.UnixPath(path) {
|
||||
case "/dev/null":
|
||||
return devNull{}, nil
|
||||
}
|
||||
return next(ctx, path, flag, perm)
|
||||
}
|
||||
}
|
||||
|
||||
var _ io.ReadWriteCloser = devNull{}
|
||||
|
||||
type devNull struct{}
|
||||
|
||||
func (devNull) Read(p []byte) (int, error) { return 0, io.EOF }
|
||||
func (devNull) Write(p []byte) (int, error) { return len(p), nil }
|
||||
func (devNull) Close() error { return nil }
|
282
vendor/mvdan.cc/sh/syntax/expand.go
vendored
282
vendor/mvdan.cc/sh/syntax/expand.go
vendored
@@ -1,282 +0,0 @@
|
||||
// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
package syntax
|
||||
|
||||
import "strconv"
|
||||
|
||||
// TODO(v3): Consider making these special syntax nodes.
|
||||
// Among other things, we can make use of Word.Lit.
|
||||
|
||||
type brace struct {
|
||||
seq bool // {x..y[..incr]} instead of {x,y[,...]}
|
||||
chars bool // sequence is of chars, not numbers
|
||||
elems []*braceWord
|
||||
}
|
||||
|
||||
// braceWord is like Word, but with braceWordPart.
|
||||
type braceWord struct {
|
||||
parts []braceWordPart
|
||||
}
|
||||
|
||||
// braceWordPart contains any WordPart or a brace.
|
||||
type braceWordPart interface{}
|
||||
|
||||
var (
|
||||
litLeftBrace = &Lit{Value: "{"}
|
||||
litComma = &Lit{Value: ","}
|
||||
litDots = &Lit{Value: ".."}
|
||||
litRightBrace = &Lit{Value: "}"}
|
||||
)
|
||||
|
||||
func splitBraces(word *Word) (*braceWord, bool) {
|
||||
any := false
|
||||
top := &braceWord{}
|
||||
acc := top
|
||||
var cur *brace
|
||||
open := []*brace{}
|
||||
|
||||
pop := func() *brace {
|
||||
old := cur
|
||||
open = open[:len(open)-1]
|
||||
if len(open) == 0 {
|
||||
cur = nil
|
||||
acc = top
|
||||
} else {
|
||||
cur = open[len(open)-1]
|
||||
acc = cur.elems[len(cur.elems)-1]
|
||||
}
|
||||
return old
|
||||
}
|
||||
addLit := func(lit *Lit) {
|
||||
acc.parts = append(acc.parts, lit)
|
||||
}
|
||||
addParts := func(parts ...braceWordPart) {
|
||||
acc.parts = append(acc.parts, parts...)
|
||||
}
|
||||
|
||||
for _, wp := range word.Parts {
|
||||
lit, ok := wp.(*Lit)
|
||||
if !ok {
|
||||
addParts(wp)
|
||||
continue
|
||||
}
|
||||
last := 0
|
||||
for j := 0; j < len(lit.Value); j++ {
|
||||
addlitidx := func() {
|
||||
if last == j {
|
||||
return // empty lit
|
||||
}
|
||||
l2 := *lit
|
||||
l2.Value = l2.Value[last:j]
|
||||
addLit(&l2)
|
||||
}
|
||||
switch lit.Value[j] {
|
||||
case '{':
|
||||
addlitidx()
|
||||
acc = &braceWord{}
|
||||
cur = &brace{elems: []*braceWord{acc}}
|
||||
open = append(open, cur)
|
||||
case ',':
|
||||
if cur == nil {
|
||||
continue
|
||||
}
|
||||
addlitidx()
|
||||
acc = &braceWord{}
|
||||
cur.elems = append(cur.elems, acc)
|
||||
case '.':
|
||||
if cur == nil {
|
||||
continue
|
||||
}
|
||||
if j+1 >= len(lit.Value) || lit.Value[j+1] != '.' {
|
||||
continue
|
||||
}
|
||||
addlitidx()
|
||||
cur.seq = true
|
||||
acc = &braceWord{}
|
||||
cur.elems = append(cur.elems, acc)
|
||||
j++
|
||||
case '}':
|
||||
if cur == nil {
|
||||
continue
|
||||
}
|
||||
any = true
|
||||
addlitidx()
|
||||
br := pop()
|
||||
if len(br.elems) == 1 {
|
||||
// return {x} to a non-brace
|
||||
addLit(litLeftBrace)
|
||||
addParts(br.elems[0].parts...)
|
||||
addLit(litRightBrace)
|
||||
break
|
||||
}
|
||||
if !br.seq {
|
||||
addParts(br)
|
||||
break
|
||||
}
|
||||
var chars [2]bool
|
||||
broken := false
|
||||
for i, elem := range br.elems[:2] {
|
||||
val := braceWordLit(elem)
|
||||
if _, err := strconv.Atoi(val); err == nil {
|
||||
} else if len(val) == 1 &&
|
||||
'a' <= val[0] && val[0] <= 'z' {
|
||||
chars[i] = true
|
||||
} else {
|
||||
broken = true
|
||||
}
|
||||
}
|
||||
if len(br.elems) == 3 {
|
||||
// increment must be a number
|
||||
val := braceWordLit(br.elems[2])
|
||||
if _, err := strconv.Atoi(val); err != nil {
|
||||
broken = true
|
||||
}
|
||||
}
|
||||
// are start and end both chars or
|
||||
// non-chars?
|
||||
if chars[0] != chars[1] {
|
||||
broken = true
|
||||
}
|
||||
if !broken {
|
||||
br.chars = chars[0]
|
||||
addParts(br)
|
||||
break
|
||||
}
|
||||
// return broken {x..y[..incr]} to a non-brace
|
||||
addLit(litLeftBrace)
|
||||
for i, elem := range br.elems {
|
||||
if i > 0 {
|
||||
addLit(litDots)
|
||||
}
|
||||
addParts(elem.parts...)
|
||||
}
|
||||
addLit(litRightBrace)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
last = j + 1
|
||||
}
|
||||
if last == 0 {
|
||||
addLit(lit)
|
||||
} else {
|
||||
left := *lit
|
||||
left.Value = left.Value[last:]
|
||||
addLit(&left)
|
||||
}
|
||||
}
|
||||
// open braces that were never closed fall back to non-braces
|
||||
for acc != top {
|
||||
br := pop()
|
||||
addLit(litLeftBrace)
|
||||
for i, elem := range br.elems {
|
||||
if i > 0 {
|
||||
if br.seq {
|
||||
addLit(litDots)
|
||||
} else {
|
||||
addLit(litComma)
|
||||
}
|
||||
}
|
||||
addParts(elem.parts...)
|
||||
}
|
||||
}
|
||||
return top, any
|
||||
}
|
||||
|
||||
func braceWordLit(v interface{}) string {
|
||||
word, _ := v.(*braceWord)
|
||||
if word == nil || len(word.parts) != 1 {
|
||||
return ""
|
||||
}
|
||||
lit, ok := word.parts[0].(*Lit)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return lit.Value
|
||||
}
|
||||
|
||||
func expandRec(bw *braceWord) []*Word {
|
||||
var all []*Word
|
||||
var left []WordPart
|
||||
for i, wp := range bw.parts {
|
||||
br, ok := wp.(*brace)
|
||||
if !ok {
|
||||
left = append(left, wp.(WordPart))
|
||||
continue
|
||||
}
|
||||
if br.seq {
|
||||
var from, to int
|
||||
if br.chars {
|
||||
from = int(braceWordLit(br.elems[0])[0])
|
||||
to = int(braceWordLit(br.elems[1])[0])
|
||||
} else {
|
||||
from, _ = strconv.Atoi(braceWordLit(br.elems[0]))
|
||||
to, _ = strconv.Atoi(braceWordLit(br.elems[1]))
|
||||
}
|
||||
upward := from <= to
|
||||
incr := 1
|
||||
if !upward {
|
||||
incr = -1
|
||||
}
|
||||
if len(br.elems) > 2 {
|
||||
val := braceWordLit(br.elems[2])
|
||||
n, _ := strconv.Atoi(val)
|
||||
if n != 0 && n > 0 == upward {
|
||||
incr = n
|
||||
}
|
||||
}
|
||||
n := from
|
||||
for {
|
||||
if upward && n > to {
|
||||
break
|
||||
}
|
||||
if !upward && n < to {
|
||||
break
|
||||
}
|
||||
next := *bw
|
||||
next.parts = next.parts[i+1:]
|
||||
lit := &Lit{}
|
||||
if br.chars {
|
||||
lit.Value = string(n)
|
||||
} else {
|
||||
lit.Value = strconv.Itoa(n)
|
||||
}
|
||||
next.parts = append([]braceWordPart{lit}, next.parts...)
|
||||
exp := expandRec(&next)
|
||||
for _, w := range exp {
|
||||
w.Parts = append(left, w.Parts...)
|
||||
}
|
||||
all = append(all, exp...)
|
||||
n += incr
|
||||
}
|
||||
return all
|
||||
}
|
||||
for _, elem := range br.elems {
|
||||
next := *bw
|
||||
next.parts = next.parts[i+1:]
|
||||
next.parts = append(elem.parts, next.parts...)
|
||||
exp := expandRec(&next)
|
||||
for _, w := range exp {
|
||||
w.Parts = append(left, w.Parts...)
|
||||
}
|
||||
all = append(all, exp...)
|
||||
}
|
||||
return all
|
||||
}
|
||||
return []*Word{{Parts: left}}
|
||||
}
|
||||
|
||||
// TODO(v3): remove
|
||||
|
||||
// ExpandBraces performs Bash brace expansion on a word. For example,
|
||||
// passing it a single-literal word "foo{bar,baz}" will return two
|
||||
// single-literal words, "foobar" and "foobaz".
|
||||
//
|
||||
// Deprecated: use mvdan.cc/sh/expand.Braces instead.
|
||||
func ExpandBraces(word *Word) []*Word {
|
||||
topBrace, any := splitBraces(word)
|
||||
if !any {
|
||||
return []*Word{word}
|
||||
}
|
||||
return expandRec(topBrace)
|
||||
}
|
173
vendor/mvdan.cc/sh/syntax/pattern.go
vendored
173
vendor/mvdan.cc/sh/syntax/pattern.go
vendored
@@ -1,173 +0,0 @@
|
||||
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
package syntax
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func charClass(s string) (string, error) {
|
||||
if strings.HasPrefix(s, "[[.") || strings.HasPrefix(s, "[[=") {
|
||||
return "", fmt.Errorf("collating features not available")
|
||||
}
|
||||
if !strings.HasPrefix(s, "[[:") {
|
||||
return "", nil
|
||||
}
|
||||
name := s[3:]
|
||||
end := strings.Index(name, ":]]")
|
||||
if end < 0 {
|
||||
return "", fmt.Errorf("[[: was not matched with a closing :]]")
|
||||
}
|
||||
name = name[:end]
|
||||
switch name {
|
||||
case "alnum", "alpha", "ascii", "blank", "cntrl", "digit", "graph",
|
||||
"lower", "print", "punct", "space", "upper", "word", "xdigit":
|
||||
default:
|
||||
return "", fmt.Errorf("invalid character class: %q", name)
|
||||
}
|
||||
return s[:len(name)+6], nil
|
||||
}
|
||||
|
||||
// TranslatePattern turns a shell wildcard pattern into a regular expression
|
||||
// that can be used with regexp.Compile. It will return an error if the input
|
||||
// pattern was incorrect. Otherwise, the returned expression can be passed to
|
||||
// regexp.MustCompile.
|
||||
//
|
||||
// For example, TranslatePattern(`foo*bar?`, true) returns `foo.*bar.`.
|
||||
//
|
||||
// Note that this function (and QuotePattern) should not be directly used with
|
||||
// file paths if Windows is supported, as the path separator on that platform is
|
||||
// the same character as the escaping character for shell patterns.
|
||||
func TranslatePattern(pattern string, greedy bool) (string, error) {
|
||||
any := false
|
||||
loop:
|
||||
for _, r := range pattern {
|
||||
switch r {
|
||||
// including those that need escaping since they are
|
||||
// special chars in regexes
|
||||
case '*', '?', '[', '\\', '.', '+', '(', ')', '|',
|
||||
']', '{', '}', '^', '$':
|
||||
any = true
|
||||
break loop
|
||||
}
|
||||
}
|
||||
if !any { // short-cut without a string copy
|
||||
return pattern, nil
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(pattern); i++ {
|
||||
switch c := pattern[i]; c {
|
||||
case '*':
|
||||
buf.WriteString(".*")
|
||||
if !greedy {
|
||||
buf.WriteByte('?')
|
||||
}
|
||||
case '?':
|
||||
buf.WriteString(".")
|
||||
case '\\':
|
||||
if i++; i >= len(pattern) {
|
||||
return "", fmt.Errorf(`\ at end of pattern`)
|
||||
}
|
||||
buf.WriteString(regexp.QuoteMeta(string(pattern[i])))
|
||||
case '[':
|
||||
name, err := charClass(pattern[i:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if name != "" {
|
||||
buf.WriteString(name)
|
||||
i += len(name) - 1
|
||||
break
|
||||
}
|
||||
buf.WriteByte(c)
|
||||
if i++; i >= len(pattern) {
|
||||
return "", fmt.Errorf("[ was not matched with a closing ]")
|
||||
}
|
||||
switch c = pattern[i]; c {
|
||||
case '!', '^':
|
||||
buf.WriteByte('^')
|
||||
i++
|
||||
c = pattern[i]
|
||||
}
|
||||
buf.WriteByte(c)
|
||||
last := c
|
||||
rangeStart := byte(0)
|
||||
for {
|
||||
if i++; i >= len(pattern) {
|
||||
return "", fmt.Errorf("[ was not matched with a closing ]")
|
||||
}
|
||||
last, c = c, pattern[i]
|
||||
buf.WriteByte(c)
|
||||
if c == ']' {
|
||||
break
|
||||
}
|
||||
if rangeStart != 0 && rangeStart > c {
|
||||
return "", fmt.Errorf("invalid range: %c-%c", rangeStart, c)
|
||||
}
|
||||
if c == '-' {
|
||||
rangeStart = last
|
||||
} else {
|
||||
rangeStart = 0
|
||||
}
|
||||
}
|
||||
default:
|
||||
buf.WriteString(regexp.QuoteMeta(string(c)))
|
||||
}
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// HasPattern returns whether a string contains any unescaped wildcard
|
||||
// characters: '*', '?', or '['. When the function returns false, the given
|
||||
// pattern can only match at most one string.
|
||||
//
|
||||
// For example, HasPattern(`foo\*bar`) returns false, but HasPattern(`foo*bar`)
|
||||
// returns true.
|
||||
//
|
||||
// This can be useful to avoid extra work, like TranslatePattern. Note that this
|
||||
// function cannot be used to avoid QuotePattern, as backslashes are quoted by
|
||||
// that function but ignored here.
|
||||
func HasPattern(pattern string) bool {
|
||||
for i := 0; i < len(pattern); i++ {
|
||||
switch pattern[i] {
|
||||
case '\\':
|
||||
i++
|
||||
case '*', '?', '[':
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// QuotePattern returns a string that quotes all special characters in the given
|
||||
// wildcard pattern. The returned string is a pattern that matches the literal
|
||||
// string.
|
||||
//
|
||||
// For example, QuotePattern(`foo*bar?`) returns `foo\*bar\?`.
|
||||
func QuotePattern(pattern string) string {
|
||||
any := false
|
||||
loop:
|
||||
for _, r := range pattern {
|
||||
switch r {
|
||||
case '*', '?', '[', '\\':
|
||||
any = true
|
||||
break loop
|
||||
}
|
||||
}
|
||||
if !any { // short-cut without a string copy
|
||||
return pattern
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
for _, r := range pattern {
|
||||
switch r {
|
||||
case '*', '?', '[', '\\':
|
||||
buf.WriteByte('\\')
|
||||
}
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
16
vendor/mvdan.cc/sh/syntax/token_string.go
vendored
16
vendor/mvdan.cc/sh/syntax/token_string.go
vendored
@@ -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]]
|
||||
}
|
346
vendor/mvdan.cc/sh/syntax/tokens.go
vendored
346
vendor/mvdan.cc/sh/syntax/tokens.go
vendored
@@ -1,346 +0,0 @@
|
||||
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
package syntax
|
||||
|
||||
//go:generate stringer -type token -linecomment -trimprefix _
|
||||
|
||||
type token uint32
|
||||
|
||||
// The list of all possible tokens.
|
||||
const (
|
||||
illegalTok token = iota
|
||||
|
||||
_EOF
|
||||
_Newl
|
||||
_Lit
|
||||
_LitWord
|
||||
_LitRedir
|
||||
|
||||
sglQuote // '
|
||||
dblQuote // "
|
||||
bckQuote // `
|
||||
|
||||
and // &
|
||||
andAnd // &&
|
||||
orOr // ||
|
||||
or // |
|
||||
orAnd // |&
|
||||
|
||||
dollar // $
|
||||
dollSglQuote // $'
|
||||
dollDblQuote // $"
|
||||
dollBrace // ${
|
||||
dollBrack // $[
|
||||
dollParen // $(
|
||||
dollDblParen // $((
|
||||
leftBrack // [
|
||||
dblLeftBrack // [[
|
||||
leftParen // (
|
||||
dblLeftParen // ((
|
||||
|
||||
rightBrace // }
|
||||
rightBrack // ]
|
||||
rightParen // )
|
||||
dblRightParen // ))
|
||||
semicolon // ;
|
||||
|
||||
dblSemicolon // ;;
|
||||
semiAnd // ;&
|
||||
dblSemiAnd // ;;&
|
||||
semiOr // ;|
|
||||
|
||||
exclMark // !
|
||||
addAdd // ++
|
||||
subSub // --
|
||||
star // *
|
||||
power // **
|
||||
equal // ==
|
||||
nequal // !=
|
||||
lequal // <=
|
||||
gequal // >=
|
||||
|
||||
addAssgn // +=
|
||||
subAssgn // -=
|
||||
mulAssgn // *=
|
||||
quoAssgn // /=
|
||||
remAssgn // %=
|
||||
andAssgn // &=
|
||||
orAssgn // |=
|
||||
xorAssgn // ^=
|
||||
shlAssgn // <<=
|
||||
shrAssgn // >>=
|
||||
|
||||
rdrOut // >
|
||||
appOut // >>
|
||||
rdrIn // <
|
||||
rdrInOut // <>
|
||||
dplIn // <&
|
||||
dplOut // >&
|
||||
clbOut // >|
|
||||
hdoc // <<
|
||||
dashHdoc // <<-
|
||||
wordHdoc // <<<
|
||||
rdrAll // &>
|
||||
appAll // &>>
|
||||
|
||||
cmdIn // <(
|
||||
cmdOut // >(
|
||||
|
||||
plus // +
|
||||
colPlus // :+
|
||||
minus // -
|
||||
colMinus // :-
|
||||
quest // ?
|
||||
colQuest // :?
|
||||
assgn // =
|
||||
colAssgn // :=
|
||||
perc // %
|
||||
dblPerc // %%
|
||||
hash // #
|
||||
dblHash // ##
|
||||
caret // ^
|
||||
dblCaret // ^^
|
||||
comma // ,
|
||||
dblComma // ,,
|
||||
at // @
|
||||
slash // /
|
||||
dblSlash // //
|
||||
colon // :
|
||||
|
||||
tsExists // -e
|
||||
tsRegFile // -f
|
||||
tsDirect // -d
|
||||
tsCharSp // -c
|
||||
tsBlckSp // -b
|
||||
tsNmPipe // -p
|
||||
tsSocket // -S
|
||||
tsSmbLink // -L
|
||||
tsSticky // -k
|
||||
tsGIDSet // -g
|
||||
tsUIDSet // -u
|
||||
tsGrpOwn // -G
|
||||
tsUsrOwn // -O
|
||||
tsModif // -N
|
||||
tsRead // -r
|
||||
tsWrite // -w
|
||||
tsExec // -x
|
||||
tsNoEmpty // -s
|
||||
tsFdTerm // -t
|
||||
tsEmpStr // -z
|
||||
tsNempStr // -n
|
||||
tsOptSet // -o
|
||||
tsVarSet // -v
|
||||
tsRefVar // -R
|
||||
|
||||
tsReMatch // =~
|
||||
tsNewer // -nt
|
||||
tsOlder // -ot
|
||||
tsDevIno // -ef
|
||||
tsEql // -eq
|
||||
tsNeq // -ne
|
||||
tsLeq // -le
|
||||
tsGeq // -ge
|
||||
tsLss // -lt
|
||||
tsGtr // -gt
|
||||
|
||||
globQuest // ?(
|
||||
globStar // *(
|
||||
globPlus // +(
|
||||
globAt // @(
|
||||
globExcl // !(
|
||||
)
|
||||
|
||||
type RedirOperator token
|
||||
|
||||
const (
|
||||
RdrOut = RedirOperator(rdrOut) + iota
|
||||
AppOut
|
||||
RdrIn
|
||||
RdrInOut
|
||||
DplIn
|
||||
DplOut
|
||||
ClbOut
|
||||
Hdoc
|
||||
DashHdoc
|
||||
WordHdoc
|
||||
RdrAll
|
||||
AppAll
|
||||
)
|
||||
|
||||
type ProcOperator token
|
||||
|
||||
const (
|
||||
CmdIn = ProcOperator(cmdIn) + iota
|
||||
CmdOut
|
||||
)
|
||||
|
||||
type GlobOperator token
|
||||
|
||||
const (
|
||||
GlobQuest = GlobOperator(globQuest) + iota
|
||||
GlobStar
|
||||
GlobPlus
|
||||
GlobAt
|
||||
GlobExcl
|
||||
)
|
||||
|
||||
type BinCmdOperator token
|
||||
|
||||
const (
|
||||
AndStmt = BinCmdOperator(andAnd) + iota
|
||||
OrStmt
|
||||
Pipe
|
||||
PipeAll
|
||||
)
|
||||
|
||||
type CaseOperator token
|
||||
|
||||
const (
|
||||
Break = CaseOperator(dblSemicolon) + iota
|
||||
Fallthrough
|
||||
Resume
|
||||
ResumeKorn
|
||||
)
|
||||
|
||||
type ParNamesOperator token
|
||||
|
||||
const (
|
||||
NamesPrefix = ParNamesOperator(star)
|
||||
NamesPrefixWords = ParNamesOperator(at)
|
||||
)
|
||||
|
||||
type ParExpOperator token
|
||||
|
||||
const (
|
||||
SubstPlus = ParExpOperator(plus) + iota
|
||||
SubstColPlus
|
||||
SubstMinus
|
||||
SubstColMinus
|
||||
SubstQuest
|
||||
SubstColQuest
|
||||
SubstAssgn
|
||||
SubstColAssgn
|
||||
RemSmallSuffix
|
||||
RemLargeSuffix
|
||||
RemSmallPrefix
|
||||
RemLargePrefix
|
||||
UpperFirst
|
||||
UpperAll
|
||||
LowerFirst
|
||||
LowerAll
|
||||
OtherParamOps
|
||||
)
|
||||
|
||||
type UnAritOperator token
|
||||
|
||||
const (
|
||||
Not = UnAritOperator(exclMark) + iota
|
||||
Inc
|
||||
Dec
|
||||
Plus = UnAritOperator(plus)
|
||||
Minus = UnAritOperator(minus)
|
||||
)
|
||||
|
||||
type BinAritOperator token
|
||||
|
||||
const (
|
||||
Add = BinAritOperator(plus)
|
||||
Sub = BinAritOperator(minus)
|
||||
Mul = BinAritOperator(star)
|
||||
Quo = BinAritOperator(slash)
|
||||
Rem = BinAritOperator(perc)
|
||||
Pow = BinAritOperator(power)
|
||||
Eql = BinAritOperator(equal)
|
||||
Gtr = BinAritOperator(rdrOut)
|
||||
Lss = BinAritOperator(rdrIn)
|
||||
Neq = BinAritOperator(nequal)
|
||||
Leq = BinAritOperator(lequal)
|
||||
Geq = BinAritOperator(gequal)
|
||||
And = BinAritOperator(and)
|
||||
Or = BinAritOperator(or)
|
||||
Xor = BinAritOperator(caret)
|
||||
Shr = BinAritOperator(appOut)
|
||||
Shl = BinAritOperator(hdoc)
|
||||
|
||||
AndArit = BinAritOperator(andAnd)
|
||||
OrArit = BinAritOperator(orOr)
|
||||
Comma = BinAritOperator(comma)
|
||||
Quest = BinAritOperator(quest)
|
||||
Colon = BinAritOperator(colon)
|
||||
|
||||
Assgn = BinAritOperator(assgn)
|
||||
AddAssgn = BinAritOperator(addAssgn)
|
||||
SubAssgn = BinAritOperator(subAssgn)
|
||||
MulAssgn = BinAritOperator(mulAssgn)
|
||||
QuoAssgn = BinAritOperator(quoAssgn)
|
||||
RemAssgn = BinAritOperator(remAssgn)
|
||||
AndAssgn = BinAritOperator(andAssgn)
|
||||
OrAssgn = BinAritOperator(orAssgn)
|
||||
XorAssgn = BinAritOperator(xorAssgn)
|
||||
ShlAssgn = BinAritOperator(shlAssgn)
|
||||
ShrAssgn = BinAritOperator(shrAssgn)
|
||||
)
|
||||
|
||||
type UnTestOperator token
|
||||
|
||||
const (
|
||||
TsExists = UnTestOperator(tsExists) + iota
|
||||
TsRegFile
|
||||
TsDirect
|
||||
TsCharSp
|
||||
TsBlckSp
|
||||
TsNmPipe
|
||||
TsSocket
|
||||
TsSmbLink
|
||||
TsSticky
|
||||
TsGIDSet
|
||||
TsUIDSet
|
||||
TsGrpOwn
|
||||
TsUsrOwn
|
||||
TsModif
|
||||
TsRead
|
||||
TsWrite
|
||||
TsExec
|
||||
TsNoEmpty
|
||||
TsFdTerm
|
||||
TsEmpStr
|
||||
TsNempStr
|
||||
TsOptSet
|
||||
TsVarSet
|
||||
TsRefVar
|
||||
TsNot = UnTestOperator(exclMark)
|
||||
)
|
||||
|
||||
type BinTestOperator token
|
||||
|
||||
const (
|
||||
TsReMatch = BinTestOperator(tsReMatch) + iota
|
||||
TsNewer
|
||||
TsOlder
|
||||
TsDevIno
|
||||
TsEql
|
||||
TsNeq
|
||||
TsLeq
|
||||
TsGeq
|
||||
TsLss
|
||||
TsGtr
|
||||
AndTest = BinTestOperator(andAnd)
|
||||
OrTest = BinTestOperator(orOr)
|
||||
TsMatch = BinTestOperator(equal)
|
||||
TsNoMatch = BinTestOperator(nequal)
|
||||
TsBefore = BinTestOperator(rdrIn)
|
||||
TsAfter = BinTestOperator(rdrOut)
|
||||
)
|
||||
|
||||
func (o RedirOperator) String() string { return token(o).String() }
|
||||
func (o ProcOperator) String() string { return token(o).String() }
|
||||
func (o GlobOperator) String() string { return token(o).String() }
|
||||
func (o BinCmdOperator) String() string { return token(o).String() }
|
||||
func (o CaseOperator) String() string { return token(o).String() }
|
||||
func (o ParNamesOperator) String() string { return token(o).String() }
|
||||
func (o ParExpOperator) String() string { return token(o).String() }
|
||||
func (o UnAritOperator) String() string { return token(o).String() }
|
||||
func (o BinAritOperator) String() string { return token(o).String() }
|
||||
func (o UnTestOperator) String() string { return token(o).String() }
|
||||
func (o BinTestOperator) String() string { return token(o).String() }
|
@@ -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
|
||||
}
|
||||
|
85
vendor/mvdan.cc/sh/v3/expand/braces.go
vendored
Normal file
85
vendor/mvdan.cc/sh/v3/expand/braces.go
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
|
||||
// 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}}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
@@ -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)
|
||||
for _, word := range words {
|
||||
afterBraces := []*syntax.Word{word}
|
||||
if syntax.SplitBraces(word) {
|
||||
afterBraces = Braces(word)
|
||||
}
|
||||
for _, word2 := range afterBraces {
|
||||
wfields, err := cfg.wordFields(word2.Parts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, field := range wfields {
|
||||
path, doGlob := cfg.escapedGlobField(field)
|
||||
var matches []string
|
||||
abs := filepath.IsAbs(path)
|
||||
if doGlob && !cfg.NoGlob {
|
||||
base := ""
|
||||
if !abs {
|
||||
base = dir
|
||||
}
|
||||
matches, err = cfg.glob(base, path)
|
||||
if doGlob && cfg.ReadDir != nil {
|
||||
matches, err = cfg.glob(dir, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
fields = append(fields, cfg.fieldJoin(field))
|
||||
if len(matches) > 0 {
|
||||
fields = append(fields, matches...)
|
||||
continue
|
||||
}
|
||||
for _, match := range matches {
|
||||
if !abs {
|
||||
match = strings.TrimPrefix(match, dir)
|
||||
}
|
||||
fields = append(fields, match)
|
||||
fields = append(fields, cfg.fieldJoin(field))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <name>", 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
|
||||
for i, dir := range matches {
|
||||
matches[i] = pathJoin2(dir, part)
|
||||
}
|
||||
newMatches = append(newMatches, 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])
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
288
vendor/mvdan.cc/sh/v3/interp/handler.go
vendored
Normal file
288
vendor/mvdan.cc/sh/v3/interp/handler.go
vendored
Normal file
@@ -0,0 +1,288 @@
|
||||
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
|
||||
// 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)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -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:
|
@@ -6,7 +6,7 @@ package interp
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"mvdan.cc/sh/syntax"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
)
|
||||
|
||||
const illegalTok = 0
|
@@ -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
|
||||
prev.Kind = expand.String
|
||||
if valType == "-n" {
|
||||
prev.Kind = expand.NameRef
|
||||
}
|
||||
switch x := prev.Value.(type) {
|
||||
case string:
|
||||
return x + s
|
||||
case []string:
|
||||
if len(x) == 0 {
|
||||
x = append(x, "")
|
||||
prev.Str = s
|
||||
return prev
|
||||
}
|
||||
x[0] += s
|
||||
return x
|
||||
case map[string]string:
|
||||
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 {
|
||||
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
|
||||
}
|
295
vendor/mvdan.cc/sh/v3/pattern/pattern.go
vendored
Normal file
295
vendor/mvdan.cc/sh/v3/pattern/pattern.go
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
|
||||
// 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()
|
||||
}
|
@@ -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,
|
@@ -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")
|
178
vendor/mvdan.cc/sh/v3/syntax/braces.go
vendored
Normal file
178
vendor/mvdan.cc/sh/v3/syntax/braces.go
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
|
||||
// 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
|
||||
}
|
@@ -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
|
@@ -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
|
@@ -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
|
||||
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,27 +475,22 @@ func (q *SglQuoted) End() Pos { return posAddCol(q.Right, 1) }
|
||||
|
||||
// DblQuoted represents a list of nodes within double quotes.
|
||||
type DblQuoted struct {
|
||||
Position Pos
|
||||
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
|
||||
|
||||
Stmts []*Stmt
|
||||
Last []Comment
|
||||
|
||||
Backquotes bool // deprecated `foo`
|
||||
TempFile bool // mksh's ${ foo;}
|
||||
ReplyVar bool // mksh's ${|foo;}
|
||||
}
|
||||
@@ -531,6 +501,7 @@ 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}
|
||||
@@ -583,6 +554,7 @@ type ArithmExp struct {
|
||||
Left, Right Pos
|
||||
Bracket bool // deprecated $[expr] form
|
||||
Unsigned bool // mksh's $((# expr))
|
||||
|
||||
X ArithmExpr
|
||||
}
|
||||
|
||||
@@ -600,6 +572,7 @@ func (a *ArithmExp) End() Pos {
|
||||
type ArithmCmd struct {
|
||||
Left, Right Pos
|
||||
Unsigned bool // mksh's ((# expr))
|
||||
|
||||
X ArithmExpr
|
||||
}
|
||||
|
||||
@@ -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,6 +638,7 @@ func (u *UnaryArithm) End() Pos {
|
||||
// ParenArithm represents an arithmetic expression within parentheses.
|
||||
type ParenArithm struct {
|
||||
Lparen, Rparen Pos
|
||||
|
||||
X ArithmExpr
|
||||
}
|
||||
|
||||
@@ -674,6 +648,7 @@ 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
|
||||
@@ -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,6 +681,7 @@ func (c *CaseItem) End() Pos {
|
||||
// This node will only appear in LangBash and LangMirBSDKorn.
|
||||
type TestClause struct {
|
||||
Left, Right Pos
|
||||
|
||||
X TestExpr
|
||||
}
|
||||
|
||||
@@ -747,6 +725,7 @@ func (u *UnaryTest) End() Pos { return u.X.End() }
|
||||
// ParenTest represents a test expression within parentheses.
|
||||
type ParenTest struct {
|
||||
Lparen, Rparen Pos
|
||||
|
||||
X TestExpr
|
||||
}
|
||||
|
||||
@@ -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,6 +758,7 @@ func (d *DeclClause) End() Pos {
|
||||
// This node will only appear with LangBash.
|
||||
type ArrayExpr struct {
|
||||
Lparen, Rparen Pos
|
||||
|
||||
Elems []*ArrayElem
|
||||
Last []Comment
|
||||
}
|
||||
@@ -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{}
|
@@ -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 {
|
||||
@@ -694,6 +729,7 @@ func (p *Parser) posErr(pos Pos, format string, a ...interface{}) {
|
||||
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)
|
||||
}
|
||||
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,10 +1579,14 @@ 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)) {
|
||||
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
|
||||
p.val = p.val[1:]
|
||||
@@ -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 {
|
||||
if binCmd {
|
||||
// left associativity: in a list of BinaryCmds, the
|
||||
// right recursion should only read a single element
|
||||
if binCmd {
|
||||
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 <cond>", "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 <cond>", "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 <cond>", "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 <cond>", "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 <cond>"
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
@@ -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) {
|
||||
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),
|
||||
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,6 +185,7 @@ func (c *colCounter) Reset(w io.Writer) {
|
||||
// program.
|
||||
type Printer struct {
|
||||
bufWriter
|
||||
tabWriter *tabwriter.Writer
|
||||
cols colCounter
|
||||
|
||||
indentSpaces uint
|
||||
@@ -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())
|
||||
thenEnd := ic.FiPos
|
||||
el := ic.Else
|
||||
if el != nil {
|
||||
thenEnd = el.Position
|
||||
}
|
||||
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 el == nil {
|
||||
p.comments(ic.Last...)
|
||||
} else {
|
||||
var left []Comment
|
||||
for _, c := range ic.ElseComments {
|
||||
if c.Pos().After(ic.ElsePos) {
|
||||
for _, c := range ic.Last {
|
||||
if c.Pos().After(el.Position) {
|
||||
left = append(left, c)
|
||||
break
|
||||
}
|
||||
p.comments(c)
|
||||
}
|
||||
if ic.FollowedByElif() {
|
||||
s := ic.Else.Stmts[0]
|
||||
p.comments(s.Comments...)
|
||||
p.semiRsrv("elif", ic.ElsePos)
|
||||
p.ifClause(s.Cmd.(*IfClause), true)
|
||||
return
|
||||
}
|
||||
if !ic.Else.empty() {
|
||||
p.semiRsrv("else", ic.ElsePos)
|
||||
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('+')
|
@@ -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{
|
@@ -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
|
149
vendor/mvdan.cc/sh/v3/syntax/token_string.go
vendored
Normal file
149
vendor/mvdan.cc/sh/v3/syntax/token_string.go
vendored
Normal file
@@ -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]]
|
||||
}
|
349
vendor/mvdan.cc/sh/v3/syntax/tokens.go
vendored
Normal file
349
vendor/mvdan.cc/sh/v3/syntax/tokens.go
vendored
Normal file
@@ -0,0 +1,349 @@
|
||||
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
package syntax
|
||||
|
||||
//go:generate stringer -type token -linecomment -trimprefix _
|
||||
|
||||
type token uint32
|
||||
|
||||
// The list of all possible tokens.
|
||||
const (
|
||||
illegalTok token = iota
|
||||
|
||||
_EOF
|
||||
_Newl
|
||||
_Lit
|
||||
_LitWord
|
||||
_LitRedir
|
||||
|
||||
sglQuote // '
|
||||
dblQuote // "
|
||||
bckQuote // `
|
||||
|
||||
and // &
|
||||
andAnd // &&
|
||||
orOr // ||
|
||||
or // |
|
||||
orAnd // |&
|
||||
|
||||
dollar // $
|
||||
dollSglQuote // $'
|
||||
dollDblQuote // $"
|
||||
dollBrace // ${
|
||||
dollBrack // $[
|
||||
dollParen // $(
|
||||
dollDblParen // $((
|
||||
leftBrack // [
|
||||
dblLeftBrack // [[
|
||||
leftParen // (
|
||||
dblLeftParen // ((
|
||||
|
||||
rightBrace // }
|
||||
rightBrack // ]
|
||||
rightParen // )
|
||||
dblRightParen // ))
|
||||
semicolon // ;
|
||||
|
||||
dblSemicolon // ;;
|
||||
semiAnd // ;&
|
||||
dblSemiAnd // ;;&
|
||||
semiOr // ;|
|
||||
|
||||
exclMark // !
|
||||
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() }
|
@@ -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)
|
Reference in New Issue
Block a user