diff --git a/vendor/modules.txt b/vendor/modules.txt index 128c3fc0..faebaa02 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -37,7 +37,7 @@ golang.org/x/xerrors golang.org/x/xerrors/internal # gopkg.in/yaml.v2 v2.2.2 gopkg.in/yaml.v2 -# mvdan.cc/sh/v3 v3.0.0-beta1 +# mvdan.cc/sh/v3 v3.0.2 mvdan.cc/sh/v3/expand mvdan.cc/sh/v3/interp mvdan.cc/sh/v3/pattern diff --git a/vendor/mvdan.cc/sh/v3/expand/braces.go b/vendor/mvdan.cc/sh/v3/expand/braces.go index 482f8116..9914fe44 100644 --- a/vendor/mvdan.cc/sh/v3/expand/braces.go +++ b/vendor/mvdan.cc/sh/v3/expand/braces.go @@ -24,13 +24,13 @@ func Braces(word *syntax.Word) []*syntax.Word { continue } if br.Sequence { - var from, to int - if br.Chars { + chars := false + from, err1 := strconv.Atoi(br.Elems[0].Lit()) + to, err2 := strconv.Atoi(br.Elems[1].Lit()) + if err1 != nil || err2 != nil { + chars = true 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 @@ -54,7 +54,7 @@ func Braces(word *syntax.Word) []*syntax.Word { next := *word next.Parts = next.Parts[i+1:] lit := &syntax.Lit{} - if br.Chars { + if chars { lit.Value = string(n) } else { lit.Value = strconv.Itoa(n) diff --git a/vendor/mvdan.cc/sh/v3/expand/expand.go b/vendor/mvdan.cc/sh/v3/expand/expand.go index 0186a8fc..847cd46d 100644 --- a/vendor/mvdan.cc/sh/v3/expand/expand.go +++ b/vendor/mvdan.cc/sh/v3/expand/expand.go @@ -159,6 +159,8 @@ func Document(cfg *Config, word *syntax.Word) (string, error) { return cfg.fieldJoin(field), nil } +const patMode = pattern.Filenames | pattern.Braces + // Pattern expands a single shell word as a pattern, using syntax.QuotePattern // on any non-quoted parts of the input word. The result can be used on // syntax.TranslatePattern directly. @@ -174,7 +176,7 @@ func Pattern(cfg *Config, word *syntax.Word) (string, error) { buf := cfg.strBuilder() for _, part := range field { if part.quote > quoteNone { - buf.WriteString(pattern.QuoteMeta(part.val)) + buf.WriteString(pattern.QuoteMeta(part.val, patMode)) } else { buf.WriteString(part.val) } @@ -345,11 +347,11 @@ func (cfg *Config) escapedGlobField(parts []fieldPart) (escaped string, glob boo buf := cfg.strBuilder() for _, part := range parts { if part.quote > quoteNone { - buf.WriteString(pattern.QuoteMeta(part.val)) + buf.WriteString(pattern.QuoteMeta(part.val, patMode)) continue } buf.WriteString(part.val) - if pattern.HasMeta(part.val) { + if pattern.HasMeta(part.val, patMode) { glob = true } } @@ -367,9 +369,10 @@ func Fields(cfg *Config, words ...*syntax.Word) ([]string, error) { fields := make([]string, 0, len(words)) dir := cfg.envGet("PWD") for _, word := range words { - afterBraces := []*syntax.Word{word} - if syntax.SplitBraces(word) { - afterBraces = Braces(word) + word := *word // make a copy, since SplitBraces replaces the Parts slice + afterBraces := []*syntax.Word{&word} + if syntax.SplitBraces(&word) { + afterBraces = Braces(&word) } for _, word2 := range afterBraces { wfields, err := cfg.wordFields(word2.Parts) @@ -540,7 +543,6 @@ func (cfg *Config) wordFields(wps []syntax.WordPart) ([][]fieldPart, error) { } curField = append(curField, fp) case *syntax.DblQuoted: - allowEmpty = true if len(x.Parts) == 1 { pe, _ := x.Parts[0].(*syntax.ParamExp) if elems := cfg.quotedElems(pe); elems != nil { @@ -556,6 +558,7 @@ func (cfg *Config) wordFields(wps []syntax.WordPart) ([][]fieldPart, error) { continue } } + allowEmpty = true wfield, err := cfg.wordField(x.Parts, quoteDouble) if err != nil { return nil, err diff --git a/vendor/mvdan.cc/sh/v3/interp/interp.go b/vendor/mvdan.cc/sh/v3/interp/interp.go index 257ed04f..c3e8e347 100644 --- a/vendor/mvdan.cc/sh/v3/interp/interp.go +++ b/vendor/mvdan.cc/sh/v3/interp/interp.go @@ -745,7 +745,6 @@ func (r *Runner) sub() *Runner { Env: r.Env, Dir: r.Dir, Params: r.Params, - Funcs: r.Funcs, execHandler: r.execHandler, openHandler: r.openHandler, stdin: r.stdin, @@ -766,6 +765,10 @@ func (r *Runner) sub() *Runner { for k, v := range r.cmdVars { r2.cmdVars[k] = v } + r2.Funcs = make(map[string]*syntax.Stmt, len(r.Funcs)) + for k, v := range r.Funcs { + r2.Funcs[k] = v + } r2.dirStack = append(r2.dirBootstrap[:0], r.dirStack...) r2.fillExpandConfig(r.ectx) r2.didReset = true diff --git a/vendor/mvdan.cc/sh/v3/pattern/pattern.go b/vendor/mvdan.cc/sh/v3/pattern/pattern.go index 185f2d80..a36e30c4 100644 --- a/vendor/mvdan.cc/sh/v3/pattern/pattern.go +++ b/vendor/mvdan.cc/sh/v3/pattern/pattern.go @@ -16,8 +16,8 @@ import ( "strings" ) -// TODO: support Mode in the other APIs too - +// Mode can be used to supply a number of options to the package's functions. +// Not all functions change their behavior with all of the options below. type Mode uint const ( @@ -254,13 +254,17 @@ func charClass(s string) (string, error) { // 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 { +func HasMeta(pat string, mode Mode) bool { for i := 0; i < len(pat); i++ { switch pat[i] { case '\\': i++ case '*', '?', '[': return true + case '{': + if mode&Braces != 0 { + return true + } } } return false @@ -270,11 +274,16 @@ func HasMeta(pat string) bool { // 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 { +func QuoteMeta(pat string, mode Mode) string { any := false loop: for _, r := range pat { switch r { + case '{': + if mode&Braces == 0 { + continue + } + fallthrough case '*', '?', '[', '\\': any = true break loop @@ -288,6 +297,10 @@ loop: switch r { case '*', '?', '[', '\\': buf.WriteByte('\\') + case '{': + if mode&Braces != 0 { + buf.WriteByte('\\') + } } buf.WriteRune(r) } diff --git a/vendor/mvdan.cc/sh/v3/shell/source.go b/vendor/mvdan.cc/sh/v3/shell/source.go deleted file mode 100644 index 13261ffd..00000000 --- a/vendor/mvdan.cc/sh/v3/shell/source.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2018, Daniel Martí -// See LICENSE for licensing information - -package shell - -import ( - "context" - "fmt" - "os" - - "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 -// declared in it. It is a convenience function that uses a default shell -// parser, parses a file from disk, and calls SourceNode. -// -// This function should be used with caution, as it can interpret arbitrary -// code. Untrusted shell programs shoudn't be sourced outside of a sandbox -// environment. -func SourceFile(ctx context.Context, path string) (map[string]expand.Variable, error) { - f, err := os.Open(path) - if err != nil { - return nil, fmt.Errorf("could not open: %v", err) - } - defer f.Close() - file, err := syntax.NewParser().Parse(f, path) - if err != nil { - return nil, fmt.Errorf("could not parse: %v", err) - } - return SourceNode(ctx, file) -} - -// SourceNode sources a shell program from a node and returns the -// variables declared in it. It accepts the same set of node types that -// interp/Runner.Run does. -// -// This function should be used with caution, as it can interpret arbitrary -// code. Untrusted shell programs shoudn't be sourced outside of a sandbox -// environment. -func SourceNode(ctx context.Context, node syntax.Node) (map[string]expand.Variable, error) { - r, _ := interp.New() - if err := r.Run(ctx, node); err != nil { - return nil, fmt.Errorf("could not run: %v", err) - } - // delete the internal shell vars that the user is not - // interested in - delete(r.Vars, "PWD") - delete(r.Vars, "UID") - delete(r.Vars, "HOME") - delete(r.Vars, "PATH") - delete(r.Vars, "IFS") - delete(r.Vars, "OPTIND") - return r.Vars, nil -} diff --git a/vendor/mvdan.cc/sh/v3/syntax/braces.go b/vendor/mvdan.cc/sh/v3/syntax/braces.go index 0baa43a0..dca854fd 100644 --- a/vendor/mvdan.cc/sh/v3/syntax/braces.go +++ b/vendor/mvdan.cc/sh/v3/syntax/braces.go @@ -129,7 +129,6 @@ func SplitBraces(word *Word) bool { broken = true } if !broken { - br.Chars = chars[0] acc.Parts = append(acc.Parts, br) break } diff --git a/vendor/mvdan.cc/sh/v3/syntax/lexer.go b/vendor/mvdan.cc/sh/v3/syntax/lexer.go index ac144cc2..b3daba6d 100644 --- a/vendor/mvdan.cc/sh/v3/syntax/lexer.go +++ b/vendor/mvdan.cc/sh/v3/syntax/lexer.go @@ -256,6 +256,7 @@ skipSpace: for r != '\n' && r != utf8.RuneSelf { if r == escNewl { p.litBs = append(p.litBs, '\\', '\n') + break } r = p.rune() } @@ -316,6 +317,7 @@ skipSpace: } else { p.tok = rightParen p.quote = noState + p.rune() // we are tokenizing manually } default: // including '(', '|' p.advanceLitRe(r) diff --git a/vendor/mvdan.cc/sh/v3/syntax/nodes.go b/vendor/mvdan.cc/sh/v3/syntax/nodes.go index e3d08b24..b8fa71bf 100644 --- a/vendor/mvdan.cc/sh/v3/syntax/nodes.go +++ b/vendor/mvdan.cc/sh/v3/syntax/nodes.go @@ -409,9 +409,9 @@ type Word struct { func (w *Word) Pos() Pos { return w.Parts[0].Pos() } func (w *Word) End() Pos { return w.Parts[len(w.Parts)-1].End() } -// Lit returns the word as a literal value, if the word consists of *syntax.Lit -// nodes only. An empty string is returned otherwise. Words with multiple -// literals, which can appear in some edge cases, are handled properly. +// Lit returns the word as a literal value, if the word consists of *Lit nodes +// only. An empty string is returned otherwise. Words with multiple literals, +// which can appear in some edge cases, are handled properly. // // For example, the word "foo" will return "foo", but the word "foo${bar}" will // return "". @@ -859,12 +859,11 @@ 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}". +// BraceExp represents a Bash brace expression, such as "{a,f}" 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 } diff --git a/vendor/mvdan.cc/sh/v3/syntax/parser.go b/vendor/mvdan.cc/sh/v3/syntax/parser.go index bce05999..e445b0ad 100644 --- a/vendor/mvdan.cc/sh/v3/syntax/parser.go +++ b/vendor/mvdan.cc/sh/v3/syntax/parser.go @@ -14,7 +14,7 @@ import ( // 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). +// call it directly, for example KeepComments(true)(parser). type ParserOption func(*Parser) // KeepComments makes the parser parse comments and attach them to @@ -524,9 +524,13 @@ func (p *Parser) unquotedWordPart(buf *bytes.Buffer, wp WordPart, quotes bool) ( } func (p *Parser) doHeredocs() { + hdocs := p.heredocs[p.buriedHdocs:] + if len(hdocs) == 0 { + // Nothing do do; don't even issue a read. + return + } p.rune() // consume '\n', since we know p.tok == _Newl old := p.quote - hdocs := p.heredocs[p.buriedHdocs:] p.heredocs = p.heredocs[:p.buriedHdocs] for i, r := range hdocs { if p.err != nil { @@ -840,7 +844,7 @@ func (p *Parser) invalidStmtStart() { } func (p *Parser) getWord() *Word { - if parts := p.wordParts(); len(parts) > 0 { + if parts := p.wordParts(); len(parts) > 0 && p.err == nil { return p.word(parts) } return nil @@ -1546,13 +1550,15 @@ func (p *Parser) hasValidIdent() bool { } if end := p.eqlOffs; end > 0 { if p.val[end-1] == '+' && p.lang != LangPOSIX { - end-- + end-- // a+=x } if ValidName(p.val[:end]) { return true } + } else if !ValidName(p.val) { + return false // *[i]=x } - return p.r == '[' + return p.r == '[' // a[i]=x } func (p *Parser) getAssign(needEqual bool) *Assign { diff --git a/vendor/mvdan.cc/sh/v3/syntax/printer.go b/vendor/mvdan.cc/sh/v3/syntax/printer.go index 2bb80e47..8ab1bdcd 100644 --- a/vendor/mvdan.cc/sh/v3/syntax/printer.go +++ b/vendor/mvdan.cc/sh/v3/syntax/printer.go @@ -15,7 +15,7 @@ import ( // 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). +// call it directly, for example KeepPadding(true)(printer). type PrinterOption func(*Printer) // Indent sets the number of spaces used for indentation. If set to 0, @@ -414,13 +414,13 @@ func (p *Printer) flushHeredocs() { bufWriter: &extra, line: r.Hdoc.Pos().Line(), } - p.tabsPrinter.word(r.Hdoc) + p.tabsPrinter.wordParts(r.Hdoc.Parts, true) p.indent() } else { p.indent() } } else if r.Hdoc != nil { - p.word(r.Hdoc) + p.wordParts(r.Hdoc.Parts, true) } p.unquotedWord(r.Word) if r.Hdoc != nil { @@ -478,6 +478,11 @@ func (p *Printer) semiRsrv(s string, pos Pos) { func (p *Printer) flushComments() { for i, c := range p.pendingComments { + if i == 0 { + // Flush any pending heredocs first. Otherwise, the + // comments would become part of a heredoc body. + p.flushHeredocs() + } p.firstLine = false // We can't call any of the newline methods, as they call this // function and we'd recurse forever.